mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 10:17:23 +01:00
Initial Commit
This commit is contained in:
commit
164f304cf6
38 changed files with 2796 additions and 0 deletions
168
Glamourer.GameData/ActorEquipExtensions.cs
Normal file
168
Glamourer.GameData/ActorEquipExtensions.cs
Normal file
|
|
@ -0,0 +1,168 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer
|
||||
{
|
||||
public static class WriteExtensions
|
||||
{
|
||||
private static unsafe void Write(IntPtr actorPtr, EquipSlot slot, SetId? id, WeaponType? type, ushort? variant, StainId? stain)
|
||||
{
|
||||
void WriteWeapon(int offset)
|
||||
{
|
||||
var address = (byte*) actorPtr + offset;
|
||||
if (id.HasValue)
|
||||
*(ushort*) address = (ushort) id.Value;
|
||||
|
||||
if (type.HasValue)
|
||||
*(ushort*) (address + 2) = (ushort) type.Value;
|
||||
|
||||
if (variant.HasValue)
|
||||
*(ushort*) (address + 4) = variant.Value;
|
||||
|
||||
if (stain.HasValue)
|
||||
*(address + 6) = (byte) stain.Value;
|
||||
}
|
||||
|
||||
void WriteEquip(int offset)
|
||||
{
|
||||
var address = (byte*) actorPtr + offset;
|
||||
if (id.HasValue)
|
||||
*(ushort*) address = (ushort) id.Value;
|
||||
|
||||
if (variant < byte.MaxValue)
|
||||
*(address + 2) = (byte) variant.Value;
|
||||
|
||||
if (stain.HasValue)
|
||||
*(address + 3) = (byte) stain.Value;
|
||||
}
|
||||
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
WriteWeapon(ActorEquipment.MainWeaponOffset);
|
||||
break;
|
||||
case EquipSlot.OffHand:
|
||||
WriteWeapon(ActorEquipment.OffWeaponOffset);
|
||||
break;
|
||||
case EquipSlot.Head:
|
||||
WriteEquip(ActorEquipment.EquipmentOffset);
|
||||
break;
|
||||
case EquipSlot.Body:
|
||||
WriteEquip(ActorEquipment.EquipmentOffset + 4);
|
||||
break;
|
||||
case EquipSlot.Hands:
|
||||
WriteEquip(ActorEquipment.EquipmentOffset + 8);
|
||||
break;
|
||||
case EquipSlot.Legs:
|
||||
WriteEquip(ActorEquipment.EquipmentOffset + 12);
|
||||
break;
|
||||
case EquipSlot.Feet:
|
||||
WriteEquip(ActorEquipment.EquipmentOffset + 16);
|
||||
break;
|
||||
case EquipSlot.Ears:
|
||||
WriteEquip(ActorEquipment.EquipmentOffset + 20);
|
||||
break;
|
||||
case EquipSlot.Neck:
|
||||
WriteEquip(ActorEquipment.EquipmentOffset + 24);
|
||||
break;
|
||||
case EquipSlot.Wrists:
|
||||
WriteEquip(ActorEquipment.EquipmentOffset + 28);
|
||||
break;
|
||||
case EquipSlot.RFinger:
|
||||
WriteEquip(ActorEquipment.EquipmentOffset + 32);
|
||||
break;
|
||||
case EquipSlot.LFinger:
|
||||
WriteEquip(ActorEquipment.EquipmentOffset + 36);
|
||||
break;
|
||||
default: throw new InvalidEnumArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
public static void Write(this Stain stain, IntPtr actorPtr, EquipSlot slot)
|
||||
=> Write(actorPtr, slot, null, null, null, stain.RowIndex);
|
||||
|
||||
public static void Write(this Item item, IntPtr actorAddress)
|
||||
{
|
||||
var (id, type, variant) = item.MainModel;
|
||||
Write(actorAddress, item.EquippableTo, id, type, variant, null);
|
||||
if (item.EquippableTo == EquipSlot.MainHand && item.HasSubModel)
|
||||
{
|
||||
var (subId, subType, subVariant) = item.SubModel;
|
||||
Write(actorAddress, EquipSlot.OffHand, subId, subType, subVariant, null);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Write(this ActorArmor armor, IntPtr actorAddress, EquipSlot slot)
|
||||
=> Write(actorAddress, slot, armor.Set, null, armor.Variant, armor.Stain);
|
||||
|
||||
public static void Write(this ActorWeapon weapon, IntPtr actorAddress, EquipSlot slot)
|
||||
=> Write(actorAddress, slot, weapon.Set, weapon.Type, weapon.Variant, weapon.Stain);
|
||||
|
||||
public static unsafe void Write(this ActorEquipment equip, IntPtr actorAddress)
|
||||
{
|
||||
if (equip.IsSet == 0)
|
||||
return;
|
||||
|
||||
Write(actorAddress, EquipSlot.MainHand, equip.MainHand.Set, equip.MainHand.Type, equip.MainHand.Variant, equip.MainHand.Stain);
|
||||
Write(actorAddress, EquipSlot.OffHand, equip.OffHand.Set, equip.OffHand.Type, equip.OffHand.Variant, equip.OffHand.Stain);
|
||||
|
||||
fixed (ActorArmor* equipment = &equip.Head)
|
||||
{
|
||||
Buffer.MemoryCopy(equipment, (byte*) actorAddress + ActorEquipment.EquipmentOffset,
|
||||
ActorEquipment.EquipmentSlots * sizeof(ActorArmor), ActorEquipment.EquipmentSlots * sizeof(ActorArmor));
|
||||
}
|
||||
}
|
||||
|
||||
public static void Write(this ActorEquipment equip, IntPtr actorAddress, ActorEquipMask models, ActorEquipMask stains)
|
||||
{
|
||||
if (models == ActorEquipMask.All && stains == ActorEquipMask.All)
|
||||
{
|
||||
equip.Write(actorAddress);
|
||||
return;
|
||||
}
|
||||
|
||||
if (models.HasFlag(ActorEquipMask.MainHand))
|
||||
Write(actorAddress, EquipSlot.MainHand, equip.MainHand.Set, equip.MainHand.Type, equip.MainHand.Variant, null);
|
||||
if (stains.HasFlag(ActorEquipMask.MainHand))
|
||||
Write(actorAddress, EquipSlot.MainHand, null, null, null, equip.MainHand.Stain);
|
||||
if (models.HasFlag(ActorEquipMask.OffHand))
|
||||
Write(actorAddress, EquipSlot.OffHand, equip.OffHand.Set, equip.OffHand.Type, equip.OffHand.Variant, null);
|
||||
if (stains.HasFlag(ActorEquipMask.OffHand))
|
||||
Write(actorAddress, EquipSlot.OffHand, null, null, null, equip.OffHand.Stain);
|
||||
|
||||
if (models.HasFlag(ActorEquipMask.Head))
|
||||
Write(actorAddress, EquipSlot.Head, equip.Head.Set, null, equip.Head.Variant, null);
|
||||
if (stains.HasFlag(ActorEquipMask.Head))
|
||||
Write(actorAddress, EquipSlot.Head, null, null, null, equip.Head.Stain);
|
||||
if (models.HasFlag(ActorEquipMask.Body))
|
||||
Write(actorAddress, EquipSlot.Body, equip.Body.Set, null, equip.Body.Variant, null);
|
||||
if (stains.HasFlag(ActorEquipMask.Body))
|
||||
Write(actorAddress, EquipSlot.Body, null, null, null, equip.Body.Stain);
|
||||
if (models.HasFlag(ActorEquipMask.Hands))
|
||||
Write(actorAddress, EquipSlot.Hands, equip.Hands.Set, null, equip.Hands.Variant, null);
|
||||
if (stains.HasFlag(ActorEquipMask.Hands))
|
||||
Write(actorAddress, EquipSlot.Hands, null, null, null, equip.Hands.Stain);
|
||||
if (models.HasFlag(ActorEquipMask.Legs))
|
||||
Write(actorAddress, EquipSlot.Legs, equip.Legs.Set, null, equip.Legs.Variant, null);
|
||||
if (stains.HasFlag(ActorEquipMask.Legs))
|
||||
Write(actorAddress, EquipSlot.Legs, null, null, null, equip.Legs.Stain);
|
||||
if (models.HasFlag(ActorEquipMask.Feet))
|
||||
Write(actorAddress, EquipSlot.Feet, equip.Feet.Set, null, equip.Feet.Variant, null);
|
||||
if (stains.HasFlag(ActorEquipMask.Feet))
|
||||
Write(actorAddress, EquipSlot.Feet, null, null, null, equip.Feet.Stain);
|
||||
|
||||
if (models.HasFlag(ActorEquipMask.Ears))
|
||||
Write(actorAddress, EquipSlot.Ears, equip.Ears.Set, null, equip.Ears.Variant, null);
|
||||
if (models.HasFlag(ActorEquipMask.Neck))
|
||||
Write(actorAddress, EquipSlot.Neck, equip.Neck.Set, null, equip.Neck.Variant, null);
|
||||
if (models.HasFlag(ActorEquipMask.Wrists))
|
||||
Write(actorAddress, EquipSlot.Wrists, equip.Wrists.Set, null, equip.Wrists.Variant, null);
|
||||
if (models.HasFlag(ActorEquipMask.LFinger))
|
||||
Write(actorAddress, EquipSlot.LFinger, equip.LFinger.Set, null, equip.LFinger.Variant, null);
|
||||
if (models.HasFlag(ActorEquipMask.RFinger))
|
||||
Write(actorAddress, EquipSlot.RFinger, equip.RFinger.Set, null, equip.RFinger.Variant, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Glamourer.GameData/ActorEquipMask.cs
Normal file
23
Glamourer.GameData/ActorEquipMask.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
|
||||
namespace Glamourer
|
||||
{
|
||||
[Flags]
|
||||
public enum ActorEquipMask : 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,
|
||||
}
|
||||
}
|
||||
124
Glamourer.GameData/Customization/CharaMakeParams.cs
Normal file
124
Glamourer.GameData/Customization/CharaMakeParams.cs
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
using Lumina.Data;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Glamourer.Customization
|
||||
{
|
||||
[Sheet("CharaMakeParams")]
|
||||
public class CharaMakeParams : ExcelRow
|
||||
{
|
||||
public const int NumMenus = 28;
|
||||
public const int NumVoices = 12;
|
||||
public const int NumGraphics = 10;
|
||||
public const int MaxNumValues = 100;
|
||||
public const int NumFaces = 8;
|
||||
public const int NumFeatures = 7;
|
||||
public const int NumEquip = 3;
|
||||
|
||||
public enum MenuType
|
||||
{
|
||||
ListSelector = 0,
|
||||
IconSelector = 1,
|
||||
ColorPicker = 2,
|
||||
DoubleColorPicker = 3,
|
||||
MultiIconSelector = 4,
|
||||
Percentage = 5,
|
||||
}
|
||||
|
||||
public struct Menu
|
||||
{
|
||||
public uint Id;
|
||||
public byte InitVal;
|
||||
public MenuType Type;
|
||||
public byte Size;
|
||||
public byte LookAt;
|
||||
public uint Mask;
|
||||
public CustomizationId Customization;
|
||||
public uint[] Values;
|
||||
public byte[] Graphic;
|
||||
}
|
||||
|
||||
public struct FacialFeatures
|
||||
{
|
||||
public uint[] Icons;
|
||||
}
|
||||
|
||||
public LazyRow<Race> Race { get; set; } = null!;
|
||||
public LazyRow<Tribe> Tribe { get; set; } = null!;
|
||||
|
||||
public sbyte Gender { get; set; }
|
||||
|
||||
public Menu[] Menus { get; set; } = new Menu[NumMenus];
|
||||
public byte[] Voices { get; set; } = new byte[NumVoices];
|
||||
public FacialFeatures[] FacialFeatureByFace { get; set; } = new FacialFeatures[NumFaces];
|
||||
public CharaMakeType.UnkStruct3347Struct[] Equip { get; set; } = new CharaMakeType.UnkStruct3347Struct[NumEquip];
|
||||
|
||||
public override void PopulateData(RowParser parser, Lumina.GameData gameData, Language language)
|
||||
{
|
||||
RowId = parser.Row;
|
||||
SubRowId = parser.SubRow;
|
||||
Race = new LazyRow<Race>(gameData, parser.ReadColumn<uint>(0), language);
|
||||
Tribe = new LazyRow<Tribe>(gameData, parser.ReadColumn<uint>(1), language);
|
||||
Gender = parser.ReadColumn<sbyte>(2);
|
||||
for (var i = 0; i < NumMenus; ++i)
|
||||
{
|
||||
Menus[i].Id = parser.ReadColumn<uint>(3 + 0 * NumMenus + i);
|
||||
Menus[i].InitVal = parser.ReadColumn<byte>(3 + 1 * NumMenus + i);
|
||||
Menus[i].Type = (MenuType) parser.ReadColumn<byte>(3 + 2 * NumMenus + i);
|
||||
Menus[i].Size = parser.ReadColumn<byte>(3 + 3 * NumMenus + i);
|
||||
Menus[i].LookAt = parser.ReadColumn<byte>(3 + 4 * NumMenus + i);
|
||||
Menus[i].Mask = parser.ReadColumn<uint>(3 + 5 * NumMenus + i);
|
||||
Menus[i].Customization = (CustomizationId) parser.ReadColumn<uint>(3 + 6 * NumMenus + i);
|
||||
Menus[i].Values = new uint[Menus[i].Size];
|
||||
|
||||
switch (Menus[i].Type)
|
||||
{
|
||||
case MenuType.ColorPicker:
|
||||
case MenuType.DoubleColorPicker:
|
||||
case MenuType.Percentage:
|
||||
break;
|
||||
default:
|
||||
for (var j = 0; j < Menus[i].Size; ++j)
|
||||
Menus[i].Values[j] = parser.ReadColumn<uint>(3 + (7 + j) * NumMenus + i);
|
||||
break;
|
||||
}
|
||||
|
||||
Menus[i].Graphic = new byte[NumGraphics];
|
||||
for (var j = 0; j < NumGraphics; ++j)
|
||||
Menus[i].Graphic[j] = parser.ReadColumn<byte>(3 + (MaxNumValues + 7 + j) * NumMenus + i);
|
||||
}
|
||||
|
||||
for (var i = 0; i < NumVoices; ++i)
|
||||
Voices[i] = parser.ReadColumn<byte>(3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + i);
|
||||
|
||||
for (var i = 0; i < NumFaces; ++i)
|
||||
{
|
||||
FacialFeatureByFace[i].Icons = new uint[NumFeatures];
|
||||
for (var j = 0; j < NumFeatures; ++j)
|
||||
FacialFeatureByFace[i].Icons[j] =
|
||||
(uint) parser.ReadColumn<int>(3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + j * NumFaces + i);
|
||||
}
|
||||
|
||||
for (var i = 0; i < NumEquip; ++i)
|
||||
{
|
||||
Equip[i] = new CharaMakeType.UnkStruct3347Struct
|
||||
{
|
||||
Helmet = parser.ReadColumn<ulong>(
|
||||
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 0),
|
||||
Top = parser.ReadColumn<ulong>(
|
||||
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 1),
|
||||
Gloves = parser.ReadColumn<ulong>(
|
||||
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 2),
|
||||
Legs = parser.ReadColumn<ulong>(
|
||||
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 3),
|
||||
Shoes = parser.ReadColumn<ulong>(
|
||||
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 4),
|
||||
Weapon = parser.ReadColumn<ulong>(
|
||||
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 5),
|
||||
SubWeapon = parser.ReadColumn<ulong>(
|
||||
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 6),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
23
Glamourer.GameData/Customization/CmpFile.cs
Normal file
23
Glamourer.GameData/Customization/CmpFile.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
using Dalamud.Plugin;
|
||||
|
||||
namespace Glamourer
|
||||
{
|
||||
public class CmpFile
|
||||
{
|
||||
public readonly Lumina.Data.FileResource File;
|
||||
public readonly uint[] RgbaColors;
|
||||
|
||||
public CmpFile(DalamudPluginInterface pi)
|
||||
{
|
||||
File = pi.Data.GetFile("chara/xls/charamake/human.cmp");
|
||||
RgbaColors = new uint[File.Data.Length >> 2];
|
||||
for (var i = 0; i < File.Data.Length; i += 4)
|
||||
{
|
||||
RgbaColors[i >> 2] = File.Data[i]
|
||||
| (uint) (File.Data[i + 1] << 8)
|
||||
| (uint) (File.Data[i + 2] << 16)
|
||||
| (uint) (File.Data[i + 3] << 24);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
32
Glamourer.GameData/Customization/Customization.cs
Normal file
32
Glamourer.GameData/Customization/Customization.cs
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Glamourer.Customization
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public readonly struct Customization
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public readonly CustomizationId Id;
|
||||
|
||||
[FieldOffset(1)]
|
||||
public readonly byte Value;
|
||||
|
||||
[FieldOffset(2)]
|
||||
public readonly ushort CustomizeId;
|
||||
|
||||
[FieldOffset(4)]
|
||||
public readonly uint IconId;
|
||||
|
||||
[FieldOffset(4)]
|
||||
public readonly uint Color;
|
||||
|
||||
public Customization(CustomizationId id, byte value, uint data = 0, ushort customizeId = 0)
|
||||
{
|
||||
Id = id;
|
||||
Value = value;
|
||||
IconId = data;
|
||||
Color = data;
|
||||
CustomizeId = customizeId;
|
||||
}
|
||||
}
|
||||
}
|
||||
70
Glamourer.GameData/Customization/CustomizationId.cs
Normal file
70
Glamourer.GameData/Customization/CustomizationId.cs
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
using System;
|
||||
|
||||
namespace Glamourer.Customization
|
||||
{
|
||||
public enum CustomizationId : byte
|
||||
{
|
||||
Race = 0,
|
||||
Gender = 1,
|
||||
BodyType = 2,
|
||||
Height = 3,
|
||||
Clan = 4,
|
||||
Face = 5,
|
||||
Hairstyle = 6,
|
||||
HighlightsOnFlag = 7,
|
||||
SkinColor = 8,
|
||||
EyeColorR = 9,
|
||||
HairColor = 10,
|
||||
HighlightColor = 11,
|
||||
FacialFeaturesTattoos = 12, // Bitmask, 1-7 per face, 8 is 1.0 tattoo
|
||||
TattooColor = 13,
|
||||
Eyebrows = 14,
|
||||
EyeColorL = 15,
|
||||
EyeShape = 16, // Flag 128 for Small
|
||||
Nose = 17,
|
||||
Jaw = 18,
|
||||
Mouth = 19, // Flag 128 for Lip Color set
|
||||
LipColor = 20, // Flag 128 for Light instead of Dark
|
||||
MuscleToneOrTailEarLength = 21,
|
||||
TailEarShape = 22,
|
||||
BustSize = 23,
|
||||
FacePaint = 24,
|
||||
FacePaintColor = 25, // Flag 128 for Light instead of Dark.
|
||||
}
|
||||
|
||||
public static class CustomizationExtensions
|
||||
{
|
||||
public static CharaMakeParams.MenuType ToType(this CustomizationId customizationId, bool isHrothgar = false)
|
||||
=> customizationId switch
|
||||
{
|
||||
CustomizationId.Race => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.Gender => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.BodyType => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.Height => CharaMakeParams.MenuType.Percentage,
|
||||
CustomizationId.Clan => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.Face => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.Hairstyle => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.HighlightsOnFlag => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.SkinColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.EyeColorR => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.HairColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.HighlightColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.FacialFeaturesTattoos => CharaMakeParams.MenuType.MultiIconSelector,
|
||||
CustomizationId.TattooColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.Eyebrows => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.EyeColorL => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.EyeShape => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.Nose => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.Jaw => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.Mouth => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.MuscleToneOrTailEarLength => CharaMakeParams.MenuType.Percentage,
|
||||
CustomizationId.TailEarShape => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.BustSize => CharaMakeParams.MenuType.Percentage,
|
||||
CustomizationId.FacePaint => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.FacePaintColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
|
||||
CustomizationId.LipColor => isHrothgar ? CharaMakeParams.MenuType.IconSelector : CharaMakeParams.MenuType.ColorPicker,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(customizationId), customizationId, null),
|
||||
};
|
||||
}
|
||||
}
|
||||
35
Glamourer.GameData/Customization/CustomizationManager.cs
Normal file
35
Glamourer.GameData/Customization/CustomizationManager.cs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
using System.Collections.Generic;
|
||||
using Dalamud.Plugin;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Customization
|
||||
{
|
||||
public class CustomizationManager : ICustomizationManager
|
||||
{
|
||||
private static CustomizationOptions? _options;
|
||||
|
||||
private CustomizationManager()
|
||||
{ }
|
||||
|
||||
public static ICustomizationManager Create(DalamudPluginInterface pi)
|
||||
{
|
||||
_options ??= new CustomizationOptions(pi);
|
||||
return new CustomizationManager();
|
||||
}
|
||||
|
||||
public IReadOnlyList<Race> Races
|
||||
=> CustomizationOptions.Races;
|
||||
|
||||
public IReadOnlyList<SubRace> Clans
|
||||
=> CustomizationOptions.Clans;
|
||||
|
||||
public IReadOnlyList<Gender> Genders
|
||||
=> CustomizationOptions.Genders;
|
||||
|
||||
public CustomizationSet GetList(SubRace clan, Gender gender)
|
||||
=> _options!.GetList(clan, gender);
|
||||
|
||||
public ImGuiScene.TextureWrap GetIcon(uint iconId)
|
||||
=> _options!.GetIcon(iconId);
|
||||
}
|
||||
}
|
||||
264
Glamourer.GameData/Customization/CustomizationOptions.cs
Normal file
264
Glamourer.GameData/Customization/CustomizationOptions.cs
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Dalamud;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.Util;
|
||||
using Lumina.Data;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Race = Penumbra.GameData.Enums.Race;
|
||||
|
||||
namespace Glamourer.Customization
|
||||
{
|
||||
public 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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
var (skinOffset, hairOffset) = race switch
|
||||
{
|
||||
SubRace.Midlander => gender == Gender.Male ? (0x1200, 0x1300) : (0x0D00, 0x0E00),
|
||||
SubRace.Highlander => gender == Gender.Male ? (0x1C00, 0x1D00) : (0x1700, 0x1800),
|
||||
SubRace.Wildwood => gender == Gender.Male ? (0x2600, 0x2700) : (0x2100, 0x2200),
|
||||
SubRace.Duskwright => gender == Gender.Male ? (0x3000, 0x3100) : (0x2B00, 0x2C00),
|
||||
SubRace.Plainsfolk => gender == Gender.Male ? (0x3A00, 0x3B00) : (0x3500, 0x3600),
|
||||
SubRace.Dunesfolk => gender == Gender.Male ? (0x4400, 0x4500) : (0x3F00, 0x4000),
|
||||
SubRace.SeekerOfTheSun => gender == Gender.Male ? (0x4E00, 0x4F00) : (0x4900, 0x4A00),
|
||||
SubRace.KeeperOfTheMoon => gender == Gender.Male ? (0x5800, 0x5900) : (0x5300, 0x5400),
|
||||
SubRace.Seawolf => gender == Gender.Male ? (0x6200, 0x6300) : (0x5D00, 0x5E00),
|
||||
SubRace.Hellsguard => gender == Gender.Male ? (0x6C00, 0x6D00) : (0x6700, 0x6800),
|
||||
SubRace.Raen => gender == Gender.Male ? (0x7100, 0x7700) : (0x7600, 0x7200),
|
||||
SubRace.Xaela => gender == Gender.Male ? (0x7B00, 0x8100) : (0x8000, 0x7C00),
|
||||
SubRace.Hellion => gender == Gender.Male ? (0x8500, 0x8600) : (0x0000, 0x0000),
|
||||
SubRace.Lost => gender == Gender.Male ? (0x8C00, 0x8F00) : (0x0000, 0x0000),
|
||||
SubRace.Rava => gender == Gender.Male ? (0x0000, 0x0000) : (0x9E00, 0x9F00),
|
||||
SubRace.Veena => gender == Gender.Male ? (0x0000, 0x0000) : (0xA800, 0xA900),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(race), race, null),
|
||||
};
|
||||
|
||||
return (CreateColorPicker(CustomizationId.SkinColor, skinOffset, 192),
|
||||
CreateColorPicker(CustomizationId.HairColor, hairOffset, 192));
|
||||
}
|
||||
|
||||
private Customization FromValueAndIndex(CustomizationId id, uint value, int index)
|
||||
{
|
||||
var row = _customizeSheet.GetRow(value);
|
||||
return row == null
|
||||
? new Customization(id, (byte) index, value, 0)
|
||||
: new Customization(id, row.FeatureID, row.Icon, (ushort) row.RowId);
|
||||
}
|
||||
|
||||
private 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(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.FacePaint)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.FacePaint, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
|
||||
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 = race.ToRace() == Race.Hrothgar ? HrothgarFaces(row) : 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(row),
|
||||
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 = race.ToRace() == Race.Hrothgar ? set.HairStyles.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;
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
private readonly ExcelSheet<CharaMakeCustomize> _customizeSheet;
|
||||
private readonly ExcelSheet<CharaMakeParams> _listSheet;
|
||||
private readonly ExcelSheet<HairMakeType> _hairSheet;
|
||||
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 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)
|
||||
{
|
||||
_cmpFile = new CmpFile(pi);
|
||||
_customizeSheet = pi.Data.GetExcelSheet<CharaMakeCustomize>();
|
||||
var tmp = pi.Data.Excel.GetType()!.GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||
.MakeGenericMethod(typeof(CharaMakeParams))!.Invoke(pi.Data.Excel, new object?[]
|
||||
{
|
||||
"charamaketype",
|
||||
FromClientLanguage(pi.ClientState.ClientLanguage),
|
||||
null,
|
||||
}) as ExcelSheet<CharaMakeParams>;
|
||||
_listSheet = tmp!;
|
||||
_hairSheet = pi.Data.GetExcelSheet<HairMakeType>();
|
||||
|
||||
_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, _list.Length * 50);
|
||||
foreach (var race in Clans)
|
||||
{
|
||||
foreach (var gender in Genders)
|
||||
_list[ToIndex(race, gender)] = GetSet(race, gender);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
140
Glamourer.GameData/Customization/CustomizationSet.cs
Normal file
140
Glamourer.GameData/Customization/CustomizationSet.cs
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Customization
|
||||
{
|
||||
public class CustomizationSet
|
||||
{
|
||||
public const int DefaultAvailable =
|
||||
(1 << (int) CustomizationId.Height)
|
||||
| (1 << (int) CustomizationId.Hairstyle)
|
||||
| (1 << (int) CustomizationId.HighlightsOnFlag)
|
||||
| (1 << (int) CustomizationId.SkinColor)
|
||||
| (1 << (int) CustomizationId.EyeColorR)
|
||||
| (1 << (int) CustomizationId.EyeColorL)
|
||||
| (1 << (int) CustomizationId.HairColor)
|
||||
| (1 << (int) CustomizationId.HighlightColor)
|
||||
| (1 << (int) CustomizationId.FacialFeaturesTattoos)
|
||||
| (1 << (int) CustomizationId.TattooColor)
|
||||
| (1 << (int) CustomizationId.LipColor)
|
||||
| (1 << (int) CustomizationId.Height);
|
||||
|
||||
internal CustomizationSet(SubRace clan, Gender gender)
|
||||
{
|
||||
Gender = gender;
|
||||
Clan = clan;
|
||||
_settingAvailable =
|
||||
clan.ToRace() == Race.Viera && gender == Gender.Male
|
||||
|| clan.ToRace() == Race.Hrothgar && gender == Gender.Female
|
||||
? 0
|
||||
: DefaultAvailable;
|
||||
}
|
||||
|
||||
public Gender Gender { get; }
|
||||
public SubRace Clan { get; }
|
||||
|
||||
public Race Race
|
||||
=> Clan.ToRace();
|
||||
|
||||
private int _settingAvailable = DefaultAvailable;
|
||||
|
||||
internal void SetAvailable(CustomizationId id)
|
||||
=> _settingAvailable |= 1 << (int) id;
|
||||
|
||||
public bool IsAvailable(CustomizationId id)
|
||||
=> (_settingAvailable & (1 << (int) id)) != 0;
|
||||
|
||||
public int NumEyebrows { get; internal set; }
|
||||
public int NumEyeShapes { get; internal set; }
|
||||
public int NumNoseShapes { get; internal set; }
|
||||
public int NumJawShapes { get; internal set; }
|
||||
public int NumMouthShapes { get; internal set; }
|
||||
|
||||
|
||||
public IReadOnlyList<Customization> Faces { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> HairStyles { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> TailEarShapes { get; internal set; } = null!;
|
||||
public IReadOnlyList<IReadOnlyList<Customization>> FeaturesTattoos { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> FacePaints { get; internal set; } = null!;
|
||||
|
||||
public IReadOnlyList<Customization> SkinColors { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> HairColors { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> HighlightColors { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> EyeColors { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> TattooColors { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> FacePaintColorsLight { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> FacePaintColorsDark { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> LipColorsLight { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> LipColorsDark { get; internal set; } = null!;
|
||||
|
||||
public IReadOnlyDictionary<CustomizationId, string> OptionName { get; internal set; } = null!;
|
||||
|
||||
public Customization FacialFeature(int faceIdx, int idx)
|
||||
=> FeaturesTattoos[faceIdx][idx];
|
||||
|
||||
public Customization Data(CustomizationId id, int idx)
|
||||
{
|
||||
if (idx > Count(id))
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
switch (id.ToType())
|
||||
{
|
||||
case CharaMakeParams.MenuType.Percentage: return new Customization(id, (byte) idx, 0, (ushort) idx);
|
||||
case CharaMakeParams.MenuType.ListSelector: return new Customization(id, (byte) idx, 0, (ushort) idx);
|
||||
}
|
||||
|
||||
return id switch
|
||||
{
|
||||
CustomizationId.Face => Faces[idx],
|
||||
CustomizationId.Hairstyle => HairStyles[idx],
|
||||
CustomizationId.TailEarShape => TailEarShapes[idx],
|
||||
CustomizationId.FacePaint => FacePaints[idx],
|
||||
CustomizationId.FacialFeaturesTattoos => FeaturesTattoos[0][idx],
|
||||
|
||||
CustomizationId.SkinColor => SkinColors[idx],
|
||||
CustomizationId.EyeColorL => EyeColors[idx],
|
||||
CustomizationId.EyeColorR => EyeColors[idx],
|
||||
CustomizationId.HairColor => HairColors[idx],
|
||||
CustomizationId.HighlightColor => HighlightColors[idx],
|
||||
CustomizationId.TattooColor => TattooColors[idx],
|
||||
CustomizationId.LipColor => idx < 96 ? LipColorsDark[idx] : LipColorsLight[idx - 96],
|
||||
CustomizationId.FacePaintColor => idx < 96 ? FacePaintColorsDark[idx] : FacePaintColorsLight[idx - 96],
|
||||
_ => new Customization(0, 0),
|
||||
};
|
||||
}
|
||||
|
||||
public int Count(CustomizationId id)
|
||||
{
|
||||
if (!IsAvailable(id))
|
||||
return 0;
|
||||
|
||||
if (id.ToType() == CharaMakeParams.MenuType.Percentage)
|
||||
return 101;
|
||||
|
||||
return id switch
|
||||
{
|
||||
CustomizationId.Face => Faces.Count,
|
||||
CustomizationId.Hairstyle => HairStyles.Count,
|
||||
CustomizationId.HighlightsOnFlag => 2,
|
||||
CustomizationId.SkinColor => SkinColors.Count,
|
||||
CustomizationId.EyeColorR => EyeColors.Count,
|
||||
CustomizationId.HairColor => HairColors.Count,
|
||||
CustomizationId.HighlightColor => HighlightColors.Count,
|
||||
CustomizationId.FacialFeaturesTattoos => 8,
|
||||
CustomizationId.TattooColor => TattooColors.Count,
|
||||
CustomizationId.Eyebrows => NumEyebrows,
|
||||
CustomizationId.EyeColorL => EyeColors.Count,
|
||||
CustomizationId.EyeShape => NumEyeShapes,
|
||||
CustomizationId.Nose => NumNoseShapes,
|
||||
CustomizationId.Jaw => NumJawShapes,
|
||||
CustomizationId.Mouth => NumMouthShapes,
|
||||
CustomizationId.LipColor => LipColorsLight.Count + LipColorsDark.Count,
|
||||
CustomizationId.TailEarShape => TailEarShapes.Count,
|
||||
CustomizationId.FacePaint => FacePaints.Count,
|
||||
CustomizationId.FacePaintColor => FacePaintColorsLight.Count + FacePaintColorsDark.Count,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(id), id, null),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
16
Glamourer.GameData/Customization/ICustomizationManager.cs
Normal file
16
Glamourer.GameData/Customization/ICustomizationManager.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
using System.Collections.Generic;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Customization
|
||||
{
|
||||
public interface ICustomizationManager
|
||||
{
|
||||
public IReadOnlyList<Race> Races { get; }
|
||||
public IReadOnlyList<SubRace> Clans { get; }
|
||||
public IReadOnlyList<Gender> Genders { get; }
|
||||
|
||||
public CustomizationSet GetList(SubRace race, Gender gender);
|
||||
|
||||
public ImGuiScene.TextureWrap GetIcon(uint iconId);
|
||||
}
|
||||
}
|
||||
61
Glamourer.GameData/Glamourer - Backup.GameData.csproj
Normal file
61
Glamourer.GameData/Glamourer - Backup.GameData.csproj
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<RootNamespace>Glamourer</RootNamespace>
|
||||
<AssemblyName>Glamourer.GameData</AssemblyName>
|
||||
<FileVersion>1.0.0.0</FileVersion>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
<Company>SoftOtter</Company>
|
||||
<Product>Glamourer</Product>
|
||||
<Copyright>Copyright © 2020</Copyright>
|
||||
<Deterministic>true</Deterministic>
|
||||
<OutputType>Library</OutputType>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugType>full</DebugType>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Dalamud">
|
||||
<HintPath>$(DALAMUD_ROOT)\Dalamud.dll</HintPath>
|
||||
<HintPath>..\libs\Dalamud.dll</HintPath>
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Lumina">
|
||||
<HintPath>$(DALAMUD_ROOT)\Lumina.dll</HintPath>
|
||||
<HintPath>..\libs\Lumina.dll</HintPath>
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Lumina.Excel">
|
||||
<HintPath>$(DALAMUD_ROOT)\Lumina.Excel.dll</HintPath>
|
||||
<HintPath>..\libs\Lumina.Excel.dll</HintPath>
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Penumbra.GameData">
|
||||
<HintPath>..\..\Penumbra\Penumbra\bin\$(Configuration)\net472\Penumbra.GameData.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Util\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
61
Glamourer.GameData/Glamourer.GameData.csproj
Normal file
61
Glamourer.GameData/Glamourer.GameData.csproj
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<RootNamespace>Glamourer</RootNamespace>
|
||||
<AssemblyName>Glamourer.GameData</AssemblyName>
|
||||
<FileVersion>1.0.0.0</FileVersion>
|
||||
<AssemblyVersion>1.0.0.0</AssemblyVersion>
|
||||
<Company>SoftOtter</Company>
|
||||
<Product>Glamourer</Product>
|
||||
<Copyright>Copyright © 2020</Copyright>
|
||||
<Deterministic>true</Deterministic>
|
||||
<OutputType>Library</OutputType>
|
||||
<WarningLevel>4</WarningLevel>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<Nullable>enable</Nullable>
|
||||
<OutputPath>bin\$(Configuration)\</OutputPath>
|
||||
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||
<DebugType>full</DebugType>
|
||||
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||
<DebugType>pdbonly</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Dalamud">
|
||||
<HintPath>$(DALAMUD_ROOT)\Dalamud.dll</HintPath>
|
||||
<HintPath>..\libs\Dalamud.dll</HintPath>
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="ImGuiScene">
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\ImGuiScene.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Lumina">
|
||||
<HintPath>$(DALAMUD_ROOT)\Lumina.dll</HintPath>
|
||||
<HintPath>..\libs\Lumina.dll</HintPath>
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Lumina.Excel">
|
||||
<HintPath>$(DALAMUD_ROOT)\Lumina.Excel.dll</HintPath>
|
||||
<HintPath>..\libs\Lumina.Excel.dll</HintPath>
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="Penumbra.GameData">
|
||||
<HintPath>..\..\Penumbra\Penumbra\bin\$(Configuration)\net472\Penumbra.GameData.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
45
Glamourer.GameData/Item.cs
Normal file
45
Glamourer.GameData/Item.cs
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
73
Glamourer.GameData/Main.cs
Normal file
73
Glamourer.GameData/Main.cs
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Plugin;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer
|
||||
{
|
||||
public static class GameData
|
||||
{
|
||||
private static Dictionary<byte, Stain>? _stains;
|
||||
private static Dictionary<EquipSlot, List<Item>>? _itemsBySlot;
|
||||
|
||||
public static IReadOnlyDictionary<byte, Stain> Stains(DalamudPluginInterface pi)
|
||||
{
|
||||
if (_stains != null)
|
||||
return _stains;
|
||||
|
||||
var sheet = pi.Data.GetExcelSheet<Lumina.Excel.GeneratedSheets.Stain>();
|
||||
_stains = sheet.Where(s => s.Color != 0).ToDictionary(s => (byte) s.RowId, s => new Stain((byte) s.RowId, s));
|
||||
return _stains;
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<EquipSlot, List<Item>> ItemsBySlot(DalamudPluginInterface pi)
|
||||
{
|
||||
if (_itemsBySlot != null)
|
||||
return _itemsBySlot;
|
||||
|
||||
var sheet = pi.Data.GetExcelSheet<Lumina.Excel.GeneratedSheets.Item>();
|
||||
|
||||
Item EmptySlot(EquipSlot slot)
|
||||
=> new(sheet.First(), "Nothing", slot);
|
||||
|
||||
_itemsBySlot = new Dictionary<EquipSlot, List<Item>>()
|
||||
{
|
||||
[EquipSlot.Head] = new(200) { EmptySlot(EquipSlot.Head) },
|
||||
[EquipSlot.Body] = new(200) { EmptySlot(EquipSlot.Body) },
|
||||
[EquipSlot.Hands] = new(200) { EmptySlot(EquipSlot.Hands) },
|
||||
[EquipSlot.Legs] = new(200) { EmptySlot(EquipSlot.Legs) },
|
||||
[EquipSlot.Feet] = new(200) { EmptySlot(EquipSlot.Feet) },
|
||||
[EquipSlot.RFinger] = new(200) { EmptySlot(EquipSlot.RFinger) },
|
||||
[EquipSlot.Neck] = new(200) { EmptySlot(EquipSlot.Neck) },
|
||||
[EquipSlot.MainHand] = new(200) { EmptySlot(EquipSlot.MainHand) },
|
||||
[EquipSlot.OffHand] = new(200) { EmptySlot(EquipSlot.OffHand) },
|
||||
[EquipSlot.Wrists] = new(200) { EmptySlot(EquipSlot.Wrists) },
|
||||
[EquipSlot.Ears] = new(200) { EmptySlot(EquipSlot.Ears) },
|
||||
};
|
||||
|
||||
foreach (var item in sheet)
|
||||
{
|
||||
var name = item.Name.ToString();
|
||||
if (!name.Any())
|
||||
continue;
|
||||
|
||||
var slot = (EquipSlot) item.EquipSlotCategory.Row;
|
||||
if (slot == EquipSlot.Unknown)
|
||||
continue;
|
||||
|
||||
slot = slot.ToSlot();
|
||||
if (!_itemsBySlot.TryGetValue(slot, out var list))
|
||||
continue;
|
||||
|
||||
list.Add(new Item(item, name, slot));
|
||||
}
|
||||
|
||||
foreach (var list in _itemsBySlot.Values)
|
||||
list.Sort((i1, i2) => string.Compare(i1.Name, i2.Name, StringComparison.InvariantCulture));
|
||||
|
||||
_itemsBySlot[EquipSlot.LFinger] = _itemsBySlot[EquipSlot.RFinger];
|
||||
return _itemsBySlot;
|
||||
}
|
||||
}
|
||||
}
|
||||
41
Glamourer.GameData/Stain.cs
Normal file
41
Glamourer.GameData/Stain.cs
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
56
Glamourer.GameData/Util/IconStorage.cs
Normal file
56
Glamourer.GameData/Util/IconStorage.cs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Data.LuminaExtensions;
|
||||
using Dalamud.Plugin;
|
||||
using ImGuiScene;
|
||||
using Lumina.Data.Files;
|
||||
|
||||
namespace Glamourer.Util
|
||||
{
|
||||
public class IconStorage : IDisposable
|
||||
{
|
||||
private readonly DalamudPluginInterface _pi;
|
||||
private readonly Dictionary<int, TextureWrap> _icons;
|
||||
|
||||
public IconStorage(DalamudPluginInterface pi, int size = 0)
|
||||
{
|
||||
_pi = pi;
|
||||
_icons = new Dictionary<int, TextureWrap>(size);
|
||||
}
|
||||
|
||||
public TextureWrap this[int id]
|
||||
=> LoadIcon(id);
|
||||
|
||||
private TexFile? LoadIconHq(int id)
|
||||
{
|
||||
var path = $"ui/icon/{id / 1000 * 1000:000000}/{id:000000}_hr1.tex";
|
||||
return _pi.Data.GetFile<TexFile>(path);
|
||||
}
|
||||
|
||||
public TextureWrap LoadIcon(uint id)
|
||||
=> LoadIcon((int) id);
|
||||
|
||||
public TextureWrap LoadIcon(int id)
|
||||
{
|
||||
if (_icons.TryGetValue(id, out var ret))
|
||||
return ret;
|
||||
|
||||
var icon = LoadIconHq(id) ?? _pi.Data.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