mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 10:17:23 +01:00
Some cleanup, use OtterGui.
This commit is contained in:
parent
a36d1f1935
commit
0fc8992271
40 changed files with 2686 additions and 2792 deletions
|
|
@ -3,166 +3,165 @@ using System.ComponentModel;
|
|||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer
|
||||
namespace Glamourer;
|
||||
|
||||
public static class WriteExtensions
|
||||
{
|
||||
public static class WriteExtensions
|
||||
private static unsafe void Write(IntPtr characterPtr, EquipSlot slot, SetId? id, WeaponType? type, ushort? variant, StainId? stain)
|
||||
{
|
||||
private static unsafe void Write(IntPtr characterPtr, EquipSlot slot, SetId? id, WeaponType? type, ushort? variant, StainId? stain)
|
||||
void WriteWeapon(int offset)
|
||||
{
|
||||
void WriteWeapon(int offset)
|
||||
{
|
||||
var address = (byte*) characterPtr + offset;
|
||||
if (id.HasValue)
|
||||
*(ushort*) address = (ushort) id.Value;
|
||||
var address = (byte*)characterPtr + offset;
|
||||
if (id.HasValue)
|
||||
*(ushort*)address = (ushort)id.Value;
|
||||
|
||||
if (type.HasValue)
|
||||
*(ushort*) (address + 2) = (ushort) type.Value;
|
||||
if (type.HasValue)
|
||||
*(ushort*)(address + 2) = (ushort)type.Value;
|
||||
|
||||
if (variant.HasValue)
|
||||
*(ushort*) (address + 4) = variant.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();
|
||||
}
|
||||
if (stain.HasValue)
|
||||
*(address + 6) = (byte)stain.Value;
|
||||
}
|
||||
|
||||
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)
|
||||
void WriteEquip(int offset)
|
||||
{
|
||||
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);
|
||||
}
|
||||
var address = (uint*)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;
|
||||
}
|
||||
|
||||
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)
|
||||
switch (slot)
|
||||
{
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +0,0 @@
|
|||
using System;
|
||||
|
||||
namespace Glamourer
|
||||
{
|
||||
[Flags]
|
||||
public enum CharacterEquipMask : ushort
|
||||
{
|
||||
None = 0,
|
||||
MainHand = 0b000000000001,
|
||||
OffHand = 0b000000000010,
|
||||
Head = 0b000000000100,
|
||||
Body = 0b000000001000,
|
||||
Hands = 0b000000010000,
|
||||
Legs = 0b000000100000,
|
||||
Feet = 0b000001000000,
|
||||
Ears = 0b000010000000,
|
||||
Neck = 0b000100000000,
|
||||
Wrists = 0b001000000000,
|
||||
RFinger = 0b010000000000,
|
||||
LFinger = 0b100000000000,
|
||||
All = 0b111111111111,
|
||||
}
|
||||
}
|
||||
|
|
@ -1,304 +1,323 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Customization
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
public unsafe struct LazyCustomization
|
||||
{
|
||||
public unsafe struct LazyCustomization
|
||||
public CharacterCustomization* Address;
|
||||
|
||||
public LazyCustomization(IntPtr characterPtr)
|
||||
=> Address = (CharacterCustomization*)(characterPtr + CharacterCustomization.CustomizationOffset);
|
||||
|
||||
public ref CharacterCustomization Value
|
||||
=> ref *Address;
|
||||
|
||||
public LazyCustomization(CharacterCustomization data)
|
||||
=> Address = &data;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct CharacterCustomization
|
||||
{
|
||||
public const int CustomizationOffset = 0x830;
|
||||
public const int CustomizationBytes = 26;
|
||||
|
||||
public static CharacterCustomization Default = new()
|
||||
{
|
||||
public CharacterCustomization* Address;
|
||||
Race = Race.Hyur,
|
||||
Gender = Gender.Male,
|
||||
BodyType = 1,
|
||||
Height = 50,
|
||||
Clan = SubRace.Midlander,
|
||||
Face = 1,
|
||||
Hairstyle = 1,
|
||||
HighlightsOn = false,
|
||||
SkinColor = 1,
|
||||
EyeColorRight = 1,
|
||||
HighlightsColor = 1,
|
||||
FacialFeatures = 0,
|
||||
TattooColor = 1,
|
||||
Eyebrow = 1,
|
||||
EyeColorLeft = 1,
|
||||
EyeShape = 1,
|
||||
Nose = 1,
|
||||
Jaw = 1,
|
||||
Mouth = 1,
|
||||
LipColor = 1,
|
||||
MuscleMass = 50,
|
||||
TailShape = 1,
|
||||
BustSize = 50,
|
||||
FacePaint = 1,
|
||||
FacePaintColor = 1,
|
||||
};
|
||||
|
||||
public LazyCustomization(IntPtr characterPtr)
|
||||
=> Address = (CharacterCustomization*) (characterPtr + CharacterCustomization.CustomizationOffset);
|
||||
public Race Race;
|
||||
private byte _gender;
|
||||
public byte BodyType;
|
||||
public byte Height;
|
||||
public SubRace Clan;
|
||||
public byte Face;
|
||||
public byte Hairstyle;
|
||||
private byte _highlightsOn;
|
||||
public byte SkinColor;
|
||||
public byte EyeColorRight;
|
||||
public byte HairColor;
|
||||
public byte HighlightsColor;
|
||||
public byte FacialFeatures;
|
||||
public byte TattooColor;
|
||||
public byte Eyebrow;
|
||||
public byte EyeColorLeft;
|
||||
private byte _eyeShape;
|
||||
public byte Nose;
|
||||
public byte Jaw;
|
||||
private byte _mouth;
|
||||
public byte LipColor;
|
||||
public byte MuscleMass;
|
||||
public byte TailShape;
|
||||
public byte BustSize;
|
||||
private byte _facePaint;
|
||||
public byte FacePaintColor;
|
||||
|
||||
public ref CharacterCustomization Value
|
||||
=> ref *Address;
|
||||
public Gender Gender
|
||||
{
|
||||
get => (Gender)(_gender + 1);
|
||||
set => _gender = (byte)(value - 1);
|
||||
}
|
||||
|
||||
public LazyCustomization(CharacterCustomization data)
|
||||
=> Address = &data;
|
||||
public bool HighlightsOn
|
||||
{
|
||||
get => (_highlightsOn & 128) == 128;
|
||||
set => _highlightsOn = (byte)(value ? _highlightsOn | 128 : _highlightsOn & 127);
|
||||
}
|
||||
|
||||
public bool FacialFeature(int idx)
|
||||
=> (FacialFeatures & (1 << idx)) != 0;
|
||||
|
||||
public void FacialFeature(int idx, bool set)
|
||||
{
|
||||
if (set)
|
||||
FacialFeatures |= (byte)(1 << idx);
|
||||
else
|
||||
FacialFeatures &= (byte)~(1 << idx);
|
||||
}
|
||||
|
||||
public byte EyeShape
|
||||
{
|
||||
get => (byte)(_eyeShape & 127);
|
||||
set => _eyeShape = (byte)((value & 127) | (_eyeShape & 128));
|
||||
}
|
||||
|
||||
public bool SmallIris
|
||||
{
|
||||
get => (_eyeShape & 128) == 128;
|
||||
set => _eyeShape = (byte)(value ? _eyeShape | 128 : _eyeShape & 127);
|
||||
}
|
||||
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct CharacterCustomization
|
||||
public byte Mouth
|
||||
{
|
||||
public const int CustomizationOffset = 0x830;
|
||||
public const int CustomizationBytes = 26;
|
||||
get => (byte)(_mouth & 127);
|
||||
set => _mouth = (byte)((value & 127) | (_mouth & 128));
|
||||
}
|
||||
|
||||
public static CharacterCustomization Default = new()
|
||||
public bool Lipstick
|
||||
{
|
||||
get => (_mouth & 128) == 128;
|
||||
set => _mouth = (byte)(value ? _mouth | 128 : _mouth & 127);
|
||||
}
|
||||
|
||||
public byte FacePaint
|
||||
{
|
||||
get => (byte)(_facePaint & 127);
|
||||
set => _facePaint = (byte)((value & 127) | (_facePaint & 128));
|
||||
}
|
||||
|
||||
public bool FacePaintReversed
|
||||
{
|
||||
get => (_facePaint & 128) == 128;
|
||||
set => _facePaint = (byte)(value ? _facePaint | 128 : _facePaint & 127);
|
||||
}
|
||||
|
||||
public unsafe void Read(IntPtr customizeAddress)
|
||||
{
|
||||
fixed (Race* ptr = &Race)
|
||||
{
|
||||
Race = Race.Hyur,
|
||||
Gender = Gender.Male,
|
||||
BodyType = 1,
|
||||
Height = 50,
|
||||
Clan = SubRace.Midlander,
|
||||
Face = 1,
|
||||
Hairstyle = 1,
|
||||
HighlightsOn = false,
|
||||
SkinColor = 1,
|
||||
EyeColorRight = 1,
|
||||
HighlightsColor = 1,
|
||||
FacialFeatures = 0,
|
||||
TattooColor = 1,
|
||||
Eyebrow = 1,
|
||||
EyeColorLeft = 1,
|
||||
EyeShape = 1,
|
||||
Nose = 1,
|
||||
Jaw = 1,
|
||||
Mouth = 1,
|
||||
LipColor = 1,
|
||||
MuscleMass = 50,
|
||||
TailShape = 1,
|
||||
BustSize = 50,
|
||||
FacePaint = 1,
|
||||
FacePaintColor = 1,
|
||||
Buffer.MemoryCopy(customizeAddress.ToPointer(), ptr, CustomizationBytes, CustomizationBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void Read(Customization* customize)
|
||||
=> Read((IntPtr)customize);
|
||||
|
||||
public void Read(Character character)
|
||||
=> Read(character.Address + CustomizationOffset);
|
||||
|
||||
public CharacterCustomization(Character character)
|
||||
: this()
|
||||
{
|
||||
Read(character.Address + CustomizationOffset);
|
||||
}
|
||||
|
||||
public byte this[CustomizationId id]
|
||||
{
|
||||
get => id switch
|
||||
{
|
||||
CustomizationId.Race => (byte)Race,
|
||||
CustomizationId.Gender => (byte)Gender,
|
||||
CustomizationId.BodyType => BodyType,
|
||||
CustomizationId.Height => Height,
|
||||
CustomizationId.Clan => (byte)Clan,
|
||||
CustomizationId.Face => Face,
|
||||
CustomizationId.Hairstyle => Hairstyle,
|
||||
CustomizationId.HighlightsOnFlag => _highlightsOn,
|
||||
CustomizationId.SkinColor => SkinColor,
|
||||
CustomizationId.EyeColorR => EyeColorRight,
|
||||
CustomizationId.HairColor => HairColor,
|
||||
CustomizationId.HighlightColor => HighlightsColor,
|
||||
CustomizationId.FacialFeaturesTattoos => FacialFeatures,
|
||||
CustomizationId.TattooColor => TattooColor,
|
||||
CustomizationId.Eyebrows => Eyebrow,
|
||||
CustomizationId.EyeColorL => EyeColorLeft,
|
||||
CustomizationId.EyeShape => EyeShape,
|
||||
CustomizationId.Nose => Nose,
|
||||
CustomizationId.Jaw => Jaw,
|
||||
CustomizationId.Mouth => Mouth,
|
||||
CustomizationId.LipColor => LipColor,
|
||||
CustomizationId.MuscleToneOrTailEarLength => MuscleMass,
|
||||
CustomizationId.TailEarShape => TailShape,
|
||||
CustomizationId.BustSize => BustSize,
|
||||
CustomizationId.FacePaint => FacePaint,
|
||||
CustomizationId.FacePaintColor => FacePaintColor,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(id), id, null),
|
||||
};
|
||||
|
||||
public Race Race;
|
||||
private byte _gender;
|
||||
public byte BodyType;
|
||||
public byte Height;
|
||||
public SubRace Clan;
|
||||
public byte Face;
|
||||
public byte Hairstyle;
|
||||
private byte _highlightsOn;
|
||||
public byte SkinColor;
|
||||
public byte EyeColorRight;
|
||||
public byte HairColor;
|
||||
public byte HighlightsColor;
|
||||
public byte FacialFeatures;
|
||||
public byte TattooColor;
|
||||
public byte Eyebrow;
|
||||
public byte EyeColorLeft;
|
||||
private byte _eyeShape;
|
||||
public byte Nose;
|
||||
public byte Jaw;
|
||||
private byte _mouth;
|
||||
public byte LipColor;
|
||||
public byte MuscleMass;
|
||||
public byte TailShape;
|
||||
public byte BustSize;
|
||||
private byte _facePaint;
|
||||
public byte FacePaintColor;
|
||||
|
||||
public Gender Gender
|
||||
set
|
||||
{
|
||||
get => (Gender) (_gender + 1);
|
||||
set => _gender = (byte) (value - 1);
|
||||
}
|
||||
|
||||
public bool HighlightsOn
|
||||
{
|
||||
get => (_highlightsOn & 128) == 128;
|
||||
set => _highlightsOn = (byte) (value ? _highlightsOn | 128 : _highlightsOn & 127);
|
||||
}
|
||||
|
||||
public bool FacialFeature(int idx)
|
||||
=> (FacialFeatures & (1 << idx)) != 0;
|
||||
|
||||
public void FacialFeature(int idx, bool set)
|
||||
{
|
||||
if (set)
|
||||
FacialFeatures |= (byte) (1 << idx);
|
||||
else
|
||||
FacialFeatures &= (byte) ~(1 << idx);
|
||||
}
|
||||
|
||||
public byte EyeShape
|
||||
{
|
||||
get => (byte) (_eyeShape & 127);
|
||||
set => _eyeShape = (byte) ((value & 127) | (_eyeShape & 128));
|
||||
}
|
||||
|
||||
public bool SmallIris
|
||||
{
|
||||
get => (_eyeShape & 128) == 128;
|
||||
set => _eyeShape = (byte) (value ? _eyeShape | 128 : _eyeShape & 127);
|
||||
}
|
||||
|
||||
|
||||
public byte Mouth
|
||||
{
|
||||
get => (byte) (_mouth & 127);
|
||||
set => _mouth = (byte) ((value & 127) | (_mouth & 128));
|
||||
}
|
||||
|
||||
public bool Lipstick
|
||||
{
|
||||
get => (_mouth & 128) == 128;
|
||||
set => _mouth = (byte) (value ? _mouth | 128 : _mouth & 127);
|
||||
}
|
||||
|
||||
public byte FacePaint
|
||||
{
|
||||
get => (byte) (_facePaint & 127);
|
||||
set => _facePaint = (byte) ((value & 127) | (_facePaint & 128));
|
||||
}
|
||||
|
||||
public bool FacePaintReversed
|
||||
{
|
||||
get => (_facePaint & 128) == 128;
|
||||
set => _facePaint = (byte) (value ? _facePaint | 128 : _facePaint & 127);
|
||||
}
|
||||
|
||||
public unsafe void Read(IntPtr customizeAddress)
|
||||
{
|
||||
fixed (Race* ptr = &Race)
|
||||
switch (id)
|
||||
{
|
||||
Buffer.MemoryCopy(customizeAddress.ToPointer(), ptr, CustomizationBytes, CustomizationBytes);
|
||||
case CustomizationId.Race:
|
||||
Race = (Race)value;
|
||||
break;
|
||||
case CustomizationId.Gender:
|
||||
Gender = (Gender)value;
|
||||
break;
|
||||
case CustomizationId.BodyType:
|
||||
BodyType = value;
|
||||
break;
|
||||
case CustomizationId.Height:
|
||||
Height = value;
|
||||
break;
|
||||
case CustomizationId.Clan:
|
||||
Clan = (SubRace)value;
|
||||
break;
|
||||
case CustomizationId.Face:
|
||||
Face = value;
|
||||
break;
|
||||
case CustomizationId.Hairstyle:
|
||||
Hairstyle = value;
|
||||
break;
|
||||
case CustomizationId.HighlightsOnFlag:
|
||||
HighlightsOn = (value & 128) == 128;
|
||||
break;
|
||||
case CustomizationId.SkinColor:
|
||||
SkinColor = value;
|
||||
break;
|
||||
case CustomizationId.EyeColorR:
|
||||
EyeColorRight = value;
|
||||
break;
|
||||
case CustomizationId.HairColor:
|
||||
HairColor = value;
|
||||
break;
|
||||
case CustomizationId.HighlightColor:
|
||||
HighlightsColor = value;
|
||||
break;
|
||||
case CustomizationId.FacialFeaturesTattoos:
|
||||
FacialFeatures = value;
|
||||
break;
|
||||
case CustomizationId.TattooColor:
|
||||
TattooColor = value;
|
||||
break;
|
||||
case CustomizationId.Eyebrows:
|
||||
Eyebrow = value;
|
||||
break;
|
||||
case CustomizationId.EyeColorL:
|
||||
EyeColorLeft = value;
|
||||
break;
|
||||
case CustomizationId.EyeShape:
|
||||
EyeShape = value;
|
||||
break;
|
||||
case CustomizationId.Nose:
|
||||
Nose = value;
|
||||
break;
|
||||
case CustomizationId.Jaw:
|
||||
Jaw = value;
|
||||
break;
|
||||
case CustomizationId.Mouth:
|
||||
Mouth = value;
|
||||
break;
|
||||
case CustomizationId.LipColor:
|
||||
LipColor = value;
|
||||
break;
|
||||
case CustomizationId.MuscleToneOrTailEarLength:
|
||||
MuscleMass = value;
|
||||
break;
|
||||
case CustomizationId.TailEarShape:
|
||||
TailShape = value;
|
||||
break;
|
||||
case CustomizationId.BustSize:
|
||||
BustSize = value;
|
||||
break;
|
||||
case CustomizationId.FacePaint:
|
||||
FacePaint = value;
|
||||
break;
|
||||
case CustomizationId.FacePaintColor:
|
||||
FacePaintColor = value;
|
||||
break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(id), id, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Read(Character character)
|
||||
=> Read(character.Address + CustomizationOffset);
|
||||
|
||||
public CharacterCustomization(Character character)
|
||||
: this()
|
||||
public unsafe void Write(IntPtr characterAddress)
|
||||
{
|
||||
fixed (Race* ptr = &Race)
|
||||
{
|
||||
Read(character.Address + CustomizationOffset);
|
||||
Buffer.MemoryCopy(ptr, (byte*)characterAddress + CustomizationOffset, CustomizationBytes, CustomizationBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public byte this[CustomizationId id]
|
||||
public unsafe void WriteBytes(byte[] array, int offset = 0)
|
||||
{
|
||||
fixed (Race* ptr = &Race)
|
||||
{
|
||||
get => id switch
|
||||
{
|
||||
CustomizationId.Race => (byte) Race,
|
||||
CustomizationId.Gender => (byte) Gender,
|
||||
CustomizationId.BodyType => BodyType,
|
||||
CustomizationId.Height => Height,
|
||||
CustomizationId.Clan => (byte) Clan,
|
||||
CustomizationId.Face => Face,
|
||||
CustomizationId.Hairstyle => Hairstyle,
|
||||
CustomizationId.HighlightsOnFlag => _highlightsOn,
|
||||
CustomizationId.SkinColor => SkinColor,
|
||||
CustomizationId.EyeColorR => EyeColorRight,
|
||||
CustomizationId.HairColor => HairColor,
|
||||
CustomizationId.HighlightColor => HighlightsColor,
|
||||
CustomizationId.FacialFeaturesTattoos => FacialFeatures,
|
||||
CustomizationId.TattooColor => TattooColor,
|
||||
CustomizationId.Eyebrows => Eyebrow,
|
||||
CustomizationId.EyeColorL => EyeColorLeft,
|
||||
CustomizationId.EyeShape => EyeShape,
|
||||
CustomizationId.Nose => Nose,
|
||||
CustomizationId.Jaw => Jaw,
|
||||
CustomizationId.Mouth => Mouth,
|
||||
CustomizationId.LipColor => LipColor,
|
||||
CustomizationId.MuscleToneOrTailEarLength => MuscleMass,
|
||||
CustomizationId.TailEarShape => TailShape,
|
||||
CustomizationId.BustSize => BustSize,
|
||||
CustomizationId.FacePaint => FacePaint,
|
||||
CustomizationId.FacePaintColor => FacePaintColor,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(id), id, null),
|
||||
};
|
||||
set
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case CustomizationId.Race:
|
||||
Race = (Race) value;
|
||||
break;
|
||||
case CustomizationId.Gender:
|
||||
Gender = (Gender) value;
|
||||
break;
|
||||
case CustomizationId.BodyType:
|
||||
BodyType = value;
|
||||
break;
|
||||
case CustomizationId.Height:
|
||||
Height = value;
|
||||
break;
|
||||
case CustomizationId.Clan:
|
||||
Clan = (SubRace) value;
|
||||
break;
|
||||
case CustomizationId.Face:
|
||||
Face = value;
|
||||
break;
|
||||
case CustomizationId.Hairstyle:
|
||||
Hairstyle = value;
|
||||
break;
|
||||
case CustomizationId.HighlightsOnFlag:
|
||||
HighlightsOn = (value & 128) == 128;
|
||||
break;
|
||||
case CustomizationId.SkinColor:
|
||||
SkinColor = value;
|
||||
break;
|
||||
case CustomizationId.EyeColorR:
|
||||
EyeColorRight = value;
|
||||
break;
|
||||
case CustomizationId.HairColor:
|
||||
HairColor = value;
|
||||
break;
|
||||
case CustomizationId.HighlightColor:
|
||||
HighlightsColor = value;
|
||||
break;
|
||||
case CustomizationId.FacialFeaturesTattoos:
|
||||
FacialFeatures = value;
|
||||
break;
|
||||
case CustomizationId.TattooColor:
|
||||
TattooColor = value;
|
||||
break;
|
||||
case CustomizationId.Eyebrows:
|
||||
Eyebrow = value;
|
||||
break;
|
||||
case CustomizationId.EyeColorL:
|
||||
EyeColorLeft = value;
|
||||
break;
|
||||
case CustomizationId.EyeShape:
|
||||
EyeShape = value;
|
||||
break;
|
||||
case CustomizationId.Nose:
|
||||
Nose = value;
|
||||
break;
|
||||
case CustomizationId.Jaw:
|
||||
Jaw = value;
|
||||
break;
|
||||
case CustomizationId.Mouth:
|
||||
Mouth = value;
|
||||
break;
|
||||
case CustomizationId.LipColor:
|
||||
LipColor = value;
|
||||
break;
|
||||
case CustomizationId.MuscleToneOrTailEarLength:
|
||||
MuscleMass = value;
|
||||
break;
|
||||
case CustomizationId.TailEarShape:
|
||||
TailShape = value;
|
||||
break;
|
||||
case CustomizationId.BustSize:
|
||||
BustSize = value;
|
||||
break;
|
||||
case CustomizationId.FacePaint:
|
||||
FacePaint = value;
|
||||
break;
|
||||
case CustomizationId.FacePaintColor:
|
||||
FacePaintColor = value;
|
||||
break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(id), id, null);
|
||||
}
|
||||
}
|
||||
Marshal.Copy(new IntPtr(ptr), array, offset, CustomizationBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void Write(IntPtr characterAddress)
|
||||
{
|
||||
fixed (Race* ptr = &Race)
|
||||
{
|
||||
Buffer.MemoryCopy(ptr, (byte*) characterAddress + CustomizationOffset, CustomizationBytes, CustomizationBytes);
|
||||
}
|
||||
}
|
||||
public byte[] ToBytes()
|
||||
{
|
||||
var ret = new byte[CustomizationBytes];
|
||||
WriteBytes(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
public unsafe void WriteBytes(byte[] array, int offset = 0)
|
||||
{
|
||||
fixed (Race* ptr = &Race)
|
||||
{
|
||||
Marshal.Copy(new IntPtr(ptr), array, offset, CustomizationBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] ToBytes()
|
||||
{
|
||||
var ret = new byte[CustomizationBytes];
|
||||
WriteBytes(ret);
|
||||
return ret;
|
||||
}
|
||||
public string HumanReadable()
|
||||
{
|
||||
// TODO
|
||||
var sb = new StringBuilder();
|
||||
sb.Append($"Race: {Race.ToName()} - {Clan.ToName()}\n");
|
||||
sb.Append($"Gender: {Gender.ToName()}\n");
|
||||
sb.Append($"Height: {Height}%\n");
|
||||
sb.Append($"Face: #{Face}\n");
|
||||
sb.Append($"Hairstyle: #{Hairstyle}\n");
|
||||
sb.Append($"Haircolor: #{HairColor}");
|
||||
if (HighlightsOn)
|
||||
sb.Append($" with Highlights #{HighlightsColor}\n");
|
||||
else
|
||||
sb.Append('\n');
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,24 +1,23 @@
|
|||
using Dalamud.Data;
|
||||
using Dalamud.Plugin;
|
||||
|
||||
namespace Glamourer
|
||||
{
|
||||
public class CmpFile
|
||||
{
|
||||
public readonly Lumina.Data.FileResource File;
|
||||
public readonly uint[] RgbaColors;
|
||||
namespace Glamourer;
|
||||
|
||||
public CmpFile(DataManager gameData)
|
||||
public class CmpFile
|
||||
{
|
||||
public readonly Lumina.Data.FileResource File;
|
||||
public readonly uint[] RgbaColors;
|
||||
|
||||
public CmpFile(DataManager gameData)
|
||||
{
|
||||
File = gameData.GetFile("chara/xls/charamake/human.cmp")!;
|
||||
RgbaColors = new uint[File.Data.Length >> 2];
|
||||
for (var i = 0; i < File.Data.Length; i += 4)
|
||||
{
|
||||
File = gameData.GetFile("chara/xls/charamake/human.cmp")!;
|
||||
RgbaColors = 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);
|
||||
}
|
||||
RgbaColors[i >> 2] = File.Data[i]
|
||||
| (uint)(File.Data[i + 1] << 8)
|
||||
| (uint)(File.Data[i + 2] << 16)
|
||||
| (uint)(File.Data[i + 3] << 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,370 +5,369 @@ using System.Reflection;
|
|||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.Util;
|
||||
using Lumina.Data;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Race = Penumbra.GameData.Enums.Race;
|
||||
|
||||
namespace Glamourer.Customization
|
||||
{
|
||||
public partial class CustomizationOptions
|
||||
{
|
||||
internal static readonly Race[] Races = ((Race[]) Enum.GetValues(typeof(Race))).Skip(1).ToArray();
|
||||
internal static readonly SubRace[] Clans = ((SubRace[]) Enum.GetValues(typeof(SubRace))).Skip(1).ToArray();
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
internal static readonly Gender[] Genders =
|
||||
public partial class CustomizationOptions
|
||||
{
|
||||
internal static readonly Race[] Races = ((Race[])Enum.GetValues(typeof(Race))).Skip(1).ToArray();
|
||||
internal static readonly SubRace[] Clans = ((SubRace[])Enum.GetValues(typeof(SubRace))).Skip(1).ToArray();
|
||||
|
||||
internal static readonly Gender[] Genders =
|
||||
{
|
||||
Gender.Male,
|
||||
Gender.Female,
|
||||
};
|
||||
|
||||
internal CustomizationSet GetList(SubRace race, Gender gender)
|
||||
=> _list[ToIndex(race, gender)];
|
||||
|
||||
internal ImGuiScene.TextureWrap GetIcon(uint id)
|
||||
=> _icons.LoadIcon(id);
|
||||
|
||||
private static readonly int ListSize = Clans.Length * Genders.Length;
|
||||
|
||||
private readonly CustomizationSet[] _list = new CustomizationSet[ListSize];
|
||||
private readonly IconStorage _icons;
|
||||
|
||||
private static void ThrowException(SubRace race, Gender gender)
|
||||
=> throw new Exception($"Invalid customization requested for {race} {gender}.");
|
||||
|
||||
private static int ToIndex(SubRace race, Gender gender)
|
||||
{
|
||||
if (race == SubRace.Unknown || gender != Gender.Female && gender != Gender.Male)
|
||||
ThrowException(race, gender);
|
||||
|
||||
var ret = (int)race - 1;
|
||||
ret = ret * Genders.Length + (gender == Gender.Female ? 1 : 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private Customization[] GetHairStyles(SubRace race, Gender gender)
|
||||
{
|
||||
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||
var hairList = new List<Customization>(row.Unknown30);
|
||||
for (var i = 0; i < row.Unknown30; ++i)
|
||||
{
|
||||
Gender.Male,
|
||||
Gender.Female,
|
||||
var name = $"Unknown{66 + i * 9}";
|
||||
var customizeIdx =
|
||||
(uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
|
||||
?? uint.MaxValue;
|
||||
if (customizeIdx == uint.MaxValue)
|
||||
continue;
|
||||
|
||||
var hairRow = _customizeSheet.GetRow(customizeIdx);
|
||||
hairList.Add(hairRow != null
|
||||
? new Customization(CustomizationId.Hairstyle, hairRow.FeatureID, hairRow.Icon, (ushort)hairRow.RowId)
|
||||
: new Customization(CustomizationId.Hairstyle, (byte)i, customizeIdx, 0));
|
||||
}
|
||||
|
||||
return hairList.ToArray();
|
||||
}
|
||||
|
||||
private Customization[] CreateColorPicker(CustomizationId id, int offset, int num, bool light = false)
|
||||
=> _cmpFile.RgbaColors.Skip(offset).Take(num)
|
||||
.Select((c, i) => new Customization(id, (byte)(light ? 128 + i : 0 + i), c, (ushort)(offset + i)))
|
||||
.ToArray();
|
||||
|
||||
private (Customization[], Customization[]) GetColors(SubRace race, Gender gender)
|
||||
{
|
||||
if (race > SubRace.Veena || race == SubRace.Unknown)
|
||||
throw new ArgumentOutOfRangeException(nameof(race), race, null);
|
||||
|
||||
var gv = gender == Gender.Male ? 0 : 1;
|
||||
var idx = ((int)race * 2 + gv) * 5 + 3;
|
||||
|
||||
return (CreateColorPicker(CustomizationId.SkinColor, idx << 8, 192),
|
||||
CreateColorPicker(CustomizationId.HairColor, (idx + 1) << 8, 192));
|
||||
}
|
||||
|
||||
private Customization FromValueAndIndex(CustomizationId id, uint value, int index)
|
||||
{
|
||||
var row = _customizeSheet.GetRow(value);
|
||||
return row == null
|
||||
? new Customization(id, (byte)(index + 1), value, 0)
|
||||
: new Customization(id, row.FeatureID, row.Icon, (ushort)row.RowId);
|
||||
}
|
||||
|
||||
private static int GetListSize(CharaMakeParams row, CustomizationId id)
|
||||
{
|
||||
var menu = row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == id);
|
||||
return menu?.Size ?? 0;
|
||||
}
|
||||
|
||||
private Customization[] GetFacePaints(SubRace race, Gender gender)
|
||||
{
|
||||
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||
var paintList = new List<Customization>(row.Unknown37);
|
||||
for (var i = 0; i < row.Unknown37; ++i)
|
||||
{
|
||||
var name = $"Unknown{73 + i * 9}";
|
||||
var customizeIdx =
|
||||
(uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
|
||||
?? uint.MaxValue;
|
||||
if (customizeIdx == uint.MaxValue)
|
||||
continue;
|
||||
|
||||
var paintRow = _customizeSheet.GetRow(customizeIdx);
|
||||
paintList.Add(paintRow != null
|
||||
? new Customization(CustomizationId.FacePaint, paintRow.FeatureID, paintRow.Icon, (ushort)paintRow.RowId)
|
||||
: new Customization(CustomizationId.FacePaint, (byte)i, customizeIdx, 0));
|
||||
}
|
||||
|
||||
return paintList.ToArray();
|
||||
}
|
||||
|
||||
private Customization[] GetTailEarShapes(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.TailEarShape)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.TailEarShape, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
|
||||
private Customization[] GetFaces(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.Face)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.Face, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
|
||||
private Customization[] HrothgarFurPattern(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.LipColor)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.LipColor, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
|
||||
private Customization[] HrothgarFaces(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.Hairstyle)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.Hairstyle, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
|
||||
private CustomizationSet GetSet(SubRace race, Gender gender)
|
||||
{
|
||||
var (skin, hair) = GetColors(race, gender);
|
||||
var row = _listSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||
var set = new CustomizationSet(race, gender)
|
||||
{
|
||||
HairStyles = GetHairStyles(race, gender),
|
||||
HairColors = hair,
|
||||
SkinColors = skin,
|
||||
EyeColors = _eyeColorPicker,
|
||||
HighlightColors = _highlightPicker,
|
||||
TattooColors = _tattooColorPicker,
|
||||
LipColorsDark = race.ToRace() == Race.Hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark,
|
||||
LipColorsLight = race.ToRace() == Race.Hrothgar ? Array.Empty<Customization>() : _lipColorPickerLight,
|
||||
FacePaintColorsDark = _facePaintColorPickerDark,
|
||||
FacePaintColorsLight = _facePaintColorPickerLight,
|
||||
Faces = GetFaces(row),
|
||||
NumEyebrows = GetListSize(row, CustomizationId.Eyebrows),
|
||||
NumEyeShapes = GetListSize(row, CustomizationId.EyeShape),
|
||||
NumNoseShapes = GetListSize(row, CustomizationId.Nose),
|
||||
NumJawShapes = GetListSize(row, CustomizationId.Jaw),
|
||||
NumMouthShapes = GetListSize(row, CustomizationId.Mouth),
|
||||
FacePaints = GetFacePaints(race, gender),
|
||||
TailEarShapes = GetTailEarShapes(row),
|
||||
};
|
||||
|
||||
internal CustomizationSet GetList(SubRace race, Gender gender)
|
||||
=> _list[ToIndex(race, gender)];
|
||||
if (GetListSize(row, CustomizationId.BustSize) > 0)
|
||||
set.SetAvailable(CustomizationId.BustSize);
|
||||
if (GetListSize(row, CustomizationId.MuscleToneOrTailEarLength) > 0)
|
||||
set.SetAvailable(CustomizationId.MuscleToneOrTailEarLength);
|
||||
|
||||
internal ImGuiScene.TextureWrap GetIcon(uint id)
|
||||
=> _icons.LoadIcon(id);
|
||||
|
||||
private static readonly int ListSize = Clans.Length * Genders.Length;
|
||||
|
||||
private readonly CustomizationSet[] _list = new CustomizationSet[ListSize];
|
||||
private readonly IconStorage _icons;
|
||||
|
||||
private static void ThrowException(SubRace race, Gender gender)
|
||||
=> throw new Exception($"Invalid customization requested for {race} {gender}.");
|
||||
|
||||
private static int ToIndex(SubRace race, Gender gender)
|
||||
if (set.NumEyebrows > 0)
|
||||
set.SetAvailable(CustomizationId.Eyebrows);
|
||||
if (set.NumEyeShapes > 0)
|
||||
set.SetAvailable(CustomizationId.EyeShape);
|
||||
if (set.NumNoseShapes > 0)
|
||||
set.SetAvailable(CustomizationId.Nose);
|
||||
if (set.NumJawShapes > 0)
|
||||
set.SetAvailable(CustomizationId.Jaw);
|
||||
if (set.NumMouthShapes > 0)
|
||||
set.SetAvailable(CustomizationId.Mouth);
|
||||
if (set.FacePaints.Count > 0)
|
||||
{
|
||||
if (race == SubRace.Unknown || gender != Gender.Female && gender != Gender.Male)
|
||||
ThrowException(race, gender);
|
||||
|
||||
var ret = (int) race - 1;
|
||||
ret = ret * Genders.Length + (gender == Gender.Female ? 1 : 0);
|
||||
return ret;
|
||||
set.SetAvailable(CustomizationId.FacePaint);
|
||||
set.SetAvailable(CustomizationId.FacePaintColor);
|
||||
}
|
||||
|
||||
private Customization[] GetHairStyles(SubRace race, Gender gender)
|
||||
{
|
||||
var row = _hairSheet.GetRow(((uint) race - 1) * 2 - 1 + (uint) gender)!;
|
||||
var hairList = new List<Customization>(row.Unknown30);
|
||||
for (var i = 0; i < row.Unknown30; ++i)
|
||||
{
|
||||
var name = $"Unknown{66 + i * 9}";
|
||||
var customizeIdx =
|
||||
(uint?) row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
|
||||
?? uint.MaxValue;
|
||||
if (customizeIdx == uint.MaxValue)
|
||||
continue;
|
||||
if (set.TailEarShapes.Count > 0)
|
||||
set.SetAvailable(CustomizationId.TailEarShape);
|
||||
if (set.Faces.Count > 0)
|
||||
set.SetAvailable(CustomizationId.Face);
|
||||
|
||||
var hairRow = _customizeSheet.GetRow(customizeIdx);
|
||||
hairList.Add(hairRow != null
|
||||
? new Customization(CustomizationId.Hairstyle, hairRow.FeatureID, hairRow.Icon, (ushort) hairRow.RowId)
|
||||
: new Customization(CustomizationId.Hairstyle, (byte) i, customizeIdx, 0));
|
||||
var count = set.Faces.Count;
|
||||
var featureDict = new List<IReadOnlyList<Customization>>(count);
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
featureDict.Add(row.FacialFeatureByFace[i].Icons.Select((val, idx)
|
||||
=> new Customization(CustomizationId.FacialFeaturesTattoos, (byte)(1 << idx), val, (ushort)(i * 8 + idx)))
|
||||
.Append(new Customization(CustomizationId.FacialFeaturesTattoos, 1 << 7, 137905, (ushort)((i + 1) * 8)))
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
set.FeaturesTattoos = featureDict;
|
||||
|
||||
var nameArray = ((CustomizationId[])Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
||||
{
|
||||
var menu = row.Menus
|
||||
.Cast<CharaMakeParams.Menu?>()
|
||||
.FirstOrDefault(m => m!.Value.Customization == c);
|
||||
if (menu == null)
|
||||
{
|
||||
if (c == CustomizationId.HighlightsOnFlag)
|
||||
return _lobby.GetRow(237)?.Text.ToString() ?? "Highlights";
|
||||
|
||||
return c.ToDefaultName();
|
||||
}
|
||||
|
||||
return hairList.ToArray();
|
||||
}
|
||||
if (c == CustomizationId.FacialFeaturesTattoos)
|
||||
return
|
||||
$"{_lobby.GetRow(1741)?.Text.ToString() ?? "Facial Features"} & {_lobby.GetRow(1742)?.Text.ToString() ?? "Tattoos"}";
|
||||
|
||||
private Customization[] CreateColorPicker(CustomizationId id, int offset, int num, bool light = false)
|
||||
=> _cmpFile.RgbaColors.Skip(offset).Take(num)
|
||||
.Select((c, i) => new Customization(id, (byte) (light ? 128 + i : 0 + i), c, (ushort) (offset + i)))
|
||||
.ToArray();
|
||||
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;
|
||||
|
||||
private (Customization[], Customization[]) GetColors(SubRace race, Gender gender)
|
||||
set.Types = ((CustomizationId[])Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
||||
{
|
||||
if (race > SubRace.Veena || race == SubRace.Unknown)
|
||||
throw new ArgumentOutOfRangeException(nameof(race), race, null);
|
||||
|
||||
var gv = gender == Gender.Male ? 0 : 1;
|
||||
var idx = ((int) race * 2 + gv) * 5 + 3;
|
||||
|
||||
return (CreateColorPicker(CustomizationId.SkinColor, idx << 8, 192),
|
||||
CreateColorPicker(CustomizationId.HairColor, (idx + 1) << 8, 192));
|
||||
}
|
||||
|
||||
private Customization FromValueAndIndex(CustomizationId id, uint value, int index)
|
||||
{
|
||||
var row = _customizeSheet.GetRow(value);
|
||||
return row == null
|
||||
? new Customization(id, (byte) (index + 1), value, 0)
|
||||
: new Customization(id, row.FeatureID, row.Icon, (ushort) row.RowId);
|
||||
}
|
||||
|
||||
private static int GetListSize(CharaMakeParams row, CustomizationId id)
|
||||
{
|
||||
var menu = row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == id);
|
||||
return menu?.Size ?? 0;
|
||||
}
|
||||
|
||||
private Customization[] GetFacePaints(SubRace race, Gender gender)
|
||||
{
|
||||
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||
var paintList = new List<Customization>(row.Unknown37);
|
||||
for (var i = 0; i < row.Unknown37; ++i)
|
||||
switch (c)
|
||||
{
|
||||
var name = $"Unknown{73 + i * 9}";
|
||||
var customizeIdx =
|
||||
(uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
|
||||
?? uint.MaxValue;
|
||||
if (customizeIdx == uint.MaxValue)
|
||||
continue;
|
||||
|
||||
var paintRow = _customizeSheet.GetRow(customizeIdx);
|
||||
paintList.Add(paintRow != null
|
||||
? new Customization(CustomizationId.FacePaint, paintRow.FeatureID, paintRow.Icon, (ushort)paintRow.RowId)
|
||||
: new Customization(CustomizationId.FacePaint, (byte)i, customizeIdx, 0));
|
||||
case CustomizationId.HighlightColor:
|
||||
case CustomizationId.EyeColorL:
|
||||
case CustomizationId.EyeColorR:
|
||||
return CharaMakeParams.MenuType.ColorPicker;
|
||||
}
|
||||
|
||||
return paintList.ToArray();
|
||||
var menu = row.Menus
|
||||
.Cast<CharaMakeParams.Menu?>()
|
||||
.FirstOrDefault(m => m!.Value.Customization == c);
|
||||
return menu?.Type ?? CharaMakeParams.MenuType.ListSelector;
|
||||
}).ToArray();
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
private readonly ExcelSheet<CharaMakeCustomize> _customizeSheet;
|
||||
private readonly ExcelSheet<CharaMakeParams> _listSheet;
|
||||
private readonly ExcelSheet<HairMakeType> _hairSheet;
|
||||
private readonly ExcelSheet<Lobby> _lobby;
|
||||
private readonly CmpFile _cmpFile;
|
||||
private readonly Customization[] _highlightPicker;
|
||||
private readonly Customization[] _eyeColorPicker;
|
||||
private readonly Customization[] _facePaintColorPickerDark;
|
||||
private readonly Customization[] _facePaintColorPickerLight;
|
||||
private readonly Customization[] _lipColorPickerDark;
|
||||
private readonly Customization[] _lipColorPickerLight;
|
||||
private readonly Customization[] _tattooColorPicker;
|
||||
private readonly string[] _names = new string[(int)CustomName.Num];
|
||||
|
||||
public string GetName(CustomName name)
|
||||
=> _names[(int)name];
|
||||
|
||||
private static Language FromClientLanguage(ClientLanguage language)
|
||||
=> language switch
|
||||
{
|
||||
ClientLanguage.English => Language.English,
|
||||
ClientLanguage.French => Language.French,
|
||||
ClientLanguage.German => Language.German,
|
||||
ClientLanguage.Japanese => Language.Japanese,
|
||||
_ => Language.English,
|
||||
};
|
||||
|
||||
internal CustomizationOptions(DalamudPluginInterface pi, DataManager gameData, ClientLanguage language)
|
||||
{
|
||||
try
|
||||
{
|
||||
_cmpFile = new CmpFile(gameData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception("READ THIS\n======== Could not obtain the human.cmp file which is necessary for color sets.\n"
|
||||
+ "======== This usually indicates an error with your index files caused by TexTools modifications.\n"
|
||||
+ "======== If you have used TexTools before, you will probably need to start over in it to use Glamourer.", e);
|
||||
}
|
||||
|
||||
private Customization[] GetTailEarShapes(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.TailEarShape)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.TailEarShape, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
|
||||
private Customization[] GetFaces(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.Face)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.Face, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
|
||||
private Customization[] HrothgarFurPattern(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.LipColor)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.LipColor, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
|
||||
private Customization[] HrothgarFaces(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.Hairstyle)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.Hairstyle, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
|
||||
private CustomizationSet GetSet(SubRace race, Gender gender)
|
||||
{
|
||||
var (skin, hair) = GetColors(race, gender);
|
||||
var row = _listSheet.GetRow(((uint) race - 1) * 2 - 1 + (uint) gender)!;
|
||||
var set = new CustomizationSet(race, gender)
|
||||
{
|
||||
HairStyles = GetHairStyles(race, gender),
|
||||
HairColors = hair,
|
||||
SkinColors = skin,
|
||||
EyeColors = _eyeColorPicker,
|
||||
HighlightColors = _highlightPicker,
|
||||
TattooColors = _tattooColorPicker,
|
||||
LipColorsDark = race.ToRace() == Race.Hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark,
|
||||
LipColorsLight = race.ToRace() == Race.Hrothgar ? Array.Empty<Customization>() : _lipColorPickerLight,
|
||||
FacePaintColorsDark = _facePaintColorPickerDark,
|
||||
FacePaintColorsLight = _facePaintColorPickerLight,
|
||||
Faces = GetFaces(row),
|
||||
NumEyebrows = GetListSize(row, CustomizationId.Eyebrows),
|
||||
NumEyeShapes = GetListSize(row, CustomizationId.EyeShape),
|
||||
NumNoseShapes = GetListSize(row, CustomizationId.Nose),
|
||||
NumJawShapes = GetListSize(row, CustomizationId.Jaw),
|
||||
NumMouthShapes = GetListSize(row, CustomizationId.Mouth),
|
||||
FacePaints = GetFacePaints(race, gender),
|
||||
TailEarShapes = GetTailEarShapes(row),
|
||||
};
|
||||
|
||||
if (GetListSize(row, CustomizationId.BustSize) > 0)
|
||||
set.SetAvailable(CustomizationId.BustSize);
|
||||
if (GetListSize(row, CustomizationId.MuscleToneOrTailEarLength) > 0)
|
||||
set.SetAvailable(CustomizationId.MuscleToneOrTailEarLength);
|
||||
|
||||
if (set.NumEyebrows > 0)
|
||||
set.SetAvailable(CustomizationId.Eyebrows);
|
||||
if (set.NumEyeShapes > 0)
|
||||
set.SetAvailable(CustomizationId.EyeShape);
|
||||
if (set.NumNoseShapes > 0)
|
||||
set.SetAvailable(CustomizationId.Nose);
|
||||
if (set.NumJawShapes > 0)
|
||||
set.SetAvailable(CustomizationId.Jaw);
|
||||
if (set.NumMouthShapes > 0)
|
||||
set.SetAvailable(CustomizationId.Mouth);
|
||||
if (set.FacePaints.Count > 0)
|
||||
{
|
||||
set.SetAvailable(CustomizationId.FacePaint);
|
||||
set.SetAvailable(CustomizationId.FacePaintColor);
|
||||
}
|
||||
|
||||
if (set.TailEarShapes.Count > 0)
|
||||
set.SetAvailable(CustomizationId.TailEarShape);
|
||||
if (set.Faces.Count > 0)
|
||||
set.SetAvailable(CustomizationId.Face);
|
||||
|
||||
var count = set.Faces.Count;
|
||||
var featureDict = new List<IReadOnlyList<Customization>>(count);
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
featureDict.Add(row.FacialFeatureByFace[i].Icons.Select((val, idx)
|
||||
=> new Customization(CustomizationId.FacialFeaturesTattoos, (byte) (1 << idx), val, (ushort) (i * 8 + idx)))
|
||||
.Append(new Customization(CustomizationId.FacialFeaturesTattoos, 1 << 7, 137905, (ushort) ((i + 1) * 8)))
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
set.FeaturesTattoos = featureDict;
|
||||
|
||||
var nameArray = ((CustomizationId[]) Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
||||
{
|
||||
var menu = row.Menus
|
||||
.Cast<CharaMakeParams.Menu?>()
|
||||
.FirstOrDefault(m => m!.Value.Customization == c);
|
||||
if (menu == null)
|
||||
{
|
||||
if (c == CustomizationId.HighlightsOnFlag)
|
||||
return _lobby.GetRow(237)?.Text.ToString() ?? "Highlights";
|
||||
|
||||
return c.ToDefaultName();
|
||||
}
|
||||
|
||||
if (c == CustomizationId.FacialFeaturesTattoos)
|
||||
return
|
||||
$"{_lobby.GetRow(1741)?.Text.ToString() ?? "Facial Features"} & {_lobby.GetRow(1742)?.Text.ToString() ?? "Tattoos"}";
|
||||
|
||||
var textRow = _lobby.GetRow(menu.Value.Id);
|
||||
return textRow?.Text.ToString() ?? c.ToDefaultName();
|
||||
}).ToArray();
|
||||
nameArray[(int) CustomizationId.EyeColorL] = nameArray[(int) CustomizationId.EyeColorR];
|
||||
nameArray[(int) CustomizationId.EyeColorR] = GetName(CustomName.OddEyes);
|
||||
set.OptionName = nameArray;
|
||||
|
||||
set.Types = ((CustomizationId[]) Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case CustomizationId.HighlightColor:
|
||||
case CustomizationId.EyeColorL:
|
||||
case CustomizationId.EyeColorR:
|
||||
return CharaMakeParams.MenuType.ColorPicker;
|
||||
}
|
||||
|
||||
var menu = row.Menus
|
||||
.Cast<CharaMakeParams.Menu?>()
|
||||
.FirstOrDefault(m => m!.Value.Customization == c);
|
||||
return menu?.Type ?? CharaMakeParams.MenuType.ListSelector;
|
||||
}).ToArray();
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
private readonly ExcelSheet<CharaMakeCustomize> _customizeSheet;
|
||||
private readonly ExcelSheet<CharaMakeParams> _listSheet;
|
||||
private readonly ExcelSheet<HairMakeType> _hairSheet;
|
||||
private readonly ExcelSheet<Lobby> _lobby;
|
||||
private readonly CmpFile _cmpFile;
|
||||
private readonly Customization[] _highlightPicker;
|
||||
private readonly Customization[] _eyeColorPicker;
|
||||
private readonly Customization[] _facePaintColorPickerDark;
|
||||
private readonly Customization[] _facePaintColorPickerLight;
|
||||
private readonly Customization[] _lipColorPickerDark;
|
||||
private readonly Customization[] _lipColorPickerLight;
|
||||
private readonly Customization[] _tattooColorPicker;
|
||||
private readonly string[] _names = new string[(int) CustomName.Num];
|
||||
|
||||
public string GetName(CustomName name)
|
||||
=> _names[(int) name];
|
||||
|
||||
private static Language FromClientLanguage(ClientLanguage language)
|
||||
=> language switch
|
||||
{
|
||||
ClientLanguage.English => Language.English,
|
||||
ClientLanguage.French => Language.French,
|
||||
ClientLanguage.German => Language.German,
|
||||
ClientLanguage.Japanese => Language.Japanese,
|
||||
_ => Language.English,
|
||||
};
|
||||
|
||||
internal CustomizationOptions(DalamudPluginInterface pi, DataManager gameData, ClientLanguage language)
|
||||
{
|
||||
try
|
||||
{
|
||||
_cmpFile = new CmpFile(gameData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception("READ THIS\n======== Could not obtain the human.cmp file which is necessary for color sets.\n"
|
||||
+ "======== This usually indicates an error with your index files caused by TexTools modifications.\n"
|
||||
+ "======== If you have used TexTools before, you will probably need to start over in it to use Glamourer.", e);
|
||||
}
|
||||
|
||||
_customizeSheet = gameData.GetExcelSheet<CharaMakeCustomize>()!;
|
||||
_lobby = gameData.GetExcelSheet<Lobby>()!;
|
||||
var tmp = gameData.Excel.GetType()!.GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||
.MakeGenericMethod(typeof(CharaMakeParams))!.Invoke(gameData.Excel, new object?[]
|
||||
_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(language),
|
||||
null,
|
||||
}) as ExcelSheet<CharaMakeParams>;
|
||||
_listSheet = tmp!;
|
||||
_hairSheet = gameData.GetExcelSheet<HairMakeType>()!;
|
||||
SetNames(gameData);
|
||||
_listSheet = tmp!;
|
||||
_hairSheet = gameData.GetExcelSheet<HairMakeType>()!;
|
||||
SetNames(gameData);
|
||||
|
||||
_highlightPicker = CreateColorPicker(CustomizationId.HighlightColor, 256, 192);
|
||||
_lipColorPickerDark = CreateColorPicker(CustomizationId.LipColor, 512, 96);
|
||||
_lipColorPickerLight = CreateColorPicker(CustomizationId.LipColor, 1024, 96, true);
|
||||
_eyeColorPicker = CreateColorPicker(CustomizationId.EyeColorL, 0, 192);
|
||||
_facePaintColorPickerDark = CreateColorPicker(CustomizationId.FacePaintColor, 640, 96);
|
||||
_facePaintColorPickerLight = CreateColorPicker(CustomizationId.FacePaintColor, 1152, 96, true);
|
||||
_tattooColorPicker = CreateColorPicker(CustomizationId.TattooColor, 0, 192);
|
||||
_highlightPicker = CreateColorPicker(CustomizationId.HighlightColor, 256, 192);
|
||||
_lipColorPickerDark = CreateColorPicker(CustomizationId.LipColor, 512, 96);
|
||||
_lipColorPickerLight = CreateColorPicker(CustomizationId.LipColor, 1024, 96, true);
|
||||
_eyeColorPicker = CreateColorPicker(CustomizationId.EyeColorL, 0, 192);
|
||||
_facePaintColorPickerDark = CreateColorPicker(CustomizationId.FacePaintColor, 640, 96);
|
||||
_facePaintColorPickerLight = CreateColorPicker(CustomizationId.FacePaintColor, 1152, 96, true);
|
||||
_tattooColorPicker = CreateColorPicker(CustomizationId.TattooColor, 0, 192);
|
||||
|
||||
_icons = new IconStorage(pi, gameData, _list.Length * 50);
|
||||
foreach (var race in Clans)
|
||||
{
|
||||
foreach (var gender in Genders)
|
||||
_list[ToIndex(race, gender)] = GetSet(race, gender);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetNames(DataManager gameData)
|
||||
_icons = new IconStorage(pi, gameData, _list.Length * 50);
|
||||
foreach (var race in Clans)
|
||||
{
|
||||
var subRace = gameData.GetExcelSheet<Tribe>()!;
|
||||
_names[(int) CustomName.Clan] = _lobby.GetRow(102)?.Text ?? "Clan";
|
||||
_names[(int) CustomName.Gender] = _lobby.GetRow(103)?.Text ?? "Gender";
|
||||
_names[(int) CustomName.Reverse] = _lobby.GetRow(2135)?.Text ?? "Reverse";
|
||||
_names[(int) CustomName.OddEyes] = _lobby.GetRow(2125)?.Text ?? "Odd Eyes";
|
||||
_names[(int) CustomName.IrisSmall] = _lobby.GetRow(1076)?.Text ?? "Small";
|
||||
_names[(int) CustomName.IrisLarge] = _lobby.GetRow(1075)?.Text ?? "Large";
|
||||
_names[(int) CustomName.IrisSize] = _lobby.GetRow(244)?.Text ?? "Iris Size";
|
||||
_names[(int) CustomName.MidlanderM] = subRace.GetRow((int) SubRace.Midlander)?.Masculine.ToString() ?? SubRace.Midlander.ToName();
|
||||
_names[(int) CustomName.MidlanderF] = subRace.GetRow((int) SubRace.Midlander)?.Feminine.ToString() ?? SubRace.Midlander.ToName();
|
||||
_names[(int) CustomName.HighlanderM] =
|
||||
subRace.GetRow((int) SubRace.Highlander)?.Masculine.ToString() ?? SubRace.Highlander.ToName();
|
||||
_names[(int) CustomName.HighlanderF] = subRace.GetRow((int) SubRace.Highlander)?.Feminine.ToString() ?? SubRace.Highlander.ToName();
|
||||
_names[(int) CustomName.WildwoodM] = subRace.GetRow((int) SubRace.Wildwood)?.Masculine.ToString() ?? SubRace.Wildwood.ToName();
|
||||
_names[(int) CustomName.WildwoodF] = subRace.GetRow((int) SubRace.Wildwood)?.Feminine.ToString() ?? SubRace.Wildwood.ToName();
|
||||
_names[(int) CustomName.DuskwightM] = subRace.GetRow((int) SubRace.Duskwight)?.Masculine.ToString() ?? SubRace.Duskwight.ToName();
|
||||
_names[(int) CustomName.DuskwightF] = subRace.GetRow((int) SubRace.Duskwight)?.Feminine.ToString() ?? SubRace.Duskwight.ToName();
|
||||
_names[(int) CustomName.PlainsfolkM] =
|
||||
subRace.GetRow((int) SubRace.Plainsfolk)?.Masculine.ToString() ?? SubRace.Plainsfolk.ToName();
|
||||
_names[(int) CustomName.PlainsfolkF] = subRace.GetRow((int) SubRace.Plainsfolk)?.Feminine.ToString() ?? SubRace.Plainsfolk.ToName();
|
||||
_names[(int) CustomName.DunesfolkM] = subRace.GetRow((int) SubRace.Dunesfolk)?.Masculine.ToString() ?? SubRace.Dunesfolk.ToName();
|
||||
_names[(int) CustomName.DunesfolkF] = subRace.GetRow((int) SubRace.Dunesfolk)?.Feminine.ToString() ?? SubRace.Dunesfolk.ToName();
|
||||
_names[(int) CustomName.SeekerOfTheSunM] =
|
||||
subRace.GetRow((int) SubRace.SeekerOfTheSun)?.Masculine.ToString() ?? SubRace.SeekerOfTheSun.ToName();
|
||||
_names[(int) CustomName.SeekerOfTheSunF] =
|
||||
subRace.GetRow((int) SubRace.SeekerOfTheSun)?.Feminine.ToString() ?? SubRace.SeekerOfTheSun.ToName();
|
||||
_names[(int) CustomName.KeeperOfTheMoonM] =
|
||||
subRace.GetRow((int) SubRace.KeeperOfTheMoon)?.Masculine.ToString() ?? SubRace.KeeperOfTheMoon.ToName();
|
||||
_names[(int) CustomName.KeeperOfTheMoonF] =
|
||||
subRace.GetRow((int) SubRace.KeeperOfTheMoon)?.Feminine.ToString() ?? SubRace.KeeperOfTheMoon.ToName();
|
||||
_names[(int) CustomName.SeawolfM] = subRace.GetRow((int) SubRace.Seawolf)?.Masculine.ToString() ?? SubRace.Seawolf.ToName();
|
||||
_names[(int) CustomName.SeawolfF] = subRace.GetRow((int) SubRace.Seawolf)?.Feminine.ToString() ?? SubRace.Seawolf.ToName();
|
||||
_names[(int) CustomName.HellsguardM] =
|
||||
subRace.GetRow((int) SubRace.Hellsguard)?.Masculine.ToString() ?? SubRace.Hellsguard.ToName();
|
||||
_names[(int) CustomName.HellsguardF] = subRace.GetRow((int) SubRace.Hellsguard)?.Feminine.ToString() ?? SubRace.Hellsguard.ToName();
|
||||
_names[(int) CustomName.RaenM] = subRace.GetRow((int) SubRace.Raen)?.Masculine.ToString() ?? SubRace.Raen.ToName();
|
||||
_names[(int) CustomName.RaenF] = subRace.GetRow((int) SubRace.Raen)?.Feminine.ToString() ?? SubRace.Raen.ToName();
|
||||
_names[(int) CustomName.XaelaM] = subRace.GetRow((int) SubRace.Xaela)?.Masculine.ToString() ?? SubRace.Xaela.ToName();
|
||||
_names[(int) CustomName.XaelaF] = subRace.GetRow((int) SubRace.Xaela)?.Feminine.ToString() ?? SubRace.Xaela.ToName();
|
||||
_names[(int) CustomName.HelionM] = subRace.GetRow((int) SubRace.Helion)?.Masculine.ToString() ?? SubRace.Helion.ToName();
|
||||
_names[(int) CustomName.HelionF] = subRace.GetRow((int) SubRace.Helion)?.Feminine.ToString() ?? SubRace.Helion.ToName();
|
||||
_names[(int) CustomName.LostM] = subRace.GetRow((int) SubRace.Lost)?.Masculine.ToString() ?? SubRace.Lost.ToName();
|
||||
_names[(int) CustomName.LostF] = subRace.GetRow((int) SubRace.Lost)?.Feminine.ToString() ?? SubRace.Lost.ToName();
|
||||
_names[(int) CustomName.RavaM] = subRace.GetRow((int) SubRace.Rava)?.Masculine.ToString() ?? SubRace.Rava.ToName();
|
||||
_names[(int) CustomName.RavaF] = subRace.GetRow((int) SubRace.Rava)?.Feminine.ToString() ?? SubRace.Rava.ToName();
|
||||
_names[(int) CustomName.VeenaM] = subRace.GetRow((int) SubRace.Veena)?.Masculine.ToString() ?? SubRace.Veena.ToName();
|
||||
_names[(int) CustomName.VeenaF] = subRace.GetRow((int) SubRace.Veena)?.Feminine.ToString() ?? SubRace.Veena.ToName();
|
||||
foreach (var gender in Genders)
|
||||
_list[ToIndex(race, gender)] = GetSet(race, gender);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetNames(DataManager gameData)
|
||||
{
|
||||
var subRace = gameData.GetExcelSheet<Tribe>()!;
|
||||
_names[(int)CustomName.Clan] = _lobby.GetRow(102)?.Text ?? "Clan";
|
||||
_names[(int)CustomName.Gender] = _lobby.GetRow(103)?.Text ?? "Gender";
|
||||
_names[(int)CustomName.Reverse] = _lobby.GetRow(2135)?.Text ?? "Reverse";
|
||||
_names[(int)CustomName.OddEyes] = _lobby.GetRow(2125)?.Text ?? "Odd Eyes";
|
||||
_names[(int)CustomName.IrisSmall] = _lobby.GetRow(1076)?.Text ?? "Small";
|
||||
_names[(int)CustomName.IrisLarge] = _lobby.GetRow(1075)?.Text ?? "Large";
|
||||
_names[(int)CustomName.IrisSize] = _lobby.GetRow(244)?.Text ?? "Iris Size";
|
||||
_names[(int)CustomName.MidlanderM] = subRace.GetRow((int)SubRace.Midlander)?.Masculine.ToString() ?? SubRace.Midlander.ToName();
|
||||
_names[(int)CustomName.MidlanderF] = subRace.GetRow((int)SubRace.Midlander)?.Feminine.ToString() ?? SubRace.Midlander.ToName();
|
||||
_names[(int)CustomName.HighlanderM] =
|
||||
subRace.GetRow((int)SubRace.Highlander)?.Masculine.ToString() ?? SubRace.Highlander.ToName();
|
||||
_names[(int)CustomName.HighlanderF] = subRace.GetRow((int)SubRace.Highlander)?.Feminine.ToString() ?? SubRace.Highlander.ToName();
|
||||
_names[(int)CustomName.WildwoodM] = subRace.GetRow((int)SubRace.Wildwood)?.Masculine.ToString() ?? SubRace.Wildwood.ToName();
|
||||
_names[(int)CustomName.WildwoodF] = subRace.GetRow((int)SubRace.Wildwood)?.Feminine.ToString() ?? SubRace.Wildwood.ToName();
|
||||
_names[(int)CustomName.DuskwightM] = subRace.GetRow((int)SubRace.Duskwight)?.Masculine.ToString() ?? SubRace.Duskwight.ToName();
|
||||
_names[(int)CustomName.DuskwightF] = subRace.GetRow((int)SubRace.Duskwight)?.Feminine.ToString() ?? SubRace.Duskwight.ToName();
|
||||
_names[(int)CustomName.PlainsfolkM] =
|
||||
subRace.GetRow((int)SubRace.Plainsfolk)?.Masculine.ToString() ?? SubRace.Plainsfolk.ToName();
|
||||
_names[(int)CustomName.PlainsfolkF] = subRace.GetRow((int)SubRace.Plainsfolk)?.Feminine.ToString() ?? SubRace.Plainsfolk.ToName();
|
||||
_names[(int)CustomName.DunesfolkM] = subRace.GetRow((int)SubRace.Dunesfolk)?.Masculine.ToString() ?? SubRace.Dunesfolk.ToName();
|
||||
_names[(int)CustomName.DunesfolkF] = subRace.GetRow((int)SubRace.Dunesfolk)?.Feminine.ToString() ?? SubRace.Dunesfolk.ToName();
|
||||
_names[(int)CustomName.SeekerOfTheSunM] =
|
||||
subRace.GetRow((int)SubRace.SeekerOfTheSun)?.Masculine.ToString() ?? SubRace.SeekerOfTheSun.ToName();
|
||||
_names[(int)CustomName.SeekerOfTheSunF] =
|
||||
subRace.GetRow((int)SubRace.SeekerOfTheSun)?.Feminine.ToString() ?? SubRace.SeekerOfTheSun.ToName();
|
||||
_names[(int)CustomName.KeeperOfTheMoonM] =
|
||||
subRace.GetRow((int)SubRace.KeeperOfTheMoon)?.Masculine.ToString() ?? SubRace.KeeperOfTheMoon.ToName();
|
||||
_names[(int)CustomName.KeeperOfTheMoonF] =
|
||||
subRace.GetRow((int)SubRace.KeeperOfTheMoon)?.Feminine.ToString() ?? SubRace.KeeperOfTheMoon.ToName();
|
||||
_names[(int)CustomName.SeawolfM] = subRace.GetRow((int)SubRace.Seawolf)?.Masculine.ToString() ?? SubRace.Seawolf.ToName();
|
||||
_names[(int)CustomName.SeawolfF] = subRace.GetRow((int)SubRace.Seawolf)?.Feminine.ToString() ?? SubRace.Seawolf.ToName();
|
||||
_names[(int)CustomName.HellsguardM] =
|
||||
subRace.GetRow((int)SubRace.Hellsguard)?.Masculine.ToString() ?? SubRace.Hellsguard.ToName();
|
||||
_names[(int)CustomName.HellsguardF] = subRace.GetRow((int)SubRace.Hellsguard)?.Feminine.ToString() ?? SubRace.Hellsguard.ToName();
|
||||
_names[(int)CustomName.RaenM] = subRace.GetRow((int)SubRace.Raen)?.Masculine.ToString() ?? SubRace.Raen.ToName();
|
||||
_names[(int)CustomName.RaenF] = subRace.GetRow((int)SubRace.Raen)?.Feminine.ToString() ?? SubRace.Raen.ToName();
|
||||
_names[(int)CustomName.XaelaM] = subRace.GetRow((int)SubRace.Xaela)?.Masculine.ToString() ?? SubRace.Xaela.ToName();
|
||||
_names[(int)CustomName.XaelaF] = subRace.GetRow((int)SubRace.Xaela)?.Feminine.ToString() ?? SubRace.Xaela.ToName();
|
||||
_names[(int)CustomName.HelionM] = subRace.GetRow((int)SubRace.Helion)?.Masculine.ToString() ?? SubRace.Helion.ToName();
|
||||
_names[(int)CustomName.HelionF] = subRace.GetRow((int)SubRace.Helion)?.Feminine.ToString() ?? SubRace.Helion.ToName();
|
||||
_names[(int)CustomName.LostM] = subRace.GetRow((int)SubRace.Lost)?.Masculine.ToString() ?? SubRace.Lost.ToName();
|
||||
_names[(int)CustomName.LostF] = subRace.GetRow((int)SubRace.Lost)?.Feminine.ToString() ?? SubRace.Lost.ToName();
|
||||
_names[(int)CustomName.RavaM] = subRace.GetRow((int)SubRace.Rava)?.Masculine.ToString() ?? SubRace.Rava.ToName();
|
||||
_names[(int)CustomName.RavaF] = subRace.GetRow((int)SubRace.Rava)?.Feminine.ToString() ?? SubRace.Rava.ToName();
|
||||
_names[(int)CustomName.VeenaM] = subRace.GetRow((int)SubRace.Veena)?.Masculine.ToString() ?? SubRace.Veena.ToName();
|
||||
_names[(int)CustomName.VeenaF] = subRace.GetRow((int)SubRace.Veena)?.Feminine.ToString() ?? SubRace.Veena.ToName();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,142 +3,180 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Glamourer.Structs;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Item = Glamourer.Structs.Item;
|
||||
using Stain = Glamourer.Structs.Stain;
|
||||
|
||||
namespace Glamourer
|
||||
namespace Glamourer;
|
||||
|
||||
public static class GameData
|
||||
{
|
||||
public static class GameData
|
||||
private static Dictionary<byte, Stain>? _stains;
|
||||
private static Dictionary<EquipSlot, List<Item>>? _itemsBySlot;
|
||||
private static Dictionary<byte, Job>? _jobs;
|
||||
private static Dictionary<ushort, JobGroup>? _jobGroups;
|
||||
private static SortedList<uint, ModelChara>? _models;
|
||||
|
||||
public static IReadOnlyDictionary<uint, ModelChara> Models(DataManager dataManager)
|
||||
{
|
||||
private static Dictionary<byte, Stain>? _stains;
|
||||
private static Dictionary<EquipSlot, List<Item>>? _itemsBySlot;
|
||||
private static Dictionary<byte, Job>? _jobs;
|
||||
private static Dictionary<ushort, JobGroup>? _jobGroups;
|
||||
private static SortedList<uint, ModelChara>? _models;
|
||||
|
||||
public static IReadOnlyDictionary<uint, ModelChara> Models(DataManager dataManager)
|
||||
{
|
||||
if (_models != null)
|
||||
return _models;
|
||||
|
||||
var sheet = dataManager.GetExcelSheet<ModelChara>()!;
|
||||
|
||||
_models = new SortedList<uint, ModelChara>((int) sheet.RowCount);
|
||||
foreach (var model in sheet.Where(m => m.Type != 0))
|
||||
_models.Add(model.RowId, model);
|
||||
if (_models != null)
|
||||
return _models;
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<byte, Stain> Stains(DataManager dataManager)
|
||||
{
|
||||
if (_stains != null)
|
||||
return _stains;
|
||||
var sheet = dataManager.GetExcelSheet<ModelChara>()!;
|
||||
|
||||
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));
|
||||
_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;
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<EquipSlot, List<Item>> ItemsBySlot(DataManager dataManager)
|
||||
{
|
||||
if (_itemsBySlot != null)
|
||||
return _itemsBySlot;
|
||||
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;
|
||||
}
|
||||
|
||||
var sheet = dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets.Item>()!;
|
||||
|
||||
Item EmptySlot(EquipSlot slot)
|
||||
=> new(sheet.First(), "Nothing", slot);
|
||||
|
||||
static Item EmptyNpc(EquipSlot slot)
|
||||
=> new(new Lumina.Excel.GeneratedSheets.Item() { ModelMain = 9903 }, "Smallclothes (NPC)", slot);
|
||||
|
||||
_itemsBySlot = new Dictionary<EquipSlot, List<Item>>()
|
||||
{
|
||||
[EquipSlot.Head] = new(200) { EmptySlot(EquipSlot.Head), EmptyNpc(EquipSlot.Head) },
|
||||
[EquipSlot.Body] = new(200) { EmptySlot(EquipSlot.Body), EmptyNpc(EquipSlot.Body) },
|
||||
[EquipSlot.Hands] = new(200) { EmptySlot(EquipSlot.Hands), EmptyNpc(EquipSlot.Hands) },
|
||||
[EquipSlot.Legs] = new(200) { EmptySlot(EquipSlot.Legs), EmptyNpc(EquipSlot.Legs) },
|
||||
[EquipSlot.Feet] = new(200) { EmptySlot(EquipSlot.Feet), EmptyNpc(EquipSlot.Feet) },
|
||||
[EquipSlot.RFinger] = new(200) { EmptySlot(EquipSlot.RFinger), EmptyNpc(EquipSlot.RFinger) },
|
||||
[EquipSlot.Neck] = new(200) { EmptySlot(EquipSlot.Neck), EmptyNpc(EquipSlot.Neck) },
|
||||
[EquipSlot.MainHand] = new(1000) { EmptySlot(EquipSlot.MainHand) },
|
||||
[EquipSlot.OffHand] = new(200) { EmptySlot(EquipSlot.OffHand) },
|
||||
[EquipSlot.Wrists] = new(200) { EmptySlot(EquipSlot.Wrists), EmptyNpc(EquipSlot.Wrists) },
|
||||
[EquipSlot.Ears] = new(200) { EmptySlot(EquipSlot.Ears), EmptyNpc(EquipSlot.Ears) },
|
||||
};
|
||||
|
||||
foreach (var item in sheet)
|
||||
{
|
||||
var name = item.Name.ToString();
|
||||
if (!name.Any())
|
||||
continue;
|
||||
|
||||
var slot = (EquipSlot) item.EquipSlotCategory.Row;
|
||||
if (slot == EquipSlot.Unknown)
|
||||
continue;
|
||||
|
||||
slot = slot.ToSlot();
|
||||
if (!_itemsBySlot.TryGetValue(slot, out var list))
|
||||
continue;
|
||||
|
||||
list.Add(new Item(item, name, slot));
|
||||
}
|
||||
|
||||
foreach (var list in _itemsBySlot.Values)
|
||||
list.Sort((i1, i2) => string.Compare(i1.Name, i2.Name, StringComparison.InvariantCulture));
|
||||
|
||||
_itemsBySlot[EquipSlot.LFinger] = _itemsBySlot[EquipSlot.RFinger];
|
||||
public static IReadOnlyDictionary<EquipSlot, List<Item>> ItemsBySlot(DataManager dataManager)
|
||||
{
|
||||
if (_itemsBySlot != null)
|
||||
return _itemsBySlot;
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<byte, Job> Jobs(DataManager dataManager)
|
||||
var sheet = dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets.Item>()!;
|
||||
|
||||
Item EmptySlot(EquipSlot slot)
|
||||
=> new(sheet.First(), "Nothing", slot);
|
||||
|
||||
static Item EmptyNpc(EquipSlot slot)
|
||||
=> new(new Lumina.Excel.GeneratedSheets.Item() { ModelMain = 9903 }, "Smallclothes (NPC)", slot);
|
||||
|
||||
_itemsBySlot = new Dictionary<EquipSlot, List<Item>>()
|
||||
{
|
||||
if (_jobs != null)
|
||||
return _jobs;
|
||||
|
||||
var sheet = dataManager.GetExcelSheet<ClassJob>()!;
|
||||
_jobs = sheet.ToDictionary(j => (byte)j.RowId, j => new Job(j));
|
||||
return _jobs;
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<ushort, JobGroup> JobGroups(DataManager dataManager)
|
||||
{
|
||||
if (_jobGroups != null)
|
||||
return _jobGroups;
|
||||
|
||||
var sheet = dataManager.GetExcelSheet<ClassJobCategory>()!;
|
||||
var jobs = dataManager.GetExcelSheet<ClassJob>(ClientLanguage.English)!;
|
||||
|
||||
static bool ValidIndex(uint idx)
|
||||
[EquipSlot.Head] = new(200)
|
||||
{
|
||||
if (idx > 0 && idx < 36)
|
||||
return true;
|
||||
EmptySlot(EquipSlot.Head),
|
||||
EmptyNpc(EquipSlot.Head),
|
||||
},
|
||||
[EquipSlot.Body] = new(200)
|
||||
{
|
||||
EmptySlot(EquipSlot.Body),
|
||||
EmptyNpc(EquipSlot.Body),
|
||||
},
|
||||
[EquipSlot.Hands] = new(200)
|
||||
{
|
||||
EmptySlot(EquipSlot.Hands),
|
||||
EmptyNpc(EquipSlot.Hands),
|
||||
},
|
||||
[EquipSlot.Legs] = new(200)
|
||||
{
|
||||
EmptySlot(EquipSlot.Legs),
|
||||
EmptyNpc(EquipSlot.Legs),
|
||||
},
|
||||
[EquipSlot.Feet] = new(200)
|
||||
{
|
||||
EmptySlot(EquipSlot.Feet),
|
||||
EmptyNpc(EquipSlot.Feet),
|
||||
},
|
||||
[EquipSlot.RFinger] = new(200)
|
||||
{
|
||||
EmptySlot(EquipSlot.RFinger),
|
||||
EmptyNpc(EquipSlot.RFinger),
|
||||
},
|
||||
[EquipSlot.Neck] = new(200)
|
||||
{
|
||||
EmptySlot(EquipSlot.Neck),
|
||||
EmptyNpc(EquipSlot.Neck),
|
||||
},
|
||||
[EquipSlot.MainHand] = new(1000) { EmptySlot(EquipSlot.MainHand) },
|
||||
[EquipSlot.OffHand] = new(200) { EmptySlot(EquipSlot.OffHand) },
|
||||
[EquipSlot.Wrists] = new(200)
|
||||
{
|
||||
EmptySlot(EquipSlot.Wrists),
|
||||
EmptyNpc(EquipSlot.Wrists),
|
||||
},
|
||||
[EquipSlot.Ears] = new(200)
|
||||
{
|
||||
EmptySlot(EquipSlot.Ears),
|
||||
EmptyNpc(EquipSlot.Ears),
|
||||
},
|
||||
};
|
||||
|
||||
return idx switch
|
||||
{
|
||||
91 => true,
|
||||
92 => true,
|
||||
96 => true,
|
||||
98 => true,
|
||||
99 => true,
|
||||
111 => true,
|
||||
112 => true,
|
||||
129 => true,
|
||||
149 => true,
|
||||
150 => true,
|
||||
156 => true,
|
||||
157 => true,
|
||||
158 => true,
|
||||
159 => true,
|
||||
180 => true,
|
||||
181 => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
foreach (var item in sheet)
|
||||
{
|
||||
var name = item.Name.ToString();
|
||||
if (!name.Any())
|
||||
continue;
|
||||
|
||||
_jobGroups = sheet.Where(j => ValidIndex(j.RowId))
|
||||
.ToDictionary(j => (ushort) j.RowId, j => new JobGroup(j, jobs));
|
||||
return _jobGroups;
|
||||
var slot = (EquipSlot)item.EquipSlotCategory.Row;
|
||||
if (slot == EquipSlot.Unknown)
|
||||
continue;
|
||||
|
||||
slot = slot.ToSlot();
|
||||
if (!_itemsBySlot.TryGetValue(slot, out var list))
|
||||
continue;
|
||||
|
||||
list.Add(new Item(item, name, slot));
|
||||
}
|
||||
|
||||
foreach (var list in _itemsBySlot.Values)
|
||||
list.Sort((i1, i2) => string.Compare(i1.Name, i2.Name, StringComparison.InvariantCulture));
|
||||
|
||||
_itemsBySlot[EquipSlot.LFinger] = _itemsBySlot[EquipSlot.RFinger];
|
||||
return _itemsBySlot;
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<byte, Job> Jobs(DataManager dataManager)
|
||||
{
|
||||
if (_jobs != null)
|
||||
return _jobs;
|
||||
|
||||
var sheet = dataManager.GetExcelSheet<ClassJob>()!;
|
||||
_jobs = sheet.ToDictionary(j => (byte)j.RowId, j => new Job(j));
|
||||
return _jobs;
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<ushort, JobGroup> JobGroups(DataManager dataManager)
|
||||
{
|
||||
if (_jobGroups != null)
|
||||
return _jobGroups;
|
||||
|
||||
var sheet = dataManager.GetExcelSheet<ClassJobCategory>()!;
|
||||
var jobs = dataManager.GetExcelSheet<ClassJob>(ClientLanguage.English)!;
|
||||
|
||||
static bool ValidIndex(uint idx)
|
||||
{
|
||||
if (idx is > 0 and < 36)
|
||||
return true;
|
||||
|
||||
return idx switch
|
||||
{
|
||||
91 => true,
|
||||
92 => true,
|
||||
96 => true,
|
||||
98 => true,
|
||||
99 => true,
|
||||
111 => true,
|
||||
112 => true,
|
||||
129 => true,
|
||||
149 => true,
|
||||
150 => true,
|
||||
156 => true,
|
||||
157 => true,
|
||||
158 => true,
|
||||
159 => true,
|
||||
180 => true,
|
||||
181 => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
_jobGroups = sheet.Where(j => ValidIndex(j.RowId))
|
||||
.ToDictionary(j => (ushort)j.RowId, j => new JobGroup(j, jobs));
|
||||
return _jobGroups;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Penumbra\OtterGui\OtterGui.csproj" />
|
||||
<ProjectReference Include="..\..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -1,55 +0,0 @@
|
|||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer
|
||||
{
|
||||
public readonly struct Item
|
||||
{
|
||||
private static (SetId id, WeaponType type, ushort variant) ParseModel(EquipSlot slot, ulong data)
|
||||
{
|
||||
if (slot == EquipSlot.MainHand || slot == EquipSlot.OffHand)
|
||||
{
|
||||
var id = (SetId) (data & 0xFFFF);
|
||||
var type = (WeaponType) ((data >> 16) & 0xFFFF);
|
||||
var variant = (ushort) ((data >> 32) & 0xFFFF);
|
||||
return (id, type, variant);
|
||||
}
|
||||
else
|
||||
{
|
||||
var id = (SetId) (data & 0xFFFF);
|
||||
var variant = (byte) ((data >> 16) & 0xFF);
|
||||
return (id, new WeaponType(), variant);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly Lumina.Excel.GeneratedSheets.Item Base;
|
||||
public readonly string Name;
|
||||
public readonly EquipSlot EquippableTo;
|
||||
|
||||
public (SetId id, WeaponType type, ushort variant) MainModel
|
||||
=> ParseModel(EquippableTo, Base.ModelMain);
|
||||
|
||||
public bool HasSubModel
|
||||
=> Base.ModelSub != 0;
|
||||
|
||||
public (SetId id, WeaponType type, ushort variant) SubModel
|
||||
=> ParseModel(EquippableTo, Base.ModelSub);
|
||||
|
||||
public Item(Lumina.Excel.GeneratedSheets.Item item, string name, EquipSlot slot = EquipSlot.Unknown)
|
||||
{
|
||||
Base = item;
|
||||
Name = name;
|
||||
EquippableTo = slot == EquipSlot.Unknown ? ((EquipSlot) item.EquipSlotCategory.Row).ToSlot() : slot;
|
||||
}
|
||||
|
||||
public static Item Nothing(EquipSlot slot)
|
||||
=> new("Nothing", slot);
|
||||
|
||||
private Item(string name, EquipSlot slot)
|
||||
{
|
||||
Name = name;
|
||||
Base = new Lumina.Excel.GeneratedSheets.Item();
|
||||
EquippableTo = slot;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Glamourer
|
||||
{
|
||||
public readonly struct Job
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly string Abbreviation;
|
||||
public readonly ClassJob Base;
|
||||
|
||||
public uint Id
|
||||
=> Base.RowId;
|
||||
|
||||
public Job(ClassJob job)
|
||||
{
|
||||
Base = job;
|
||||
Name = job.Name.ToString();
|
||||
Abbreviation = job.Abbreviation.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Glamourer
|
||||
{
|
||||
public readonly struct JobGroup
|
||||
{
|
||||
public readonly string Name;
|
||||
private readonly ulong _flags;
|
||||
public readonly int Count;
|
||||
public readonly uint Id;
|
||||
|
||||
public JobGroup(ClassJobCategory group, ExcelSheet<ClassJob> jobs)
|
||||
{
|
||||
Count = 0;
|
||||
_flags = 0ul;
|
||||
Id = group.RowId;
|
||||
Name = group.Name.ToString();
|
||||
|
||||
Debug.Assert(jobs.RowCount < 64);
|
||||
foreach (var job in jobs)
|
||||
{
|
||||
var abbr = job.Abbreviation.ToString();
|
||||
if (!abbr.Any())
|
||||
continue;
|
||||
|
||||
var prop = group.GetType().GetProperty(abbr);
|
||||
Debug.Assert(prop != null);
|
||||
|
||||
if (!(bool) prop.GetValue(group)!)
|
||||
continue;
|
||||
|
||||
++Count;
|
||||
_flags |= 1ul << (int) job.RowId;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Fits(Job job)
|
||||
=> Fits(job.Id);
|
||||
|
||||
public bool Fits(uint jobId)
|
||||
{
|
||||
var flag = 1ul << (int)jobId;
|
||||
return (flag & _flags) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,50 +0,0 @@
|
|||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer
|
||||
{
|
||||
public readonly struct Stain
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly uint RgbaColor;
|
||||
|
||||
private readonly uint _seColorId;
|
||||
|
||||
public byte R
|
||||
=> (byte) (RgbaColor & 0xFF);
|
||||
|
||||
public byte G
|
||||
=> (byte) ((RgbaColor >> 8) & 0xFF);
|
||||
|
||||
public byte B
|
||||
=> (byte) ((RgbaColor >> 16) & 0xFF);
|
||||
|
||||
public byte Intensity
|
||||
=> (byte) ((1 + R + G + B) / 3);
|
||||
|
||||
public uint SeColor
|
||||
=> _seColorId & 0x00FFFFFF;
|
||||
|
||||
public StainId RowIndex
|
||||
=> (StainId) (_seColorId >> 24);
|
||||
|
||||
|
||||
public static uint SeColorToRgba(uint color)
|
||||
=> ((color & 0xFF) << 16) | ((color >> 16) & 0xFF) | (color & 0xFF00) | 0xFF000000;
|
||||
|
||||
public Stain(byte index, Lumina.Excel.GeneratedSheets.Stain stain)
|
||||
{
|
||||
Name = stain.Name.ToString();
|
||||
_seColorId = stain.Color | ((uint) index << 24);
|
||||
RgbaColor = SeColorToRgba(stain.Color);
|
||||
}
|
||||
|
||||
public static readonly Stain None = new("None");
|
||||
|
||||
private Stain(string name)
|
||||
{
|
||||
Name = name;
|
||||
_seColorId = 0;
|
||||
RgbaColor = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
43
Glamourer.GameData/Structs/CharacterEquipMask.cs
Normal file
43
Glamourer.GameData/Structs/CharacterEquipMask.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
using System;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Structs;
|
||||
|
||||
[Flags]
|
||||
public enum CharacterEquipMask : ushort
|
||||
{
|
||||
None = 0,
|
||||
MainHand = 0b000000000001,
|
||||
OffHand = 0b000000000010,
|
||||
Head = 0b000000000100,
|
||||
Body = 0b000000001000,
|
||||
Hands = 0b000000010000,
|
||||
Legs = 0b000000100000,
|
||||
Feet = 0b000001000000,
|
||||
Ears = 0b000010000000,
|
||||
Neck = 0b000100000000,
|
||||
Wrists = 0b001000000000,
|
||||
RFinger = 0b010000000000,
|
||||
LFinger = 0b100000000000,
|
||||
All = 0b111111111111,
|
||||
}
|
||||
|
||||
public static class CharacterEquipMaskExtensions
|
||||
{
|
||||
public static bool Fits(this CharacterEquipMask mask, EquipSlot slot)
|
||||
=> slot switch
|
||||
{
|
||||
EquipSlot.Unknown => false,
|
||||
EquipSlot.Head => mask.HasFlag(CharacterEquipMask.Head),
|
||||
EquipSlot.Body => mask.HasFlag(CharacterEquipMask.Body),
|
||||
EquipSlot.Hands => mask.HasFlag(CharacterEquipMask.Hands),
|
||||
EquipSlot.Legs => mask.HasFlag(CharacterEquipMask.Legs),
|
||||
EquipSlot.Feet => mask.HasFlag(CharacterEquipMask.Feet),
|
||||
EquipSlot.Ears => mask.HasFlag(CharacterEquipMask.Ears),
|
||||
EquipSlot.Neck => mask.HasFlag(CharacterEquipMask.Neck),
|
||||
EquipSlot.Wrists => mask.HasFlag(CharacterEquipMask.Wrists),
|
||||
EquipSlot.RFinger => mask.HasFlag(CharacterEquipMask.RFinger),
|
||||
EquipSlot.LFinger => mask.HasFlag(CharacterEquipMask.LFinger),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
54
Glamourer.GameData/Structs/Item.cs
Normal file
54
Glamourer.GameData/Structs/Item.cs
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Structs;
|
||||
|
||||
public readonly struct Item
|
||||
{
|
||||
private static (SetId id, WeaponType type, ushort variant) ParseModel(EquipSlot slot, ulong data)
|
||||
{
|
||||
if (slot is EquipSlot.MainHand or EquipSlot.OffHand)
|
||||
{
|
||||
var id = (SetId)(data & 0xFFFF);
|
||||
var type = (WeaponType)((data >> 16) & 0xFFFF);
|
||||
var variant = (ushort)((data >> 32) & 0xFFFF);
|
||||
return (id, type, variant);
|
||||
}
|
||||
else
|
||||
{
|
||||
var id = (SetId)(data & 0xFFFF);
|
||||
var variant = (byte)((data >> 16) & 0xFF);
|
||||
return (id, new WeaponType(), variant);
|
||||
}
|
||||
}
|
||||
|
||||
public readonly Lumina.Excel.GeneratedSheets.Item Base;
|
||||
public readonly string Name;
|
||||
public readonly EquipSlot EquippableTo;
|
||||
|
||||
public (SetId id, WeaponType type, ushort variant) MainModel
|
||||
=> ParseModel(EquippableTo, Base.ModelMain);
|
||||
|
||||
public bool HasSubModel
|
||||
=> Base.ModelSub != 0;
|
||||
|
||||
public (SetId id, WeaponType type, ushort variant) SubModel
|
||||
=> ParseModel(EquippableTo, Base.ModelSub);
|
||||
|
||||
public Item(Lumina.Excel.GeneratedSheets.Item item, string name, EquipSlot slot = EquipSlot.Unknown)
|
||||
{
|
||||
Base = item;
|
||||
Name = name;
|
||||
EquippableTo = slot == EquipSlot.Unknown ? ((EquipSlot)item.EquipSlotCategory.Row).ToSlot() : slot;
|
||||
}
|
||||
|
||||
public static Item Nothing(EquipSlot slot)
|
||||
=> new("Nothing", slot);
|
||||
|
||||
private Item(string name, EquipSlot slot)
|
||||
{
|
||||
Name = name;
|
||||
Base = new Lumina.Excel.GeneratedSheets.Item();
|
||||
EquippableTo = slot;
|
||||
}
|
||||
}
|
||||
20
Glamourer.GameData/Structs/Job.cs
Normal file
20
Glamourer.GameData/Structs/Job.cs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Glamourer.Structs;
|
||||
|
||||
public readonly struct Job
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly string Abbreviation;
|
||||
public readonly ClassJob Base;
|
||||
|
||||
public uint Id
|
||||
=> Base.RowId;
|
||||
|
||||
public Job(ClassJob job)
|
||||
{
|
||||
Base = job;
|
||||
Name = job.Name.ToString();
|
||||
Abbreviation = job.Abbreviation.ToString();
|
||||
}
|
||||
}
|
||||
48
Glamourer.GameData/Structs/JobGroup.cs
Normal file
48
Glamourer.GameData/Structs/JobGroup.cs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Glamourer.Structs;
|
||||
|
||||
public readonly struct JobGroup
|
||||
{
|
||||
public readonly string Name;
|
||||
private readonly ulong _flags;
|
||||
public readonly int Count;
|
||||
public readonly uint Id;
|
||||
|
||||
public JobGroup(ClassJobCategory group, ExcelSheet<ClassJob> jobs)
|
||||
{
|
||||
Count = 0;
|
||||
_flags = 0ul;
|
||||
Id = group.RowId;
|
||||
Name = group.Name.ToString();
|
||||
|
||||
Debug.Assert(jobs.RowCount < 64);
|
||||
foreach (var job in jobs)
|
||||
{
|
||||
var abbr = job.Abbreviation.ToString();
|
||||
if (!abbr.Any())
|
||||
continue;
|
||||
|
||||
var prop = group.GetType().GetProperty(abbr);
|
||||
Debug.Assert(prop != null);
|
||||
|
||||
if (!(bool)prop.GetValue(group)!)
|
||||
continue;
|
||||
|
||||
++Count;
|
||||
_flags |= 1ul << (int)job.RowId;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Fits(Job job)
|
||||
=> Fits(job.Id);
|
||||
|
||||
public bool Fits(uint jobId)
|
||||
{
|
||||
var flag = 1ul << (int)jobId;
|
||||
return (flag & _flags) != 0;
|
||||
}
|
||||
}
|
||||
49
Glamourer.GameData/Structs/Stain.cs
Normal file
49
Glamourer.GameData/Structs/Stain.cs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Structs;
|
||||
|
||||
public readonly struct Stain
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly uint RgbaColor;
|
||||
|
||||
private readonly uint _seColorId;
|
||||
|
||||
public byte R
|
||||
=> (byte)(RgbaColor & 0xFF);
|
||||
|
||||
public byte G
|
||||
=> (byte)(RgbaColor >> 8 & 0xFF);
|
||||
|
||||
public byte B
|
||||
=> (byte)(RgbaColor >> 16 & 0xFF);
|
||||
|
||||
public byte Intensity
|
||||
=> (byte)((1 + R + G + B) / 3);
|
||||
|
||||
public uint SeColor
|
||||
=> _seColorId & 0x00FFFFFF;
|
||||
|
||||
public StainId RowIndex
|
||||
=> (StainId)(_seColorId >> 24);
|
||||
|
||||
|
||||
public static uint SeColorToRgba(uint color)
|
||||
=> (color & 0xFF) << 16 | color >> 16 & 0xFF | color & 0xFF00 | 0xFF000000;
|
||||
|
||||
public Stain(byte index, Lumina.Excel.GeneratedSheets.Stain stain)
|
||||
{
|
||||
Name = stain.Name.ToString();
|
||||
_seColorId = stain.Color | (uint)index << 24;
|
||||
RgbaColor = SeColorToRgba(stain.Color);
|
||||
}
|
||||
|
||||
public static readonly Stain None = new("None");
|
||||
|
||||
private Stain(string name)
|
||||
{
|
||||
Name = name;
|
||||
_seColorId = 0;
|
||||
RgbaColor = 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Utility;
|
||||
using ImGuiScene;
|
||||
using Lumina.Data.Files;
|
||||
|
||||
namespace Glamourer.Util
|
||||
{
|
||||
public class IconStorage : IDisposable
|
||||
{
|
||||
private readonly DalamudPluginInterface _pi;
|
||||
private readonly DataManager _gameData;
|
||||
private readonly Dictionary<uint, TextureWrap> _icons;
|
||||
|
||||
public IconStorage(DalamudPluginInterface pi, DataManager gameData, int size = 0)
|
||||
{
|
||||
_pi = pi;
|
||||
_gameData = gameData;
|
||||
_icons = new Dictionary<uint, TextureWrap>(size);
|
||||
}
|
||||
|
||||
public TextureWrap this[int id]
|
||||
=> LoadIcon(id);
|
||||
|
||||
private TexFile? LoadIconHq(uint id)
|
||||
{
|
||||
var path = $"ui/icon/{id / 1000 * 1000:000000}/{id:000000}_hr1.tex";
|
||||
return _gameData.GetFile<TexFile>(path);
|
||||
}
|
||||
|
||||
public TextureWrap LoadIcon(int id)
|
||||
=> LoadIcon((uint) id);
|
||||
|
||||
public TextureWrap LoadIcon(uint id)
|
||||
{
|
||||
if (_icons.TryGetValue(id, out var ret))
|
||||
return ret;
|
||||
|
||||
var icon = LoadIconHq(id) ?? _gameData.GetIcon(id)!;
|
||||
var iconData = icon.GetRgbaImageData();
|
||||
|
||||
ret = _pi.UiBuilder.LoadImageRaw(iconData, icon.Header.Width, icon.Header.Height, 4);
|
||||
_icons[id] = ret;
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var icon in _icons.Values)
|
||||
icon.Dispose();
|
||||
_icons.Clear();
|
||||
}
|
||||
|
||||
~IconStorage()
|
||||
=> Dispose();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue