mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 10:17:23 +01:00
Some more reworking
This commit is contained in:
parent
6a4b5fc3b2
commit
dad146d043
41 changed files with 1714 additions and 1320 deletions
|
|
@ -22,21 +22,23 @@ public class CharaMakeParams : ExcelRow
|
|||
IconSelector = 1,
|
||||
ColorPicker = 2,
|
||||
DoubleColorPicker = 3,
|
||||
MultiIconSelector = 4,
|
||||
IconCheckmark = 4,
|
||||
Percentage = 5,
|
||||
Checkmark = 6, // custom
|
||||
Nothing = 7, // custom
|
||||
}
|
||||
|
||||
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 uint Id;
|
||||
public byte InitVal;
|
||||
public MenuType Type;
|
||||
public byte Size;
|
||||
public byte LookAt;
|
||||
public uint Mask;
|
||||
public uint Customize;
|
||||
public uint[] Values;
|
||||
public byte[] Graphic;
|
||||
}
|
||||
|
||||
public struct FacialFeatures
|
||||
|
|
@ -51,7 +53,7 @@ public class CharaMakeParams : ExcelRow
|
|||
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.CharaMakeTypeUnkData3347Obj[] Equip { get; set; } = new CharaMakeType.CharaMakeTypeUnkData3347Obj[NumEquip];
|
||||
|
||||
public override void PopulateData(RowParser parser, Lumina.GameData gameData, Language language)
|
||||
|
|
@ -64,15 +66,15 @@ public class CharaMakeParams : ExcelRow
|
|||
var currentOffset = 0;
|
||||
for (var i = 0; i < NumMenus; ++i)
|
||||
{
|
||||
currentOffset = 3 + i;
|
||||
Menus[i].Id = parser.ReadColumn<uint>(0 * NumMenus + currentOffset);
|
||||
Menus[i].InitVal = parser.ReadColumn<byte>(1 * NumMenus + currentOffset);
|
||||
Menus[i].Type = (MenuType)parser.ReadColumn<byte>(2 * NumMenus + currentOffset);
|
||||
Menus[i].Size = parser.ReadColumn<byte>(3 * NumMenus + currentOffset);
|
||||
Menus[i].LookAt = parser.ReadColumn<byte>(4 * NumMenus + currentOffset);
|
||||
Menus[i].Mask = parser.ReadColumn<uint>(5 * NumMenus + currentOffset);
|
||||
Menus[i].Customization = (CustomizationId)parser.ReadColumn<uint>(6 * NumMenus + currentOffset);
|
||||
Menus[i].Values = new uint[Menus[i].Size];
|
||||
currentOffset = 3 + i;
|
||||
Menus[i].Id = parser.ReadColumn<uint>(0 * NumMenus + currentOffset);
|
||||
Menus[i].InitVal = parser.ReadColumn<byte>(1 * NumMenus + currentOffset);
|
||||
Menus[i].Type = (MenuType)parser.ReadColumn<byte>(2 * NumMenus + currentOffset);
|
||||
Menus[i].Size = parser.ReadColumn<byte>(3 * NumMenus + currentOffset);
|
||||
Menus[i].LookAt = parser.ReadColumn<byte>(4 * NumMenus + currentOffset);
|
||||
Menus[i].Mask = parser.ReadColumn<uint>(5 * NumMenus + currentOffset);
|
||||
Menus[i].Customize = parser.ReadColumn<uint>(6 * NumMenus + currentOffset);
|
||||
Menus[i].Values = new uint[Menus[i].Size];
|
||||
|
||||
switch (Menus[i].Type)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,107 +0,0 @@
|
|||
using System;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
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 string ToDefaultName(this CustomizationId customizationId)
|
||||
=> customizationId switch
|
||||
{
|
||||
CustomizationId.Race => "Race",
|
||||
CustomizationId.Gender => "Gender",
|
||||
CustomizationId.BodyType => "Body Type",
|
||||
CustomizationId.Height => "Height",
|
||||
CustomizationId.Clan => "Clan",
|
||||
CustomizationId.Face => "Head Style",
|
||||
CustomizationId.Hairstyle => "Hair Style",
|
||||
CustomizationId.HighlightsOnFlag => "Highlights",
|
||||
CustomizationId.SkinColor => "Skin Color",
|
||||
CustomizationId.EyeColorR => "Right Eye Color",
|
||||
CustomizationId.HairColor => "Hair Color",
|
||||
CustomizationId.HighlightColor => "Highlights Color",
|
||||
CustomizationId.FacialFeaturesTattoos => "Facial Features",
|
||||
CustomizationId.TattooColor => "Tattoo Color",
|
||||
CustomizationId.Eyebrows => "Eyebrow Style",
|
||||
CustomizationId.EyeColorL => "Left Eye Color",
|
||||
CustomizationId.EyeShape => "Eye Shape",
|
||||
CustomizationId.Nose => "Nose Style",
|
||||
CustomizationId.Jaw => "Jaw Style",
|
||||
CustomizationId.Mouth => "Mouth Style",
|
||||
CustomizationId.MuscleToneOrTailEarLength => "Muscle Tone",
|
||||
CustomizationId.TailEarShape => "Tail Shape",
|
||||
CustomizationId.BustSize => "Bust Size",
|
||||
CustomizationId.FacePaint => "Face Paint",
|
||||
CustomizationId.FacePaintColor => "Face Paint Color",
|
||||
CustomizationId.LipColor => "Lip Color",
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(customizationId), customizationId, null),
|
||||
};
|
||||
|
||||
public static CharaMakeParams.MenuType ToType(this CustomizationId customizationId, Race race = Race.Hyur)
|
||||
=> 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.BustSize => CharaMakeParams.MenuType.Percentage,
|
||||
CustomizationId.FacePaint => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.FacePaintColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
|
||||
CustomizationId.TailEarShape => race is Race.Elezen or Race.Lalafell
|
||||
? CharaMakeParams.MenuType.ListSelector
|
||||
: CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.LipColor => race == Race.Hrothgar
|
||||
? CharaMakeParams.MenuType.IconSelector
|
||||
: CharaMakeParams.MenuType.ColorPicker,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(customizationId), customizationId, null),
|
||||
};
|
||||
}
|
||||
|
|
@ -13,9 +13,9 @@ namespace Glamourer.Customization
|
|||
private CustomizationManager()
|
||||
{ }
|
||||
|
||||
public static ICustomizationManager Create(DalamudPluginInterface pi, DataManager gameData, ClientLanguage language)
|
||||
public static ICustomizationManager Create(DalamudPluginInterface pi, DataManager gameData)
|
||||
{
|
||||
_options ??= new CustomizationOptions(pi, gameData, language);
|
||||
_options ??= new CustomizationOptions(pi, gameData);
|
||||
return new CustomizationManager();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,9 +64,9 @@ public partial class CustomizationOptions
|
|||
public string GetName(CustomName name)
|
||||
=> _names[(int)name];
|
||||
|
||||
internal CustomizationOptions(DalamudPluginInterface pi, DataManager gameData, ClientLanguage language)
|
||||
internal CustomizationOptions(DalamudPluginInterface pi, DataManager gameData)
|
||||
{
|
||||
var tmp = new TemporaryData(gameData, this, language);
|
||||
var tmp = new TemporaryData(gameData, this);
|
||||
_icons = new IconStorage(pi, gameData, _customizationSets.Length * 50);
|
||||
Valid = tmp.Valid;
|
||||
SetNames(gameData, tmp);
|
||||
|
|
@ -149,15 +149,15 @@ public partial class CustomizationOptions
|
|||
HighlightColors = _highlightPicker,
|
||||
TattooColors = _tattooColorPicker,
|
||||
LipColorsDark = hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark,
|
||||
LipColorsLight = hrothgar ? Array.Empty<CustomizationData>() : _lipColorPickerLight,
|
||||
LipColorsLight = hrothgar ? Array.Empty<CustomizeData>() : _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),
|
||||
NumEyebrows = GetListSize(row, CustomizeIndex.Eyebrows),
|
||||
NumEyeShapes = GetListSize(row, CustomizeIndex.EyeShape),
|
||||
NumNoseShapes = GetListSize(row, CustomizeIndex.Nose),
|
||||
NumJawShapes = GetListSize(row, CustomizeIndex.Jaw),
|
||||
NumMouthShapes = GetListSize(row, CustomizeIndex.Mouth),
|
||||
FacePaints = GetFacePaints(race, gender),
|
||||
TailEarShapes = GetTailEarShapes(row),
|
||||
};
|
||||
|
|
@ -171,7 +171,7 @@ public partial class CustomizationOptions
|
|||
return set;
|
||||
}
|
||||
|
||||
public TemporaryData(DataManager gameData, CustomizationOptions options, ClientLanguage language)
|
||||
public TemporaryData(DataManager gameData, CustomizationOptions options)
|
||||
{
|
||||
_options = options;
|
||||
_cmpFile = new CmpFile(gameData);
|
||||
|
|
@ -181,18 +181,18 @@ public partial class CustomizationOptions
|
|||
.MakeGenericMethod(typeof(CharaMakeParams)).Invoke(gameData.Excel, new object?[]
|
||||
{
|
||||
"charamaketype",
|
||||
language.ToLumina(),
|
||||
gameData.Language.ToLumina(),
|
||||
null,
|
||||
}) as ExcelSheet<CharaMakeParams>;
|
||||
_listSheet = tmp!;
|
||||
_hairSheet = gameData.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);
|
||||
_highlightPicker = CreateColorPicker(CustomizeIndex.HighlightsColor, 256, 192);
|
||||
_lipColorPickerDark = CreateColorPicker(CustomizeIndex.LipColor, 512, 96);
|
||||
_lipColorPickerLight = CreateColorPicker(CustomizeIndex.LipColor, 1024, 96, true);
|
||||
_eyeColorPicker = CreateColorPicker(CustomizeIndex.EyeColorLeft, 0, 192);
|
||||
_facePaintColorPickerDark = CreateColorPicker(CustomizeIndex.FacePaintColor, 640, 96);
|
||||
_facePaintColorPickerLight = CreateColorPicker(CustomizeIndex.FacePaintColor, 1152, 96, true);
|
||||
_tattooColorPicker = CreateColorPicker(CustomizeIndex.TattooColor, 0, 192);
|
||||
}
|
||||
|
||||
// Required sheets.
|
||||
|
|
@ -203,19 +203,19 @@ public partial class CustomizationOptions
|
|||
private readonly CmpFile _cmpFile;
|
||||
|
||||
// Those values are shared between all races.
|
||||
private readonly CustomizationData[] _highlightPicker;
|
||||
private readonly CustomizationData[] _eyeColorPicker;
|
||||
private readonly CustomizationData[] _facePaintColorPickerDark;
|
||||
private readonly CustomizationData[] _facePaintColorPickerLight;
|
||||
private readonly CustomizationData[] _lipColorPickerDark;
|
||||
private readonly CustomizationData[] _lipColorPickerLight;
|
||||
private readonly CustomizationData[] _tattooColorPicker;
|
||||
private readonly CustomizeData[] _highlightPicker;
|
||||
private readonly CustomizeData[] _eyeColorPicker;
|
||||
private readonly CustomizeData[] _facePaintColorPickerDark;
|
||||
private readonly CustomizeData[] _facePaintColorPickerLight;
|
||||
private readonly CustomizeData[] _lipColorPickerDark;
|
||||
private readonly CustomizeData[] _lipColorPickerLight;
|
||||
private readonly CustomizeData[] _tattooColorPicker;
|
||||
|
||||
private readonly CustomizationOptions _options;
|
||||
|
||||
private CustomizationData[] CreateColorPicker(CustomizationId id, int offset, int num, bool light = false)
|
||||
private CustomizeData[] CreateColorPicker(CustomizeIndex index, int offset, int num, bool light = false)
|
||||
=> _cmpFile.GetSlice(offset, num)
|
||||
.Select((c, i) => new CustomizationData(id, (CustomizationByteValue)(light ? 128 + i : 0 + i), c, (ushort)(offset + i)))
|
||||
.Select((c, i) => new CustomizeData(index, (CustomizeValue)(light ? 128 + i : 0 + i), c, (ushort)(offset + i)))
|
||||
.ToArray();
|
||||
|
||||
|
||||
|
|
@ -227,12 +227,12 @@ public partial class CustomizationOptions
|
|||
return;
|
||||
}
|
||||
|
||||
var tmp = new IReadOnlyList<CustomizationData>[set.Faces.Count + 1];
|
||||
var tmp = new IReadOnlyList<CustomizeData>[set.Faces.Count + 1];
|
||||
tmp[0] = set.HairStyles;
|
||||
|
||||
for (var i = 1; i <= set.Faces.Count; ++i)
|
||||
{
|
||||
bool Valid(CustomizationData c)
|
||||
bool Valid(CustomizeData c)
|
||||
{
|
||||
var data = _customizeSheet.GetRow(c.CustomizeId)?.Unknown6 ?? 0;
|
||||
return data == 0 || data == i + set.Faces.Count;
|
||||
|
|
@ -247,22 +247,38 @@ public partial class CustomizationOptions
|
|||
private static void SetMenuTypes(CustomizationSet set, CharaMakeParams row)
|
||||
{
|
||||
// Set up the menu types for all customizations.
|
||||
set.Types = ((CustomizationId[])Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
||||
set.Types = Enum.GetValues<CustomizeIndex>().Select(c =>
|
||||
{
|
||||
// Those types are not correctly given in the menu, so special case them to color pickers.
|
||||
switch (c)
|
||||
{
|
||||
case CustomizationId.HighlightColor:
|
||||
case CustomizationId.EyeColorL:
|
||||
case CustomizationId.EyeColorR:
|
||||
case CustomizeIndex.HighlightsColor:
|
||||
case CustomizeIndex.EyeColorLeft:
|
||||
case CustomizeIndex.EyeColorRight:
|
||||
return CharaMakeParams.MenuType.ColorPicker;
|
||||
case CustomizeIndex.BodyType: return CharaMakeParams.MenuType.Nothing;
|
||||
case CustomizeIndex.FacePaintReversed:
|
||||
case CustomizeIndex.Highlights:
|
||||
case CustomizeIndex.SmallIris:
|
||||
case CustomizeIndex.Lipstick:
|
||||
return CharaMakeParams.MenuType.Checkmark;
|
||||
case CustomizeIndex.FacialFeature1:
|
||||
case CustomizeIndex.FacialFeature2:
|
||||
case CustomizeIndex.FacialFeature3:
|
||||
case CustomizeIndex.FacialFeature4:
|
||||
case CustomizeIndex.FacialFeature5:
|
||||
case CustomizeIndex.FacialFeature6:
|
||||
case CustomizeIndex.FacialFeature7:
|
||||
case CustomizeIndex.LegacyTattoo:
|
||||
return CharaMakeParams.MenuType.IconCheckmark;
|
||||
}
|
||||
|
||||
var gameId = c.ToByteAndMask().ByteIdx;
|
||||
// Otherwise find the first menu corresponding to the id.
|
||||
// If there is none, assume a list.
|
||||
var menu = row.Menus
|
||||
.Cast<CharaMakeParams.Menu?>()
|
||||
.FirstOrDefault(m => m!.Value.Customization == c);
|
||||
.FirstOrDefault(m => m!.Value.Customize == gameId);
|
||||
return menu?.Type ?? CharaMakeParams.MenuType.ListSelector;
|
||||
}).ToArray();
|
||||
set.Order = CustomizationSet.ComputeOrder(set);
|
||||
|
|
@ -271,59 +287,96 @@ public partial class CustomizationOptions
|
|||
// Set customizations available if they have any options.
|
||||
private static void SetAvailability(CustomizationSet set, CharaMakeParams row)
|
||||
{
|
||||
void Set(bool available, CustomizationId flag)
|
||||
if (set.Race == Race.Hrothgar && set.Gender == Gender.Female)
|
||||
return;
|
||||
|
||||
void Set(bool available, CustomizeIndex flag)
|
||||
{
|
||||
if (available)
|
||||
set.SetAvailable(flag);
|
||||
}
|
||||
|
||||
// Both are percentages that are either unavailable or 0-100.
|
||||
Set(GetListSize(row, CustomizationId.BustSize) > 0, CustomizationId.BustSize);
|
||||
Set(GetListSize(row, CustomizationId.MuscleToneOrTailEarLength) > 0, CustomizationId.MuscleToneOrTailEarLength);
|
||||
Set(set.NumEyebrows > 0, CustomizationId.Eyebrows);
|
||||
Set(set.NumEyeShapes > 0, CustomizationId.EyeShape);
|
||||
Set(set.NumNoseShapes > 0, CustomizationId.Nose);
|
||||
Set(set.NumJawShapes > 0, CustomizationId.Jaw);
|
||||
Set(set.NumMouthShapes > 0, CustomizationId.Mouth);
|
||||
Set(set.TailEarShapes.Count > 0, CustomizationId.TailEarShape);
|
||||
Set(set.Faces.Count > 0, CustomizationId.Face);
|
||||
Set(set.FacePaints.Count > 0, CustomizationId.FacePaint);
|
||||
Set(set.FacePaints.Count > 0, CustomizationId.FacePaintColor);
|
||||
Set(true, CustomizeIndex.Height);
|
||||
Set(set.Faces.Count > 0, CustomizeIndex.Face);
|
||||
Set(true, CustomizeIndex.Hairstyle);
|
||||
Set(true, CustomizeIndex.Highlights);
|
||||
Set(true, CustomizeIndex.SkinColor);
|
||||
Set(true, CustomizeIndex.EyeColorRight);
|
||||
Set(true, CustomizeIndex.HairColor);
|
||||
Set(true, CustomizeIndex.HighlightsColor);
|
||||
Set(true, CustomizeIndex.TattooColor);
|
||||
Set(set.NumEyebrows > 0, CustomizeIndex.Eyebrows);
|
||||
Set(true, CustomizeIndex.EyeColorLeft);
|
||||
Set(set.NumEyeShapes > 0, CustomizeIndex.EyeShape);
|
||||
Set(set.NumNoseShapes > 0, CustomizeIndex.Nose);
|
||||
Set(set.NumJawShapes > 0, CustomizeIndex.Jaw);
|
||||
Set(set.NumMouthShapes > 0, CustomizeIndex.Mouth);
|
||||
Set(set.LipColorsDark.Count > 0, CustomizeIndex.LipColor);
|
||||
Set(GetListSize(row, CustomizeIndex.MuscleMass) > 0, CustomizeIndex.MuscleMass);
|
||||
Set(set.TailEarShapes.Count > 0, CustomizeIndex.TailShape);
|
||||
Set(GetListSize(row, CustomizeIndex.BustSize) > 0, CustomizeIndex.BustSize);
|
||||
Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaint);
|
||||
Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaintColor);
|
||||
Set(true, CustomizeIndex.FacialFeature1);
|
||||
Set(true, CustomizeIndex.FacialFeature2);
|
||||
Set(true, CustomizeIndex.FacialFeature3);
|
||||
Set(true, CustomizeIndex.FacialFeature4);
|
||||
Set(true, CustomizeIndex.FacialFeature5);
|
||||
Set(true, CustomizeIndex.FacialFeature6);
|
||||
Set(true, CustomizeIndex.FacialFeature7);
|
||||
Set(true, CustomizeIndex.LegacyTattoo);
|
||||
Set(true, CustomizeIndex.SmallIris);
|
||||
Set(set.Race != Race.Hrothgar, CustomizeIndex.Lipstick);
|
||||
Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaintReversed);
|
||||
}
|
||||
|
||||
// Create a list of lists of facial features and the legacy tattoo.
|
||||
private static void SetFacialFeatures(CustomizationSet set, CharaMakeParams row)
|
||||
{
|
||||
var count = set.Faces.Count;
|
||||
var featureDict = new List<IReadOnlyList<CustomizationData>>(count);
|
||||
var count = set.Faces.Count;
|
||||
set.FacialFeature1 = new List<(CustomizeData, CustomizeData)>(count);
|
||||
|
||||
static (CustomizeData, CustomizeData) Create(CustomizeIndex i, uint data)
|
||||
=> (new CustomizeData(i, CustomizeValue.Zero, data, 0), new CustomizeData(i, CustomizeValue.Max, data, 1));
|
||||
|
||||
set.LegacyTattoo = Create(CustomizeIndex.LegacyTattoo, 137905);
|
||||
|
||||
var tmp = Enumerable.Repeat(0, 7).Select(_ => new (CustomizeData, CustomizeData)[count + 1]).ToArray();
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
var legacyTattoo = new CustomizationData(CustomizationId.FacialFeaturesTattoos, (CustomizationByteValue)(1 << 7), 137905,
|
||||
(ushort)((i + 1) * 8));
|
||||
featureDict.Add(row.FacialFeatureByFace[i].Icons.Select((val, idx)
|
||||
=> new CustomizationData(CustomizationId.FacialFeaturesTattoos, (CustomizationByteValue)(1 << idx), val,
|
||||
(ushort)(i * 8 + idx)))
|
||||
.Append(legacyTattoo)
|
||||
.ToArray());
|
||||
var data = row.FacialFeatureByFace[i].Icons;
|
||||
tmp[0][i + 1] = Create(CustomizeIndex.FacialFeature1, data[0]);
|
||||
tmp[1][i + 1] = Create(CustomizeIndex.FacialFeature2, data[1]);
|
||||
tmp[2][i + 1] = Create(CustomizeIndex.FacialFeature3, data[2]);
|
||||
tmp[3][i + 1] = Create(CustomizeIndex.FacialFeature4, data[3]);
|
||||
tmp[4][i + 1] = Create(CustomizeIndex.FacialFeature5, data[4]);
|
||||
tmp[5][i + 1] = Create(CustomizeIndex.FacialFeature6, data[5]);
|
||||
tmp[6][i + 1] = Create(CustomizeIndex.FacialFeature7, data[6]);
|
||||
}
|
||||
|
||||
set.FeaturesTattoos = featureDict.ToArray();
|
||||
set.FacialFeature1 = tmp[0];
|
||||
set.FacialFeature2 = tmp[1];
|
||||
set.FacialFeature3 = tmp[2];
|
||||
set.FacialFeature4 = tmp[3];
|
||||
set.FacialFeature5 = tmp[4];
|
||||
set.FacialFeature6 = tmp[5];
|
||||
set.FacialFeature7 = tmp[6];
|
||||
}
|
||||
|
||||
// Set the names for the given set of parameters.
|
||||
private void SetNames(CustomizationSet set, CharaMakeParams row)
|
||||
{
|
||||
var nameArray = ((CustomizationId[])Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
||||
var nameArray = Enum.GetValues<CustomizeIndex>().Select(c =>
|
||||
{
|
||||
// Find the first menu that corresponds to the Id.
|
||||
var byteId = c.ToByteAndMask().ByteIdx;
|
||||
var menu = row.Menus
|
||||
.Cast<CharaMakeParams.Menu?>()
|
||||
.FirstOrDefault(m => m!.Value.Customization == c);
|
||||
.FirstOrDefault(m => m!.Value.Customize == byteId);
|
||||
if (menu == null)
|
||||
{
|
||||
// If none exists and the id corresponds to highlights, set the Highlights name.
|
||||
if (c == CustomizationId.HighlightsOnFlag)
|
||||
if (c == CustomizeIndex.Highlights)
|
||||
return Lobby.GetRow(237)?.Text.ToDalamudString().ToString() ?? "Highlights";
|
||||
|
||||
// Otherwise there is an error and we use the default name.
|
||||
|
|
@ -331,7 +384,7 @@ public partial class CustomizationOptions
|
|||
}
|
||||
|
||||
// Facial Features and Tattoos is created by combining two strings.
|
||||
if (c == CustomizationId.FacialFeaturesTattoos)
|
||||
if (c is >= CustomizeIndex.FacialFeature1 and <= CustomizeIndex.LegacyTattoo)
|
||||
return
|
||||
$"{Lobby.GetRow(1741)?.Text.ToDalamudString().ToString() ?? "Facial Features"} & {Lobby.GetRow(1742)?.Text.ToDalamudString().ToString() ?? "Tattoos"}";
|
||||
|
||||
|
|
@ -341,14 +394,14 @@ public partial class CustomizationOptions
|
|||
}).ToArray();
|
||||
|
||||
// Add names for both eye colors.
|
||||
nameArray[(int)CustomizationId.EyeColorL] = nameArray[(int)CustomizationId.EyeColorR];
|
||||
nameArray[(int)CustomizationId.EyeColorR] = _options.GetName(CustomName.OddEyes);
|
||||
nameArray[(int)CustomizeIndex.EyeColorLeft] = nameArray[(int)CustomizeIndex.EyeColorRight];
|
||||
nameArray[(int)CustomizeIndex.EyeColorRight] = _options.GetName(CustomName.OddEyes);
|
||||
|
||||
set.OptionName = nameArray;
|
||||
}
|
||||
|
||||
// Obtain available skin and hair colors for the given subrace and gender.
|
||||
private (CustomizationData[], CustomizationData[]) GetColors(SubRace race, Gender gender)
|
||||
private (CustomizeData[], CustomizeData[]) GetColors(SubRace race, Gender gender)
|
||||
{
|
||||
if (race is > SubRace.Veena or SubRace.Unknown)
|
||||
throw new ArgumentOutOfRangeException(nameof(race), race, null);
|
||||
|
|
@ -356,16 +409,16 @@ public partial class CustomizationOptions
|
|||
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));
|
||||
return (CreateColorPicker(CustomizeIndex.SkinColor, idx << 8, 192),
|
||||
CreateColorPicker(CustomizeIndex.HairColor, (idx + 1) << 8, 192));
|
||||
}
|
||||
|
||||
// Obtain available hairstyles via reflection from the Hair sheet for the given subrace and gender.
|
||||
private CustomizationData[] GetHairStyles(SubRace race, Gender gender)
|
||||
private CustomizeData[] GetHairStyles(SubRace race, Gender gender)
|
||||
{
|
||||
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||
// Unknown30 is the number of available hairstyles.
|
||||
var hairList = new List<CustomizationData>(row.Unknown30);
|
||||
var hairList = new List<CustomizeData>(row.Unknown30);
|
||||
// Hairstyles can be found starting at Unknown66.
|
||||
for (var i = 0; i < row.Unknown30; ++i)
|
||||
{
|
||||
|
|
@ -378,35 +431,36 @@ public partial class CustomizationOptions
|
|||
// Hair Row from CustomizeSheet might not be set in case of unlockable hair.
|
||||
var hairRow = _customizeSheet.GetRow(customizeIdx);
|
||||
hairList.Add(hairRow != null
|
||||
? new CustomizationData(CustomizationId.Hairstyle, (CustomizationByteValue)hairRow.FeatureID, hairRow.Icon,
|
||||
? new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon,
|
||||
(ushort)hairRow.RowId)
|
||||
: new CustomizationData(CustomizationId.Hairstyle, (CustomizationByteValue)i, customizeIdx));
|
||||
: new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx));
|
||||
}
|
||||
|
||||
return hairList.ToArray();
|
||||
}
|
||||
|
||||
// Get Features.
|
||||
private CustomizationData FromValueAndIndex(CustomizationId id, uint value, int index)
|
||||
private CustomizeData FromValueAndIndex(CustomizeIndex id, uint value, int index)
|
||||
{
|
||||
var row = _customizeSheet.GetRow(value);
|
||||
return row == null
|
||||
? new CustomizationData(id, (CustomizationByteValue)(index + 1), value)
|
||||
: new CustomizationData(id, (CustomizationByteValue)row.FeatureID, row.Icon, (ushort)row.RowId);
|
||||
? new CustomizeData(id, (CustomizeValue)(index + 1), value)
|
||||
: new CustomizeData(id, (CustomizeValue)row.FeatureID, row.Icon, (ushort)row.RowId);
|
||||
}
|
||||
|
||||
// Get List sizes.
|
||||
private static int GetListSize(CharaMakeParams row, CustomizationId id)
|
||||
private static int GetListSize(CharaMakeParams row, CustomizeIndex index)
|
||||
{
|
||||
var menu = row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == id);
|
||||
var gameId = index.ToByteAndMask().ByteIdx;
|
||||
var menu = row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customize == gameId);
|
||||
return menu?.Size ?? 0;
|
||||
}
|
||||
|
||||
// Get face paints from the hair sheet via reflection.
|
||||
private CustomizationData[] GetFacePaints(SubRace race, Gender gender)
|
||||
private CustomizeData[] GetFacePaints(SubRace race, Gender gender)
|
||||
{
|
||||
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||
var paintList = new List<CustomizationData>(row.Unknown37);
|
||||
var paintList = new List<CustomizeData>(row.Unknown37);
|
||||
|
||||
// Number of available face paints is at Unknown37.
|
||||
for (var i = 0; i < row.Unknown37; ++i)
|
||||
|
|
@ -422,30 +476,33 @@ public partial class CustomizationOptions
|
|||
var paintRow = _customizeSheet.GetRow(customizeIdx);
|
||||
// Facepaint Row from CustomizeSheet might not be set in case of unlockable facepaints.
|
||||
paintList.Add(paintRow != null
|
||||
? new CustomizationData(CustomizationId.FacePaint, (CustomizationByteValue)paintRow.FeatureID, paintRow.Icon,
|
||||
? new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)paintRow.FeatureID, paintRow.Icon,
|
||||
(ushort)paintRow.RowId)
|
||||
: new CustomizationData(CustomizationId.FacePaint, (CustomizationByteValue)i, customizeIdx));
|
||||
: new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)i, customizeIdx));
|
||||
}
|
||||
|
||||
return paintList.ToArray();
|
||||
}
|
||||
|
||||
// Specific icons for tails or ears.
|
||||
private CustomizationData[] 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<CustomizationData>();
|
||||
private CustomizeData[] GetTailEarShapes(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>()
|
||||
.FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.TailShape.ToByteAndMask().ByteIdx)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizeIndex.TailShape, v, i)).ToArray()
|
||||
?? Array.Empty<CustomizeData>();
|
||||
|
||||
// Specific icons for faces.
|
||||
private CustomizationData[] 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<CustomizationData>();
|
||||
private CustomizeData[] GetFaces(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.Face.ToByteAndMask().ByteIdx)
|
||||
?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizeIndex.Face, v, i)).ToArray()
|
||||
?? Array.Empty<CustomizeData>();
|
||||
|
||||
// Specific icons for Hrothgar patterns.
|
||||
private CustomizationData[] 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<CustomizationData>();
|
||||
private CustomizeData[] HrothgarFurPattern(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>()
|
||||
.FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.LipColor.ToByteAndMask().ByteIdx)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizeIndex.LipColor, v, i)).ToArray()
|
||||
?? Array.Empty<CustomizeData>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Xml.XPath;
|
||||
using OtterGui;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
|
|
@ -13,56 +14,32 @@ public class CustomizationSet
|
|||
{
|
||||
internal CustomizationSet(SubRace clan, Gender gender)
|
||||
{
|
||||
Gender = gender;
|
||||
Clan = clan;
|
||||
Race = clan.ToRace();
|
||||
_settingAvailable = Race == Race.Hrothgar && gender == Gender.Female
|
||||
? 0u
|
||||
: DefaultAvailable;
|
||||
Gender = gender;
|
||||
Clan = clan;
|
||||
Race = clan.ToRace();
|
||||
_settingAvailable = 0;
|
||||
}
|
||||
|
||||
public Gender Gender { get; }
|
||||
public SubRace Clan { get; }
|
||||
public Race Race { get; }
|
||||
|
||||
private uint _settingAvailable;
|
||||
private CustomizeFlag _settingAvailable;
|
||||
|
||||
internal void SetAvailable(CustomizationId id)
|
||||
=> _settingAvailable |= 1u << (int)id;
|
||||
internal void SetAvailable(CustomizeIndex index)
|
||||
=> _settingAvailable |= index.ToFlag();
|
||||
|
||||
public bool IsAvailable(CustomizationId id)
|
||||
=> (_settingAvailable & (1u << (int)id)) != 0;
|
||||
|
||||
private const uint DefaultAvailable =
|
||||
(1u << (int)CustomizationId.Height)
|
||||
| (1u << (int)CustomizationId.Hairstyle)
|
||||
| (1u << (int)CustomizationId.SkinColor)
|
||||
| (1u << (int)CustomizationId.EyeColorR)
|
||||
| (1u << (int)CustomizationId.EyeColorL)
|
||||
| (1u << (int)CustomizationId.HairColor)
|
||||
| (1u << (int)CustomizationId.HighlightColor)
|
||||
| (1u << (int)CustomizationId.FacialFeaturesTattoos)
|
||||
| (1u << (int)CustomizationId.TattooColor)
|
||||
| (1u << (int)CustomizationId.LipColor)
|
||||
| (1u << (int)CustomizationId.Height);
|
||||
|
||||
public string ToHumanReadable(Customize customizationData)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var id in Enum.GetValues<CustomizationId>().Where(IsAvailable))
|
||||
sb.AppendFormat("{0,-20}", Option(id)).Append(customizationData[id]);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
public bool IsAvailable(CustomizeIndex index)
|
||||
=> _settingAvailable.HasFlag(index.ToFlag());
|
||||
|
||||
// Meta
|
||||
public IReadOnlyList<string> OptionName { get; internal set; } = null!;
|
||||
|
||||
public string Option(CustomizationId id)
|
||||
=> OptionName[(int)id];
|
||||
public string Option(CustomizeIndex index)
|
||||
=> OptionName[(int)index];
|
||||
|
||||
public IReadOnlyList<CharaMakeParams.MenuType> Types { get; internal set; } = null!;
|
||||
public IReadOnlyDictionary<CharaMakeParams.MenuType, CustomizationId[]> Order { get; internal set; } = null!;
|
||||
public IReadOnlyList<CharaMakeParams.MenuType> Types { get; internal set; } = null!;
|
||||
public IReadOnlyDictionary<CharaMakeParams.MenuType, CustomizeIndex[]> Order { get; internal set; } = null!;
|
||||
|
||||
|
||||
// Always list selector.
|
||||
|
|
@ -74,164 +51,224 @@ public class CustomizationSet
|
|||
|
||||
|
||||
// Always Icon Selector
|
||||
public IReadOnlyList<CustomizationData> Faces { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizationData> HairStyles { get; internal init; } = null!;
|
||||
public IReadOnlyList<IReadOnlyList<CustomizationData>> HairByFace { get; internal set; } = null!;
|
||||
public IReadOnlyList<CustomizationData> TailEarShapes { get; internal init; } = null!;
|
||||
public IReadOnlyList<IReadOnlyList<CustomizationData>> FeaturesTattoos { get; internal set; } = null!;
|
||||
public IReadOnlyList<CustomizationData> FacePaints { get; internal init; } = null!;
|
||||
|
||||
public CustomizationData FacialFeature(CustomizationByteValue face, int idx)
|
||||
{
|
||||
face = HrothgarFaceHack(face);
|
||||
var faceIdx = Faces.IndexOf(p => p.Value == face);
|
||||
return FeaturesTattoos[faceIdx != -1 ? faceIdx : 0][idx];
|
||||
}
|
||||
public IReadOnlyList<CustomizeData> Faces { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizeData> HairStyles { get; internal init; } = null!;
|
||||
public IReadOnlyList<IReadOnlyList<CustomizeData>> HairByFace { get; internal set; } = null!;
|
||||
public IReadOnlyList<CustomizeData> TailEarShapes { get; internal init; } = null!;
|
||||
public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature1 { get; internal set; } = null!;
|
||||
public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature2 { get; internal set; } = null!;
|
||||
public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature3 { get; internal set; } = null!;
|
||||
public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature4 { get; internal set; } = null!;
|
||||
public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature5 { get; internal set; } = null!;
|
||||
public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature6 { get; internal set; } = null!;
|
||||
public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature7 { get; internal set; } = null!;
|
||||
public (CustomizeData, CustomizeData) LegacyTattoo { get; internal set; }
|
||||
public IReadOnlyList<CustomizeData> FacePaints { get; internal init; } = null!;
|
||||
|
||||
|
||||
// Always Color Selector
|
||||
public IReadOnlyList<CustomizationData> SkinColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizationData> HairColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizationData> HighlightColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizationData> EyeColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizationData> TattooColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizationData> FacePaintColorsLight { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizationData> FacePaintColorsDark { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizationData> LipColorsLight { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizationData> LipColorsDark { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizeData> SkinColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizeData> HairColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizeData> HighlightColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizeData> EyeColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizeData> TattooColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizeData> FacePaintColorsLight { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizeData> FacePaintColorsDark { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizeData> LipColorsLight { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizeData> LipColorsDark { get; internal init; } = null!;
|
||||
|
||||
|
||||
public int DataByValue(CustomizationId id, CustomizationByteValue value, out CustomizationData? custom)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public int DataByValue(CustomizeIndex index, CustomizeValue value, out CustomizeData? custom, CustomizeValue face)
|
||||
{
|
||||
var type = id.ToType();
|
||||
custom = null;
|
||||
if (type is CharaMakeParams.MenuType.Percentage or CharaMakeParams.MenuType.ListSelector)
|
||||
var type = Types[(int)index];
|
||||
|
||||
int GetInteger(out CustomizeData? custom)
|
||||
{
|
||||
if (value < Count(id))
|
||||
if (value < Count(index))
|
||||
{
|
||||
custom = new CustomizationData(id, value, 0, value.Value);
|
||||
custom = new CustomizeData(index, value, 0, value.Value);
|
||||
return value.Value;
|
||||
}
|
||||
|
||||
custom = null;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Get(IEnumerable<CustomizationData> list, CustomizationByteValue v, ref CustomizationData? output)
|
||||
static int GetBool(CustomizeIndex index, CustomizeValue value, out CustomizeData? custom)
|
||||
{
|
||||
var (val, idx) = list.Cast<CustomizationData?>().WithIndex().FirstOrDefault(p => p.Item1!.Value.Value == v);
|
||||
if (value == CustomizeValue.Zero)
|
||||
{
|
||||
custom = new CustomizeData(index, CustomizeValue.Zero, 0, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
var (_, mask) = index.ToByteAndMask();
|
||||
if (value.Value == mask)
|
||||
{
|
||||
custom = new CustomizeData(index, new CustomizeValue(mask), 0, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
custom = null;
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int Invalid(out CustomizeData? custom)
|
||||
{
|
||||
custom = null;
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Get(IEnumerable<CustomizeData> list, CustomizeValue v, out CustomizeData? output)
|
||||
{
|
||||
var (val, idx) = list.Cast<CustomizeData?>().WithIndex().FirstOrDefault(p => p.Item1!.Value.Value == v);
|
||||
if (val == null)
|
||||
{
|
||||
output = null;
|
||||
return -1;
|
||||
}
|
||||
|
||||
output = val;
|
||||
return idx;
|
||||
}
|
||||
|
||||
return id switch
|
||||
return type switch
|
||||
{
|
||||
CustomizationId.SkinColor => Get(SkinColors, value, ref custom),
|
||||
CustomizationId.EyeColorL => Get(EyeColors, value, ref custom),
|
||||
CustomizationId.EyeColorR => Get(EyeColors, value, ref custom),
|
||||
CustomizationId.HairColor => Get(HairColors, value, ref custom),
|
||||
CustomizationId.HighlightColor => Get(HighlightColors, value, ref custom),
|
||||
CustomizationId.TattooColor => Get(TattooColors, value, ref custom),
|
||||
CustomizationId.LipColor => Get(LipColorsDark.Concat(LipColorsLight), value, ref custom),
|
||||
CustomizationId.FacePaintColor => Get(FacePaintColorsDark.Concat(FacePaintColorsLight), value, ref custom),
|
||||
|
||||
CustomizationId.Face => Get(Faces, HrothgarFaceHack(value), ref custom),
|
||||
CustomizationId.Hairstyle => Get(HairStyles, value, ref custom),
|
||||
CustomizationId.TailEarShape => Get(TailEarShapes, value, ref custom),
|
||||
CustomizationId.FacePaint => Get(FacePaints, value, ref custom),
|
||||
CustomizationId.FacialFeaturesTattoos => Get(FeaturesTattoos[0], value, ref custom),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(id), id, null),
|
||||
CharaMakeParams.MenuType.ListSelector => GetInteger(out custom),
|
||||
CharaMakeParams.MenuType.IconSelector => index switch
|
||||
{
|
||||
CustomizeIndex.Face => Get(Faces, HrothgarFaceHack(value), out custom),
|
||||
CustomizeIndex.Hairstyle => Get((face = HrothgarFaceHack(face)).Value < HairByFace.Count ? HairByFace[face.Value] : HairStyles, value, out custom),
|
||||
CustomizeIndex.TailShape => Get(TailEarShapes, value, out custom),
|
||||
CustomizeIndex.FacePaint => Get(FacePaints, value, out custom),
|
||||
CustomizeIndex.LipColor => Get(LipColorsDark, value, out custom),
|
||||
_ => Invalid(out custom),
|
||||
},
|
||||
CharaMakeParams.MenuType.ColorPicker => index switch
|
||||
{
|
||||
CustomizeIndex.SkinColor => Get(SkinColors, value, out custom),
|
||||
CustomizeIndex.EyeColorLeft => Get(EyeColors, value, out custom),
|
||||
CustomizeIndex.EyeColorRight => Get(EyeColors, value, out custom),
|
||||
CustomizeIndex.HairColor => Get(HairColors, value, out custom),
|
||||
CustomizeIndex.HighlightsColor => Get(HighlightColors, value, out custom),
|
||||
CustomizeIndex.TattooColor => Get(TattooColors, value, out custom),
|
||||
CustomizeIndex.LipColor => Get(LipColorsDark.Concat(LipColorsLight), value, out custom),
|
||||
CustomizeIndex.FacePaintColor => Get(FacePaintColorsDark.Concat(FacePaintColorsLight), value, out custom),
|
||||
_ => Invalid(out custom),
|
||||
},
|
||||
CharaMakeParams.MenuType.DoubleColorPicker => index switch
|
||||
{
|
||||
CustomizeIndex.LipColor => Get(LipColorsDark.Concat(LipColorsLight), value, out custom),
|
||||
CustomizeIndex.FacePaintColor => Get(FacePaintColorsDark.Concat(FacePaintColorsLight), value, out custom),
|
||||
_ => Invalid(out custom),
|
||||
},
|
||||
CharaMakeParams.MenuType.IconCheckmark => GetBool(index, value, out custom),
|
||||
CharaMakeParams.MenuType.Percentage => GetInteger(out custom),
|
||||
CharaMakeParams.MenuType.Checkmark => GetBool(index, value, out custom),
|
||||
_ => Invalid(out custom),
|
||||
};
|
||||
}
|
||||
|
||||
public CustomizationData Data(CustomizationId id, int idx)
|
||||
=> Data(id, idx, CustomizationByteValue.Zero);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public CustomizeData Data(CustomizeIndex index, int idx)
|
||||
=> Data(index, idx, CustomizeValue.Zero);
|
||||
|
||||
public CustomizationData Data(CustomizationId id, int idx, CustomizationByteValue face)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public CustomizeData Data(CustomizeIndex index, int idx, CustomizeValue face)
|
||||
{
|
||||
if (idx >= Count(id, face = HrothgarFaceHack(face)))
|
||||
if (idx >= Count(index, face = HrothgarFaceHack(face)))
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
switch (id.ToType())
|
||||
switch (Types[(int)index])
|
||||
{
|
||||
case CharaMakeParams.MenuType.Percentage: return new CustomizationData(id, (CustomizationByteValue)idx, 0, (ushort)idx);
|
||||
case CharaMakeParams.MenuType.ListSelector: return new CustomizationData(id, (CustomizationByteValue)idx, 0, (ushort)idx);
|
||||
case CharaMakeParams.MenuType.Percentage: return new CustomizeData(index, (CustomizeValue)idx, 0, (ushort)idx);
|
||||
case CharaMakeParams.MenuType.ListSelector: return new CustomizeData(index, (CustomizeValue)idx, 0, (ushort)idx);
|
||||
case CharaMakeParams.MenuType.Checkmark: return new CustomizeData(index, CustomizeValue.Bool(idx != 0), 0, (ushort)idx);
|
||||
}
|
||||
|
||||
return id switch
|
||||
return index switch
|
||||
{
|
||||
CustomizationId.Face => Faces[idx],
|
||||
CustomizationId.Hairstyle => face < HairByFace.Count ? HairByFace[face.Value][idx] : 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 CustomizationData(0, CustomizationByteValue.Zero),
|
||||
CustomizeIndex.Face => Faces[idx],
|
||||
CustomizeIndex.Hairstyle => face < HairByFace.Count ? HairByFace[face.Value][idx] : HairStyles[idx],
|
||||
CustomizeIndex.TailShape => TailEarShapes[idx],
|
||||
CustomizeIndex.FacePaint => FacePaints[idx],
|
||||
CustomizeIndex.FacialFeature1 => idx == 0 ? FacialFeature1[face.Value].Item1 : FacialFeature1[face.Value].Item2,
|
||||
CustomizeIndex.FacialFeature2 => idx == 0 ? FacialFeature2[face.Value].Item1 : FacialFeature2[face.Value].Item2,
|
||||
CustomizeIndex.FacialFeature3 => idx == 0 ? FacialFeature3[face.Value].Item1 : FacialFeature3[face.Value].Item2,
|
||||
CustomizeIndex.FacialFeature4 => idx == 0 ? FacialFeature4[face.Value].Item1 : FacialFeature4[face.Value].Item2,
|
||||
CustomizeIndex.FacialFeature5 => idx == 0 ? FacialFeature5[face.Value].Item1 : FacialFeature5[face.Value].Item2,
|
||||
CustomizeIndex.FacialFeature6 => idx == 0 ? FacialFeature6[face.Value].Item1 : FacialFeature6[face.Value].Item2,
|
||||
CustomizeIndex.FacialFeature7 => idx == 0 ? FacialFeature7[face.Value].Item1 : FacialFeature7[face.Value].Item2,
|
||||
CustomizeIndex.LegacyTattoo => idx == 0 ? LegacyTattoo.Item1 : LegacyTattoo.Item2,
|
||||
CustomizeIndex.SkinColor => SkinColors[idx],
|
||||
CustomizeIndex.EyeColorLeft => EyeColors[idx],
|
||||
CustomizeIndex.EyeColorRight => EyeColors[idx],
|
||||
CustomizeIndex.HairColor => HairColors[idx],
|
||||
CustomizeIndex.HighlightsColor => HighlightColors[idx],
|
||||
CustomizeIndex.TattooColor => TattooColors[idx],
|
||||
CustomizeIndex.LipColor => idx < 96 ? LipColorsDark[idx] : LipColorsLight[idx - 96],
|
||||
CustomizeIndex.FacePaintColor => idx < 96 ? FacePaintColorsDark[idx] : FacePaintColorsLight[idx - 96],
|
||||
_ => new CustomizeData(0, CustomizeValue.Zero),
|
||||
};
|
||||
}
|
||||
|
||||
public CharaMakeParams.MenuType Type(CustomizationId id)
|
||||
=> Types[(int)id];
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public CharaMakeParams.MenuType Type(CustomizeIndex index)
|
||||
=> Types[(int)index];
|
||||
|
||||
internal static IReadOnlyDictionary<CharaMakeParams.MenuType, CustomizationId[]> ComputeOrder(CustomizationSet set)
|
||||
internal static IReadOnlyDictionary<CharaMakeParams.MenuType, CustomizeIndex[]> ComputeOrder(CustomizationSet set)
|
||||
{
|
||||
var ret = (CustomizationId[])Enum.GetValues(typeof(CustomizationId));
|
||||
ret[(int)CustomizationId.TattooColor] = CustomizationId.EyeColorL;
|
||||
ret[(int)CustomizationId.EyeColorL] = CustomizationId.EyeColorR;
|
||||
ret[(int)CustomizationId.EyeColorR] = CustomizationId.TattooColor;
|
||||
var ret = Enum.GetValues<CustomizeIndex>().SkipLast(1).ToArray();
|
||||
ret[(int)CustomizeIndex.TattooColor] = CustomizeIndex.EyeColorLeft;
|
||||
ret[(int)CustomizeIndex.EyeColorLeft] = CustomizeIndex.EyeColorRight;
|
||||
ret[(int)CustomizeIndex.EyeColorRight] = CustomizeIndex.TattooColor;
|
||||
|
||||
var dict = ret.Skip(2).Where(set.IsAvailable).GroupBy(set.Type).ToDictionary(k => k.Key, k => k.ToArray());
|
||||
foreach (var type in Enum.GetValues<CharaMakeParams.MenuType>())
|
||||
dict.TryAdd(type, Array.Empty<CustomizationId>());
|
||||
dict.TryAdd(type, Array.Empty<CustomizeIndex>());
|
||||
return dict;
|
||||
}
|
||||
|
||||
public int Count(CustomizationId id)
|
||||
=> Count(id, CustomizationByteValue.Zero);
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public int Count(CustomizeIndex index)
|
||||
=> Count(index, CustomizeValue.Zero);
|
||||
|
||||
public int Count(CustomizationId id, CustomizationByteValue face)
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public int Count(CustomizeIndex index, CustomizeValue face)
|
||||
{
|
||||
if (!IsAvailable(id))
|
||||
if (!IsAvailable(index))
|
||||
return 0;
|
||||
|
||||
if (id.ToType() == CharaMakeParams.MenuType.Percentage)
|
||||
return 101;
|
||||
|
||||
return id switch
|
||||
return Type(index) switch
|
||||
{
|
||||
CustomizationId.Face => Faces.Count,
|
||||
CustomizationId.Hairstyle => (face = HrothgarFaceHack(face)) < HairByFace.Count ? HairByFace[face.Value].Count : 0,
|
||||
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),
|
||||
CharaMakeParams.MenuType.Percentage => 101,
|
||||
CharaMakeParams.MenuType.IconCheckmark => 2,
|
||||
CharaMakeParams.MenuType.Checkmark => 2,
|
||||
_ => index switch
|
||||
{
|
||||
CustomizeIndex.Face => Faces.Count,
|
||||
CustomizeIndex.Hairstyle => (face = HrothgarFaceHack(face)) < HairByFace.Count ? HairByFace[face.Value].Count : 0,
|
||||
CustomizeIndex.SkinColor => SkinColors.Count,
|
||||
CustomizeIndex.EyeColorRight => EyeColors.Count,
|
||||
CustomizeIndex.HairColor => HairColors.Count,
|
||||
CustomizeIndex.HighlightsColor => HighlightColors.Count,
|
||||
CustomizeIndex.TattooColor => TattooColors.Count,
|
||||
CustomizeIndex.Eyebrows => NumEyebrows,
|
||||
CustomizeIndex.EyeColorLeft => EyeColors.Count,
|
||||
CustomizeIndex.EyeShape => NumEyeShapes,
|
||||
CustomizeIndex.Nose => NumNoseShapes,
|
||||
CustomizeIndex.Jaw => NumJawShapes,
|
||||
CustomizeIndex.Mouth => NumMouthShapes,
|
||||
CustomizeIndex.LipColor => LipColorsLight.Count + LipColorsDark.Count,
|
||||
CustomizeIndex.TailShape => TailEarShapes.Count,
|
||||
CustomizeIndex.FacePaint => FacePaints.Count,
|
||||
CustomizeIndex.FacePaintColor => FacePaintColorsLight.Count + FacePaintColorsDark.Count,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index), index, null),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private CustomizationByteValue HrothgarFaceHack(CustomizationByteValue value)
|
||||
private CustomizeValue HrothgarFaceHack(CustomizeValue value)
|
||||
=> Race == Race.Hrothgar && value.Value is > 4 and < 9 ? value - 4 : value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,352 +1,87 @@
|
|||
using System;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
public record struct CustomizationByteValue(byte Value)
|
||||
{
|
||||
public static readonly CustomizationByteValue Zero = new(0);
|
||||
|
||||
public static explicit operator CustomizationByteValue(byte value)
|
||||
=> new(value);
|
||||
|
||||
public static CustomizationByteValue operator ++(CustomizationByteValue v)
|
||||
=> new(++v.Value);
|
||||
|
||||
public static CustomizationByteValue operator --(CustomizationByteValue v)
|
||||
=> new(--v.Value);
|
||||
|
||||
public static bool operator <(CustomizationByteValue v, int count)
|
||||
=> v.Value < count;
|
||||
|
||||
public static bool operator >(CustomizationByteValue v, int count)
|
||||
=> v.Value > count;
|
||||
|
||||
public static CustomizationByteValue operator +(CustomizationByteValue v, int rhs)
|
||||
=> new((byte)(v.Value + rhs));
|
||||
|
||||
public static CustomizationByteValue operator -(CustomizationByteValue v, int rhs)
|
||||
=> new((byte)(v.Value - rhs));
|
||||
|
||||
public override string ToString()
|
||||
=> Value.ToString();
|
||||
}
|
||||
|
||||
public unsafe struct Customize
|
||||
{
|
||||
public readonly CustomizeData* Data;
|
||||
public readonly Penumbra.GameData.Structs.CustomizeData* Data;
|
||||
|
||||
public Customize(CustomizeData* data)
|
||||
public Customize(Penumbra.GameData.Structs.CustomizeData* data)
|
||||
=> Data = data;
|
||||
|
||||
public Race Race
|
||||
{
|
||||
get => (Race)Data->Data[0];
|
||||
set => Data->Data[0] = (byte)value;
|
||||
get => (Race)Data->Get(CustomizeIndex.Race).Value;
|
||||
set => Data->Set(CustomizeIndex.Race, (CustomizeValue)(byte)value);
|
||||
}
|
||||
|
||||
// Skip Unknown Gender
|
||||
public Gender Gender
|
||||
{
|
||||
get => (Gender)(Data->Data[1] + 1);
|
||||
set => Data->Data[1] = (byte)(value - 1);
|
||||
get => (Gender)Data->Get(CustomizeIndex.Gender).Value + 1;
|
||||
set => Data->Set(CustomizeIndex.Gender, (CustomizeValue)(byte)value - 1);
|
||||
}
|
||||
|
||||
public CustomizationByteValue BodyType
|
||||
public CustomizeValue BodyType
|
||||
{
|
||||
get => (CustomizationByteValue)Data->Data[2];
|
||||
set => Data->Data[2] = value.Value;
|
||||
}
|
||||
|
||||
public CustomizationByteValue Height
|
||||
{
|
||||
get => (CustomizationByteValue)Data->Data[3];
|
||||
set => Data->Data[3] = value.Value;
|
||||
get => Data->Get(CustomizeIndex.BodyType);
|
||||
set => Data->Set(CustomizeIndex.BodyType, value);
|
||||
}
|
||||
|
||||
public SubRace Clan
|
||||
{
|
||||
get => (SubRace)Data->Data[4];
|
||||
set => Data->Data[4] = (byte)value;
|
||||
get => (SubRace)Data->Get(CustomizeIndex.Clan).Value;
|
||||
set => Data->Set(CustomizeIndex.Clan, (CustomizeValue)(byte)value);
|
||||
}
|
||||
|
||||
public CustomizationByteValue Face
|
||||
public CustomizeValue Face
|
||||
{
|
||||
get => (CustomizationByteValue)Data->Data[5];
|
||||
set => Data->Data[5] = value.Value;
|
||||
get => Data->Get(CustomizeIndex.Face);
|
||||
set => Data->Set(CustomizeIndex.Face, value);
|
||||
}
|
||||
|
||||
public CustomizationByteValue Hairstyle
|
||||
{
|
||||
get => (CustomizationByteValue)Data->Data[6];
|
||||
set => Data->Data[6] = value.Value;
|
||||
}
|
||||
|
||||
public bool HighlightsOn
|
||||
{
|
||||
get => Data->Data[7] >> 7 == 1;
|
||||
set => Data->Data[7] = (byte)(value ? Data->Data[7] | 0x80 : Data->Data[7] & 0x7F);
|
||||
}
|
||||
public static readonly Penumbra.GameData.Structs.CustomizeData Default = GenerateDefault();
|
||||
public static readonly Penumbra.GameData.Structs.CustomizeData Empty = new();
|
||||
|
||||
public CustomizationByteValue SkinColor
|
||||
{
|
||||
get => (CustomizationByteValue)Data->Data[8];
|
||||
set => Data->Data[8] = value.Value;
|
||||
}
|
||||
public CustomizeValue Get(CustomizeIndex index)
|
||||
=> Data->Get(index);
|
||||
|
||||
public CustomizationByteValue EyeColorRight
|
||||
{
|
||||
get => (CustomizationByteValue)Data->Data[9];
|
||||
set => Data->Data[9] = value.Value;
|
||||
}
|
||||
|
||||
public CustomizationByteValue HairColor
|
||||
{
|
||||
get => (CustomizationByteValue)Data->Data[10];
|
||||
set => Data->Data[10] = value.Value;
|
||||
}
|
||||
|
||||
public CustomizationByteValue HighlightsColor
|
||||
{
|
||||
get => (CustomizationByteValue)Data->Data[11];
|
||||
set => Data->Data[11] = value.Value;
|
||||
}
|
||||
|
||||
public readonly ref struct FacialFeatureStruct
|
||||
{
|
||||
private readonly byte* _bitfield;
|
||||
|
||||
public FacialFeatureStruct(byte* data)
|
||||
=> _bitfield = data;
|
||||
|
||||
public bool this[int idx]
|
||||
{
|
||||
get => (*_bitfield & (1 << idx)) != 0;
|
||||
set => Set(idx, value);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
=> *_bitfield = 0;
|
||||
|
||||
public void All()
|
||||
=> *_bitfield = 0xFF;
|
||||
|
||||
public void Set(int idx, bool value)
|
||||
=> *_bitfield = (byte)(value ? *_bitfield | (1 << idx) : *_bitfield & ~(1 << idx));
|
||||
}
|
||||
|
||||
public FacialFeatureStruct FacialFeatures
|
||||
=> new(Data->Data + 12);
|
||||
|
||||
public CustomizationByteValue TattooColor
|
||||
{
|
||||
get => (CustomizationByteValue)Data->Data[13];
|
||||
set => Data->Data[13] = value.Value;
|
||||
}
|
||||
|
||||
public CustomizationByteValue Eyebrows
|
||||
{
|
||||
get => (CustomizationByteValue)Data->Data[14];
|
||||
set => Data->Data[14] = value.Value;
|
||||
}
|
||||
|
||||
public CustomizationByteValue EyeColorLeft
|
||||
{
|
||||
get => (CustomizationByteValue)Data->Data[15];
|
||||
set => Data->Data[15] = value.Value;
|
||||
}
|
||||
|
||||
public CustomizationByteValue EyeShape
|
||||
{
|
||||
get => (CustomizationByteValue)(Data->Data[16] & 0x7F);
|
||||
set => Data->Data[16] = (byte)((value.Value & 0x7F) | (Data->Data[16] & 0x80));
|
||||
}
|
||||
|
||||
public bool SmallIris
|
||||
{
|
||||
get => Data->Data[16] >> 7 == 1;
|
||||
set => Data->Data[16] = (byte)(value ? Data->Data[16] | 0x80 : Data->Data[16] & 0x7F);
|
||||
}
|
||||
|
||||
public CustomizationByteValue Nose
|
||||
{
|
||||
get => (CustomizationByteValue)Data->Data[17];
|
||||
set => Data->Data[17] = value.Value;
|
||||
}
|
||||
|
||||
public CustomizationByteValue Jaw
|
||||
{
|
||||
get => (CustomizationByteValue)Data->Data[18];
|
||||
set => Data->Data[18] = value.Value;
|
||||
}
|
||||
|
||||
public CustomizationByteValue Mouth
|
||||
{
|
||||
get => (CustomizationByteValue)(Data->Data[19] & 0x7F);
|
||||
set => Data->Data[19] = (byte)((value.Value & 0x7F) | (Data->Data[19] & 0x80));
|
||||
}
|
||||
|
||||
public bool Lipstick
|
||||
{
|
||||
get => Data->Data[19] >> 7 == 1;
|
||||
set => Data->Data[19] = (byte)(value ? Data->Data[19] | 0x80 : Data->Data[19] & 0x7F);
|
||||
}
|
||||
|
||||
public CustomizationByteValue LipColor
|
||||
{
|
||||
get => (CustomizationByteValue)Data->Data[20];
|
||||
set => Data->Data[20] = value.Value;
|
||||
}
|
||||
|
||||
public CustomizationByteValue MuscleMass
|
||||
{
|
||||
get => (CustomizationByteValue)Data->Data[21];
|
||||
set => Data->Data[21] = value.Value;
|
||||
}
|
||||
|
||||
public CustomizationByteValue TailShape
|
||||
{
|
||||
get => (CustomizationByteValue)Data->Data[22];
|
||||
set => Data->Data[22] = value.Value;
|
||||
}
|
||||
|
||||
public CustomizationByteValue BustSize
|
||||
{
|
||||
get => (CustomizationByteValue)Data->Data[23];
|
||||
set => Data->Data[23] = value.Value;
|
||||
}
|
||||
|
||||
public CustomizationByteValue FacePaint
|
||||
{
|
||||
get => (CustomizationByteValue)(Data->Data[24] & 0x7F);
|
||||
set => Data->Data[24] = (byte)((value.Value & 0x7F) | (Data->Data[24] & 0x80));
|
||||
}
|
||||
|
||||
public bool FacePaintReversed
|
||||
{
|
||||
get => Data->Data[24] >> 7 == 1;
|
||||
set => Data->Data[24] = (byte)(value ? Data->Data[24] | 0x80 : Data->Data[24] & 0x7F);
|
||||
}
|
||||
|
||||
public CustomizationByteValue FacePaintColor
|
||||
{
|
||||
get => (CustomizationByteValue)Data->Data[25];
|
||||
set => Data->Data[25] = value.Value;
|
||||
}
|
||||
|
||||
public static readonly CustomizeData Default = GenerateDefault();
|
||||
public static readonly CustomizeData Empty = new();
|
||||
|
||||
public CustomizationByteValue Get(CustomizationId id)
|
||||
=> id switch
|
||||
{
|
||||
CustomizationId.Race => (CustomizationByteValue)(byte)Race,
|
||||
CustomizationId.Gender => (CustomizationByteValue)(byte)Gender,
|
||||
CustomizationId.BodyType => BodyType,
|
||||
CustomizationId.Height => Height,
|
||||
CustomizationId.Clan => (CustomizationByteValue)(byte)Clan,
|
||||
CustomizationId.Face => Face,
|
||||
CustomizationId.Hairstyle => Hairstyle,
|
||||
CustomizationId.HighlightsOnFlag => (CustomizationByteValue)Data->Data[7],
|
||||
CustomizationId.SkinColor => SkinColor,
|
||||
CustomizationId.EyeColorR => EyeColorRight,
|
||||
CustomizationId.HairColor => HairColor,
|
||||
CustomizationId.HighlightColor => HighlightsColor,
|
||||
CustomizationId.FacialFeaturesTattoos => (CustomizationByteValue)Data->Data[12],
|
||||
CustomizationId.TattooColor => TattooColor,
|
||||
CustomizationId.Eyebrows => Eyebrows,
|
||||
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 void Set(CustomizationId id, CustomizationByteValue value)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
// @formatter:off
|
||||
case CustomizationId.Race: Race = (Race)value.Value; break;
|
||||
case CustomizationId.Gender: Gender = (Gender)value.Value; break;
|
||||
case CustomizationId.BodyType: BodyType = value; break;
|
||||
case CustomizationId.Height: Height = value; break;
|
||||
case CustomizationId.Clan: Clan = (SubRace)value.Value; break;
|
||||
case CustomizationId.Face: Face = value; break;
|
||||
case CustomizationId.Hairstyle: Hairstyle = value; break;
|
||||
case CustomizationId.HighlightsOnFlag: HighlightsOn = (value.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: Data->Data[12] = value.Value; break;
|
||||
case CustomizationId.TattooColor: TattooColor = value; break;
|
||||
case CustomizationId.Eyebrows: Eyebrows = 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);
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
public void Set(CustomizeIndex flag, CustomizeValue index)
|
||||
=> Data->Set(flag, index);
|
||||
|
||||
public bool Equals(Customize other)
|
||||
=> CustomizeData.Equals(Data, other.Data);
|
||||
=> Penumbra.GameData.Structs.CustomizeData.Equals(Data, other.Data);
|
||||
|
||||
public CustomizationByteValue this[CustomizationId id]
|
||||
public CustomizeValue this[CustomizeIndex index]
|
||||
{
|
||||
get => Get(id);
|
||||
set => Set(id, value);
|
||||
get => Get(index);
|
||||
set => Set(index, value);
|
||||
}
|
||||
|
||||
private static CustomizeData GenerateDefault()
|
||||
private static Penumbra.GameData.Structs.CustomizeData GenerateDefault()
|
||||
{
|
||||
var ret = new CustomizeData();
|
||||
var customize = new Customize(&ret)
|
||||
{
|
||||
Race = Race.Hyur,
|
||||
Gender = Gender.Male,
|
||||
BodyType = (CustomizationByteValue)1,
|
||||
Height = (CustomizationByteValue)50,
|
||||
Clan = SubRace.Midlander,
|
||||
Face = (CustomizationByteValue)1,
|
||||
Hairstyle = (CustomizationByteValue)1,
|
||||
HighlightsOn = false,
|
||||
SkinColor = (CustomizationByteValue)1,
|
||||
EyeColorRight = (CustomizationByteValue)1,
|
||||
HighlightsColor = (CustomizationByteValue)1,
|
||||
TattooColor = (CustomizationByteValue)1,
|
||||
Eyebrows = (CustomizationByteValue)1,
|
||||
EyeColorLeft = (CustomizationByteValue)1,
|
||||
EyeShape = (CustomizationByteValue)1,
|
||||
Nose = (CustomizationByteValue)1,
|
||||
Jaw = (CustomizationByteValue)1,
|
||||
Mouth = (CustomizationByteValue)1,
|
||||
LipColor = (CustomizationByteValue)1,
|
||||
MuscleMass = (CustomizationByteValue)50,
|
||||
TailShape = (CustomizationByteValue)1,
|
||||
BustSize = (CustomizationByteValue)50,
|
||||
FacePaint = (CustomizationByteValue)1,
|
||||
FacePaintColor = (CustomizationByteValue)1,
|
||||
};
|
||||
customize.FacialFeatures.Clear();
|
||||
|
||||
var ret = new Penumbra.GameData.Structs.CustomizeData();
|
||||
ret.Set(CustomizeIndex.BodyType, (CustomizeValue)1);
|
||||
ret.Set(CustomizeIndex.Height, (CustomizeValue)50);
|
||||
ret.Set(CustomizeIndex.Face, (CustomizeValue)1);
|
||||
ret.Set(CustomizeIndex.Hairstyle, (CustomizeValue)1);
|
||||
ret.Set(CustomizeIndex.SkinColor, (CustomizeValue)1);
|
||||
ret.Set(CustomizeIndex.EyeColorRight, (CustomizeValue)1);
|
||||
ret.Set(CustomizeIndex.HighlightsColor, (CustomizeValue)1);
|
||||
ret.Set(CustomizeIndex.TattooColor, (CustomizeValue)1);
|
||||
ret.Set(CustomizeIndex.Eyebrows, (CustomizeValue)1);
|
||||
ret.Set(CustomizeIndex.EyeColorLeft, (CustomizeValue)1);
|
||||
ret.Set(CustomizeIndex.EyeShape, (CustomizeValue)1);
|
||||
ret.Set(CustomizeIndex.Nose, (CustomizeValue)1);
|
||||
ret.Set(CustomizeIndex.Jaw, (CustomizeValue)1);
|
||||
ret.Set(CustomizeIndex.Mouth, (CustomizeValue)1);
|
||||
ret.Set(CustomizeIndex.LipColor, (CustomizeValue)1);
|
||||
ret.Set(CustomizeIndex.MuscleMass, (CustomizeValue)50);
|
||||
ret.Set(CustomizeIndex.TailShape, (CustomizeValue)1);
|
||||
ret.Set(CustomizeIndex.BustSize, (CustomizeValue)50);
|
||||
ret.Set(CustomizeIndex.FacePaint, (CustomizeValue)1);
|
||||
ret.Set(CustomizeIndex.FacePaintColor, (CustomizeValue)1);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -355,4 +90,10 @@ public unsafe struct Customize
|
|||
|
||||
public void Write(IntPtr target)
|
||||
=> Data->Write((void*)target);
|
||||
|
||||
public bool LoadBase64(string data)
|
||||
=> Data->LoadBase64(data);
|
||||
|
||||
public string WriteBase64()
|
||||
=> Data->WriteBase64();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,13 +5,13 @@ namespace Glamourer.Customization;
|
|||
// Any customization value can be represented in 8 bytes by its ID,
|
||||
// a byte value, an optional value-id and an optional icon or color.
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public readonly struct CustomizationData
|
||||
public readonly struct CustomizeData
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public readonly CustomizationId Id;
|
||||
public readonly CustomizeIndex Index;
|
||||
|
||||
[FieldOffset(1)]
|
||||
public readonly CustomizationByteValue Value;
|
||||
public readonly CustomizeValue Value;
|
||||
|
||||
[FieldOffset(2)]
|
||||
public readonly ushort CustomizeId;
|
||||
|
|
@ -22,9 +22,9 @@ public readonly struct CustomizationData
|
|||
[FieldOffset(4)]
|
||||
public readonly uint Color;
|
||||
|
||||
public CustomizationData(CustomizationId id, CustomizationByteValue value, uint data = 0, ushort customizeId = 0)
|
||||
public CustomizeData(CustomizeIndex index, CustomizeValue value, uint data = 0, ushort customizeId = 0)
|
||||
{
|
||||
Id = id;
|
||||
Index = index;
|
||||
Value = value;
|
||||
IconId = data;
|
||||
Color = data;
|
||||
92
Glamourer.GameData/Customization/CustomizeFlag.cs
Normal file
92
Glamourer.GameData/Customization/CustomizeFlag.cs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
[Flags]
|
||||
public enum CustomizeFlag : ulong
|
||||
{
|
||||
Invalid = 0,
|
||||
Race = 1ul << CustomizeIndex.Race,
|
||||
Gender = 1ul << CustomizeIndex.Gender,
|
||||
BodyType = 1ul << CustomizeIndex.BodyType,
|
||||
Height = 1ul << CustomizeIndex.Height,
|
||||
Clan = 1ul << CustomizeIndex.Clan,
|
||||
Face = 1ul << CustomizeIndex.Face,
|
||||
Hairstyle = 1ul << CustomizeIndex.Hairstyle,
|
||||
Highlights = 1ul << CustomizeIndex.Highlights,
|
||||
SkinColor = 1ul << CustomizeIndex.SkinColor,
|
||||
EyeColorRight = 1ul << CustomizeIndex.EyeColorRight,
|
||||
HairColor = 1ul << CustomizeIndex.HairColor,
|
||||
HighlightsColor = 1ul << CustomizeIndex.HighlightsColor,
|
||||
FacialFeature1 = 1ul << CustomizeIndex.FacialFeature1,
|
||||
FacialFeature2 = 1ul << CustomizeIndex.FacialFeature2,
|
||||
FacialFeature3 = 1ul << CustomizeIndex.FacialFeature3,
|
||||
FacialFeature4 = 1ul << CustomizeIndex.FacialFeature4,
|
||||
FacialFeature5 = 1ul << CustomizeIndex.FacialFeature5,
|
||||
FacialFeature6 = 1ul << CustomizeIndex.FacialFeature6,
|
||||
FacialFeature7 = 1ul << CustomizeIndex.FacialFeature7,
|
||||
LegacyTattoo = 1ul << CustomizeIndex.LegacyTattoo,
|
||||
TattooColor = 1ul << CustomizeIndex.TattooColor,
|
||||
Eyebrows = 1ul << CustomizeIndex.Eyebrows,
|
||||
EyeColorLeft = 1ul << CustomizeIndex.EyeColorLeft,
|
||||
EyeShape = 1ul << CustomizeIndex.EyeShape,
|
||||
SmallIris = 1ul << CustomizeIndex.SmallIris,
|
||||
Nose = 1ul << CustomizeIndex.Nose,
|
||||
Jaw = 1ul << CustomizeIndex.Jaw,
|
||||
Mouth = 1ul << CustomizeIndex.Mouth,
|
||||
Lipstick = 1ul << CustomizeIndex.Lipstick,
|
||||
LipColor = 1ul << CustomizeIndex.LipColor,
|
||||
MuscleMass = 1ul << CustomizeIndex.MuscleMass,
|
||||
TailShape = 1ul << CustomizeIndex.TailShape,
|
||||
BustSize = 1ul << CustomizeIndex.BustSize,
|
||||
FacePaint = 1ul << CustomizeIndex.FacePaint,
|
||||
FacePaintReversed = 1ul << CustomizeIndex.FacePaintReversed,
|
||||
FacePaintColor = 1ul << CustomizeIndex.FacePaintColor,
|
||||
}
|
||||
|
||||
public static class CustomizeFlagExtensions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static CustomizeIndex ToIndex(this CustomizeFlag flag)
|
||||
=> flag switch
|
||||
{
|
||||
CustomizeFlag.Race => CustomizeIndex.Race,
|
||||
CustomizeFlag.Gender => CustomizeIndex.Gender,
|
||||
CustomizeFlag.BodyType => CustomizeIndex.BodyType,
|
||||
CustomizeFlag.Height => CustomizeIndex.Height,
|
||||
CustomizeFlag.Clan => CustomizeIndex.Clan,
|
||||
CustomizeFlag.Face => CustomizeIndex.Face,
|
||||
CustomizeFlag.Hairstyle => CustomizeIndex.Hairstyle,
|
||||
CustomizeFlag.Highlights => CustomizeIndex.Highlights,
|
||||
CustomizeFlag.SkinColor => CustomizeIndex.SkinColor,
|
||||
CustomizeFlag.EyeColorRight => CustomizeIndex.EyeColorRight,
|
||||
CustomizeFlag.HairColor => CustomizeIndex.HairColor,
|
||||
CustomizeFlag.HighlightsColor => CustomizeIndex.HighlightsColor,
|
||||
CustomizeFlag.FacialFeature1 => CustomizeIndex.FacialFeature1,
|
||||
CustomizeFlag.FacialFeature2 => CustomizeIndex.FacialFeature2,
|
||||
CustomizeFlag.FacialFeature3 => CustomizeIndex.FacialFeature3,
|
||||
CustomizeFlag.FacialFeature4 => CustomizeIndex.FacialFeature4,
|
||||
CustomizeFlag.FacialFeature5 => CustomizeIndex.FacialFeature5,
|
||||
CustomizeFlag.FacialFeature6 => CustomizeIndex.FacialFeature6,
|
||||
CustomizeFlag.FacialFeature7 => CustomizeIndex.FacialFeature7,
|
||||
CustomizeFlag.LegacyTattoo => CustomizeIndex.LegacyTattoo,
|
||||
CustomizeFlag.TattooColor => CustomizeIndex.TattooColor,
|
||||
CustomizeFlag.Eyebrows => CustomizeIndex.Eyebrows,
|
||||
CustomizeFlag.EyeColorLeft => CustomizeIndex.EyeColorLeft,
|
||||
CustomizeFlag.EyeShape => CustomizeIndex.EyeShape,
|
||||
CustomizeFlag.SmallIris => CustomizeIndex.SmallIris,
|
||||
CustomizeFlag.Nose => CustomizeIndex.Nose,
|
||||
CustomizeFlag.Jaw => CustomizeIndex.Jaw,
|
||||
CustomizeFlag.Mouth => CustomizeIndex.Mouth,
|
||||
CustomizeFlag.Lipstick => CustomizeIndex.Lipstick,
|
||||
CustomizeFlag.LipColor => CustomizeIndex.LipColor,
|
||||
CustomizeFlag.MuscleMass => CustomizeIndex.MuscleMass,
|
||||
CustomizeFlag.TailShape => CustomizeIndex.TailShape,
|
||||
CustomizeFlag.BustSize => CustomizeIndex.BustSize,
|
||||
CustomizeFlag.FacePaint => CustomizeIndex.FacePaint,
|
||||
CustomizeFlag.FacePaintReversed => CustomizeIndex.FacePaintReversed,
|
||||
CustomizeFlag.FacePaintColor => CustomizeIndex.FacePaintColor,
|
||||
_ => (CustomizeIndex) byte.MaxValue,
|
||||
};
|
||||
}
|
||||
173
Glamourer.GameData/Customization/CustomizeIndex.cs
Normal file
173
Glamourer.GameData/Customization/CustomizeIndex.cs
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
public enum CustomizeIndex : byte
|
||||
{
|
||||
Race,
|
||||
Gender,
|
||||
BodyType,
|
||||
Height,
|
||||
Clan,
|
||||
Face,
|
||||
Hairstyle,
|
||||
Highlights,
|
||||
SkinColor,
|
||||
EyeColorRight,
|
||||
HairColor,
|
||||
HighlightsColor,
|
||||
FacialFeature1,
|
||||
FacialFeature2,
|
||||
FacialFeature3,
|
||||
FacialFeature4,
|
||||
FacialFeature5,
|
||||
FacialFeature6,
|
||||
FacialFeature7,
|
||||
LegacyTattoo,
|
||||
TattooColor,
|
||||
Eyebrows,
|
||||
EyeColorLeft,
|
||||
EyeShape,
|
||||
SmallIris,
|
||||
Nose,
|
||||
Jaw,
|
||||
Mouth,
|
||||
Lipstick,
|
||||
LipColor,
|
||||
MuscleMass,
|
||||
TailShape,
|
||||
BustSize,
|
||||
FacePaint,
|
||||
FacePaintReversed,
|
||||
FacePaintColor,
|
||||
}
|
||||
|
||||
public static class CustomizationExtensions
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static (int ByteIdx, byte Mask) ToByteAndMask(this CustomizeIndex index)
|
||||
=> index switch
|
||||
{
|
||||
CustomizeIndex.Race => (0, 0xFF),
|
||||
CustomizeIndex.Gender => (1, 0xFF),
|
||||
CustomizeIndex.BodyType => (2, 0xFF),
|
||||
CustomizeIndex.Height => (3, 0xFF),
|
||||
CustomizeIndex.Clan => (4, 0xFF),
|
||||
CustomizeIndex.Face => (5, 0xFF),
|
||||
CustomizeIndex.Hairstyle => (6, 0xFF),
|
||||
CustomizeIndex.Highlights => (7, 0xFF),
|
||||
CustomizeIndex.SkinColor => (8, 0xFF),
|
||||
CustomizeIndex.EyeColorRight => (9, 0xFF),
|
||||
CustomizeIndex.HairColor => (10, 0xFF),
|
||||
CustomizeIndex.HighlightsColor => (11, 0xFF),
|
||||
CustomizeIndex.FacialFeature1 => (12, 0x01),
|
||||
CustomizeIndex.FacialFeature2 => (12, 0x02),
|
||||
CustomizeIndex.FacialFeature3 => (12, 0x04),
|
||||
CustomizeIndex.FacialFeature4 => (12, 0x08),
|
||||
CustomizeIndex.FacialFeature5 => (12, 0x10),
|
||||
CustomizeIndex.FacialFeature6 => (12, 0x20),
|
||||
CustomizeIndex.FacialFeature7 => (12, 0x40),
|
||||
CustomizeIndex.LegacyTattoo => (12, 0x80),
|
||||
CustomizeIndex.TattooColor => (13, 0xFF),
|
||||
CustomizeIndex.Eyebrows => (14, 0xFF),
|
||||
CustomizeIndex.EyeColorLeft => (15, 0xFF),
|
||||
CustomizeIndex.EyeShape => (16, 0x7F),
|
||||
CustomizeIndex.SmallIris => (16, 0x80),
|
||||
CustomizeIndex.Nose => (17, 0xFF),
|
||||
CustomizeIndex.Jaw => (18, 0xFF),
|
||||
CustomizeIndex.Mouth => (19, 0x7F),
|
||||
CustomizeIndex.Lipstick => (19, 0x80),
|
||||
CustomizeIndex.LipColor => (20, 0xFF),
|
||||
CustomizeIndex.MuscleMass => (21, 0xFF),
|
||||
CustomizeIndex.TailShape => (22, 0xFF),
|
||||
CustomizeIndex.BustSize => (23, 0xFF),
|
||||
CustomizeIndex.FacePaint => (24, 0x7F),
|
||||
CustomizeIndex.FacePaintReversed => (24, 0x80),
|
||||
CustomizeIndex.FacePaintColor => (25, 0xFF),
|
||||
_ => (0, 0x00),
|
||||
};
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static CustomizeFlag ToFlag(this CustomizeIndex index)
|
||||
=> (CustomizeFlag)(1ul << (int)index);
|
||||
|
||||
public static string ToDefaultName(this CustomizeIndex customizeIndex)
|
||||
=> customizeIndex switch
|
||||
{
|
||||
CustomizeIndex.Race => "Race",
|
||||
CustomizeIndex.Gender => "Gender",
|
||||
CustomizeIndex.BodyType => "Body Type",
|
||||
CustomizeIndex.Height => "Height",
|
||||
CustomizeIndex.Clan => "Clan",
|
||||
CustomizeIndex.Face => "Head Style",
|
||||
CustomizeIndex.Hairstyle => "Hair Style",
|
||||
CustomizeIndex.Highlights => "Highlights",
|
||||
CustomizeIndex.SkinColor => "Skin Color",
|
||||
CustomizeIndex.EyeColorRight => "Right Eye Color",
|
||||
CustomizeIndex.HairColor => "Hair Color",
|
||||
CustomizeIndex.HighlightsColor => "Highlights Color",
|
||||
CustomizeIndex.TattooColor => "Tattoo Color",
|
||||
CustomizeIndex.Eyebrows => "Eyebrow Style",
|
||||
CustomizeIndex.EyeColorLeft => "Left Eye Color",
|
||||
CustomizeIndex.EyeShape => "Eye Shape",
|
||||
CustomizeIndex.Nose => "Nose Style",
|
||||
CustomizeIndex.Jaw => "Jaw Style",
|
||||
CustomizeIndex.Mouth => "Mouth Style",
|
||||
CustomizeIndex.MuscleMass => "Muscle Tone",
|
||||
CustomizeIndex.TailShape => "Tail Shape",
|
||||
CustomizeIndex.BustSize => "Bust Size",
|
||||
CustomizeIndex.FacePaint => "Face Paint",
|
||||
CustomizeIndex.FacePaintColor => "Face Paint Color",
|
||||
CustomizeIndex.LipColor => "Lip Color",
|
||||
CustomizeIndex.FacialFeature1 => "Facial Feature 1",
|
||||
CustomizeIndex.FacialFeature2 => "Facial Feature 2",
|
||||
CustomizeIndex.FacialFeature3 => "Facial Feature 3",
|
||||
CustomizeIndex.FacialFeature4 => "Facial Feature 4",
|
||||
CustomizeIndex.FacialFeature5 => "Facial Feature 5",
|
||||
CustomizeIndex.FacialFeature6 => "Facial Feature 6",
|
||||
CustomizeIndex.FacialFeature7 => "Facial Feature 7",
|
||||
CustomizeIndex.LegacyTattoo => "Legacy Tattoo",
|
||||
CustomizeIndex.SmallIris => "Small Iris",
|
||||
CustomizeIndex.Lipstick => "Enable Lipstick",
|
||||
CustomizeIndex.FacePaintReversed => "Reverse Face Paint",
|
||||
_ => string.Empty,
|
||||
};
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static unsafe CustomizeValue Get(this in Penumbra.GameData.Structs.CustomizeData data, CustomizeIndex index)
|
||||
{
|
||||
var (offset, mask) = index.ToByteAndMask();
|
||||
return (CustomizeValue)(data.Data[offset] & mask);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static unsafe bool Set(this ref Penumbra.GameData.Structs.CustomizeData data, CustomizeIndex index, CustomizeValue value)
|
||||
{
|
||||
var (offset, mask) = index.ToByteAndMask();
|
||||
return mask != 0xFF
|
||||
? SetIfDifferentMasked(ref data.Data[offset], value, mask)
|
||||
: SetIfDifferent(ref data.Data[offset], value);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static bool SetIfDifferentMasked(ref byte oldValue, CustomizeValue newValue, byte mask)
|
||||
{
|
||||
var tmp = (byte)(newValue.Value & mask);
|
||||
tmp = (byte)(tmp | (oldValue & ~mask));
|
||||
if (oldValue == tmp)
|
||||
return false;
|
||||
|
||||
oldValue = tmp;
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static bool SetIfDifferent(ref byte oldValue, CustomizeValue newValue)
|
||||
{
|
||||
if (oldValue == newValue.Value)
|
||||
return false;
|
||||
|
||||
oldValue = newValue.Value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
34
Glamourer.GameData/Customization/CustomizeValue.cs
Normal file
34
Glamourer.GameData/Customization/CustomizeValue.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
namespace Glamourer.Customization;
|
||||
|
||||
public record struct CustomizeValue(byte Value)
|
||||
{
|
||||
public static readonly CustomizeValue Zero = new(0);
|
||||
public static readonly CustomizeValue Max = new(0xFF);
|
||||
|
||||
public static CustomizeValue Bool(bool b)
|
||||
=> b ? Max : Zero;
|
||||
|
||||
public static explicit operator CustomizeValue(byte value)
|
||||
=> new(value);
|
||||
|
||||
public static CustomizeValue operator ++(CustomizeValue v)
|
||||
=> new(++v.Value);
|
||||
|
||||
public static CustomizeValue operator --(CustomizeValue v)
|
||||
=> new(--v.Value);
|
||||
|
||||
public static bool operator <(CustomizeValue v, int count)
|
||||
=> v.Value < count;
|
||||
|
||||
public static bool operator >(CustomizeValue v, int count)
|
||||
=> v.Value > count;
|
||||
|
||||
public static CustomizeValue operator +(CustomizeValue v, int rhs)
|
||||
=> new((byte)(v.Value + rhs));
|
||||
|
||||
public static CustomizeValue operator -(CustomizeValue v, int rhs)
|
||||
=> new((byte)(v.Value - rhs));
|
||||
|
||||
public override string ToString()
|
||||
=> Value.ToString();
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility;
|
||||
|
|
@ -187,6 +188,7 @@ public class RestrictedGear
|
|||
_femaleToMale.TryAdd(fModelIdSlot, mModelIdSlot);
|
||||
}
|
||||
|
||||
// @formatter:off
|
||||
// Add all currently existing and known gender restricted items.
|
||||
private void AddKnown()
|
||||
{
|
||||
|
|
@ -431,4 +433,5 @@ public class RestrictedGear
|
|||
0x0102E8,
|
||||
0x010245,
|
||||
};
|
||||
// @Formatter:on
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue