Initial Commit

This commit is contained in:
Ottermandias 2021-07-30 17:23:15 +02:00
commit 164f304cf6
38 changed files with 2796 additions and 0 deletions

View 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),
};
}
}
}
}

View 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);
}
}
}
}

View 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;
}
}
}

View 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),
};
}
}

View 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);
}
}

View 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);
}
}
}
}

View 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),
};
}
}
}

View 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);
}
}