mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +01:00
Do the Reworkings
This commit is contained in:
parent
a67cd8ad89
commit
e3a58340b3
23 changed files with 1067 additions and 943 deletions
|
|
@ -1,33 +0,0 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,314 +1,33 @@
|
|||
using System;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
public unsafe struct Customize
|
||||
// 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 CustomizeData* Data;
|
||||
[FieldOffset(0)]
|
||||
public readonly CustomizationId Id;
|
||||
|
||||
public Customize(CustomizeData* data)
|
||||
=> Data = data;
|
||||
[FieldOffset(1)]
|
||||
public readonly CustomizationByteValue Value;
|
||||
|
||||
public Race Race
|
||||
[FieldOffset(2)]
|
||||
public readonly ushort CustomizeId;
|
||||
|
||||
[FieldOffset(4)]
|
||||
public readonly uint IconId;
|
||||
|
||||
[FieldOffset(4)]
|
||||
public readonly uint Color;
|
||||
|
||||
public CustomizationData(CustomizationId id, CustomizationByteValue value, uint data = 0, ushort customizeId = 0)
|
||||
{
|
||||
get => (Race)Data->Data[0];
|
||||
set => Data->Data[0] = (byte)value;
|
||||
Id = id;
|
||||
Value = value;
|
||||
IconId = data;
|
||||
Color = data;
|
||||
CustomizeId = customizeId;
|
||||
}
|
||||
|
||||
// Skip Unknown Gender
|
||||
public Gender Gender
|
||||
{
|
||||
get => (Gender)(Data->Data[1] + 1);
|
||||
set => Data->Data[1] = (byte)(value - 1);
|
||||
}
|
||||
|
||||
public byte BodyType
|
||||
{
|
||||
get => Data->Data[2];
|
||||
set => Data->Data[2] = value;
|
||||
}
|
||||
|
||||
public byte Height
|
||||
{
|
||||
get => Data->Data[3];
|
||||
set => Data->Data[3] = value;
|
||||
}
|
||||
|
||||
public SubRace Clan
|
||||
{
|
||||
get => (SubRace)Data->Data[4];
|
||||
set => Data->Data[4] = (byte)value;
|
||||
}
|
||||
|
||||
public byte Face
|
||||
{
|
||||
get => Data->Data[5];
|
||||
set => Data->Data[5] = value;
|
||||
}
|
||||
|
||||
public byte Hairstyle
|
||||
{
|
||||
get => Data->Data[6];
|
||||
set => Data->Data[6] = 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 byte SkinColor
|
||||
{
|
||||
get => Data->Data[8];
|
||||
set => Data->Data[8] = value;
|
||||
}
|
||||
|
||||
public byte EyeColorRight
|
||||
{
|
||||
get => Data->Data[9];
|
||||
set => Data->Data[9] = value;
|
||||
}
|
||||
|
||||
public byte HairColor
|
||||
{
|
||||
get => Data->Data[10];
|
||||
set => Data->Data[10] = value;
|
||||
}
|
||||
|
||||
public byte HighlightsColor
|
||||
{
|
||||
get => Data->Data[11];
|
||||
set => Data->Data[11] = 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 byte TattooColor
|
||||
{
|
||||
get => Data->Data[13];
|
||||
set => Data->Data[13] = value;
|
||||
}
|
||||
|
||||
public byte Eyebrows
|
||||
{
|
||||
get => Data->Data[14];
|
||||
set => Data->Data[14] = value;
|
||||
}
|
||||
|
||||
public byte EyeColorLeft
|
||||
{
|
||||
get => Data->Data[15];
|
||||
set => Data->Data[15] = value;
|
||||
}
|
||||
|
||||
public byte EyeShape
|
||||
{
|
||||
get => (byte)(Data->Data[16] & 0x7F);
|
||||
set => Data->Data[16] = (byte)((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 byte Nose
|
||||
{
|
||||
get => Data->Data[17];
|
||||
set => Data->Data[17] = value;
|
||||
}
|
||||
|
||||
public byte Jaw
|
||||
{
|
||||
get => Data->Data[18];
|
||||
set => Data->Data[18] = value;
|
||||
}
|
||||
|
||||
public byte Mouth
|
||||
{
|
||||
get => (byte)(Data->Data[19] & 0x7F);
|
||||
set => Data->Data[19] = (byte)((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 ref byte LipColor
|
||||
=> ref Data->Data[20];
|
||||
|
||||
public ref byte MuscleMass
|
||||
=> ref Data->Data[21];
|
||||
|
||||
public ref byte TailShape
|
||||
=> ref Data->Data[22];
|
||||
|
||||
public ref byte BustSize
|
||||
=> ref Data->Data[23];
|
||||
|
||||
public byte FacePaint
|
||||
{
|
||||
get => (byte)(Data->Data[24] & 0x7F);
|
||||
set => Data->Data[24] = (byte)((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 ref byte FacePaintColor
|
||||
=> ref Data->Data[25];
|
||||
|
||||
public static readonly CustomizeData Default = GenerateDefault();
|
||||
public static readonly CustomizeData Empty = new();
|
||||
|
||||
public byte Get(CustomizationId id)
|
||||
=> id switch
|
||||
{
|
||||
CustomizationId.Race => (byte)Race,
|
||||
CustomizationId.Gender => (byte)Gender,
|
||||
CustomizationId.BodyType => BodyType,
|
||||
CustomizationId.Height => Height,
|
||||
CustomizationId.Clan => (byte)Clan,
|
||||
CustomizationId.Face => Face,
|
||||
CustomizationId.Hairstyle => Hairstyle,
|
||||
CustomizationId.HighlightsOnFlag => Data->Data[7],
|
||||
CustomizationId.SkinColor => SkinColor,
|
||||
CustomizationId.EyeColorR => EyeColorRight,
|
||||
CustomizationId.HairColor => HairColor,
|
||||
CustomizationId.HighlightColor => HighlightsColor,
|
||||
CustomizationId.FacialFeaturesTattoos => 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, byte value)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
// @formatter:off
|
||||
case CustomizationId.Race: Race = (Race)value; break;
|
||||
case CustomizationId.Gender: Gender = (Gender)value; break;
|
||||
case CustomizationId.BodyType: BodyType = value; break;
|
||||
case CustomizationId.Height: Height = value; break;
|
||||
case CustomizationId.Clan: Clan = (SubRace)value; break;
|
||||
case CustomizationId.Face: Face = value; break;
|
||||
case CustomizationId.Hairstyle: Hairstyle = value; break;
|
||||
case CustomizationId.HighlightsOnFlag: HighlightsOn = (value & 128) == 128; break;
|
||||
case CustomizationId.SkinColor: SkinColor = value; break;
|
||||
case CustomizationId.EyeColorR: EyeColorRight = value; break;
|
||||
case CustomizationId.HairColor: HairColor = value; break;
|
||||
case CustomizationId.HighlightColor: HighlightsColor = value; break;
|
||||
case CustomizationId.FacialFeaturesTattoos: Data->Data[12] = 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 bool Equals(Customize other)
|
||||
=> CustomizeData.Equals(Data, other.Data);
|
||||
|
||||
public byte this[CustomizationId id]
|
||||
{
|
||||
get => Get(id);
|
||||
set => Set(id, value);
|
||||
}
|
||||
|
||||
private static CustomizeData GenerateDefault()
|
||||
{
|
||||
var ret = new CustomizeData();
|
||||
var customize = new Customize(&ret)
|
||||
{
|
||||
Race = Race.Hyur,
|
||||
Gender = Gender.Male,
|
||||
BodyType = 1,
|
||||
Height = 50,
|
||||
Clan = SubRace.Midlander,
|
||||
Face = 1,
|
||||
Hairstyle = 1,
|
||||
HighlightsOn = false,
|
||||
SkinColor = 1,
|
||||
EyeColorRight = 1,
|
||||
HighlightsColor = 1,
|
||||
TattooColor = 1,
|
||||
Eyebrows = 1,
|
||||
EyeColorLeft = 1,
|
||||
EyeShape = 1,
|
||||
Nose = 1,
|
||||
Jaw = 1,
|
||||
Mouth = 1,
|
||||
LipColor = 1,
|
||||
MuscleMass = 50,
|
||||
TailShape = 1,
|
||||
BustSize = 50,
|
||||
FacePaint = 1,
|
||||
FacePaintColor = 1,
|
||||
};
|
||||
customize.FacialFeatures.Clear();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void Load(Customize other)
|
||||
=> Data->Read(other.Data);
|
||||
|
||||
public void Write(IntPtr target)
|
||||
=> Data->Write((void*)target);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,7 +149,7 @@ public partial class CustomizationOptions
|
|||
HighlightColors = _highlightPicker,
|
||||
TattooColors = _tattooColorPicker,
|
||||
LipColorsDark = hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark,
|
||||
LipColorsLight = hrothgar ? Array.Empty<Customization>() : _lipColorPickerLight,
|
||||
LipColorsLight = hrothgar ? Array.Empty<CustomizationData>() : _lipColorPickerLight,
|
||||
FacePaintColorsDark = _facePaintColorPickerDark,
|
||||
FacePaintColorsLight = _facePaintColorPickerLight,
|
||||
Faces = GetFaces(row),
|
||||
|
|
@ -203,19 +203,19 @@ public partial class CustomizationOptions
|
|||
private readonly CmpFile _cmpFile;
|
||||
|
||||
// Those values are shared between all races.
|
||||
private readonly Customization[] _highlightPicker;
|
||||
private readonly Customization[] _eyeColorPicker;
|
||||
private readonly Customization[] _facePaintColorPickerDark;
|
||||
private readonly Customization[] _facePaintColorPickerLight;
|
||||
private readonly Customization[] _lipColorPickerDark;
|
||||
private readonly Customization[] _lipColorPickerLight;
|
||||
private readonly Customization[] _tattooColorPicker;
|
||||
private readonly 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 CustomizationOptions _options;
|
||||
|
||||
private Customization[] CreateColorPicker(CustomizationId id, int offset, int num, bool light = false)
|
||||
private CustomizationData[] CreateColorPicker(CustomizationId id, int offset, int num, bool light = false)
|
||||
=> _cmpFile.GetSlice(offset, num)
|
||||
.Select((c, i) => new Customization(id, (byte)(light ? 128 + i : 0 + i), c, (ushort)(offset + i)))
|
||||
.Select((c, i) => new CustomizationData(id, (CustomizationByteValue)(light ? 128 + i : 0 + i), c, (ushort)(offset + i)))
|
||||
.ToArray();
|
||||
|
||||
|
||||
|
|
@ -227,12 +227,12 @@ public partial class CustomizationOptions
|
|||
return;
|
||||
}
|
||||
|
||||
var tmp = new IReadOnlyList<Customization>[set.Faces.Count + 1];
|
||||
var tmp = new IReadOnlyList<CustomizationData>[set.Faces.Count + 1];
|
||||
tmp[0] = set.HairStyles;
|
||||
|
||||
for (var i = 1; i <= set.Faces.Count; ++i)
|
||||
{
|
||||
bool Valid(Customization c)
|
||||
bool Valid(CustomizationData c)
|
||||
{
|
||||
var data = _customizeSheet.GetRow(c.CustomizeId)?.Unknown6 ?? 0;
|
||||
return data == 0 || data == i + set.Faces.Count;
|
||||
|
|
@ -295,13 +295,15 @@ public partial class CustomizationOptions
|
|||
private static void SetFacialFeatures(CustomizationSet set, CharaMakeParams row)
|
||||
{
|
||||
var count = set.Faces.Count;
|
||||
var featureDict = new List<IReadOnlyList<Customization>>(count);
|
||||
var featureDict = new List<IReadOnlyList<CustomizationData>>(count);
|
||||
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
var legacyTattoo = new Customization(CustomizationId.FacialFeaturesTattoos, 1 << 7, 137905, (ushort)((i + 1) * 8));
|
||||
var legacyTattoo = new CustomizationData(CustomizationId.FacialFeaturesTattoos, (CustomizationByteValue)(1 << 7), 137905,
|
||||
(ushort)((i + 1) * 8));
|
||||
featureDict.Add(row.FacialFeatureByFace[i].Icons.Select((val, idx)
|
||||
=> new Customization(CustomizationId.FacialFeaturesTattoos, (byte)(1 << idx), val, (ushort)(i * 8 + idx)))
|
||||
=> new CustomizationData(CustomizationId.FacialFeaturesTattoos, (CustomizationByteValue)(1 << idx), val,
|
||||
(ushort)(i * 8 + idx)))
|
||||
.Append(legacyTattoo)
|
||||
.ToArray());
|
||||
}
|
||||
|
|
@ -346,7 +348,7 @@ public partial class CustomizationOptions
|
|||
}
|
||||
|
||||
// Obtain available skin and hair colors for the given subrace and gender.
|
||||
private (Customization[], Customization[]) GetColors(SubRace race, Gender gender)
|
||||
private (CustomizationData[], CustomizationData[]) GetColors(SubRace race, Gender gender)
|
||||
{
|
||||
if (race is > SubRace.Veena or SubRace.Unknown)
|
||||
throw new ArgumentOutOfRangeException(nameof(race), race, null);
|
||||
|
|
@ -359,11 +361,11 @@ public partial class CustomizationOptions
|
|||
}
|
||||
|
||||
// Obtain available hairstyles via reflection from the Hair sheet for the given subrace and gender.
|
||||
private Customization[] GetHairStyles(SubRace race, Gender gender)
|
||||
private CustomizationData[] 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<Customization>(row.Unknown30);
|
||||
var hairList = new List<CustomizationData>(row.Unknown30);
|
||||
// Hairstyles can be found starting at Unknown66.
|
||||
for (var i = 0; i < row.Unknown30; ++i)
|
||||
{
|
||||
|
|
@ -376,20 +378,21 @@ 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 Customization(CustomizationId.Hairstyle, hairRow.FeatureID, hairRow.Icon, (ushort)hairRow.RowId)
|
||||
: new Customization(CustomizationId.Hairstyle, (byte)i, customizeIdx));
|
||||
? new CustomizationData(CustomizationId.Hairstyle, (CustomizationByteValue)hairRow.FeatureID, hairRow.Icon,
|
||||
(ushort)hairRow.RowId)
|
||||
: new CustomizationData(CustomizationId.Hairstyle, (CustomizationByteValue)i, customizeIdx));
|
||||
}
|
||||
|
||||
return hairList.ToArray();
|
||||
}
|
||||
|
||||
// Get Features.
|
||||
private Customization FromValueAndIndex(CustomizationId id, uint value, int index)
|
||||
private CustomizationData FromValueAndIndex(CustomizationId id, uint value, int index)
|
||||
{
|
||||
var row = _customizeSheet.GetRow(value);
|
||||
return row == null
|
||||
? new Customization(id, (byte)(index + 1), value)
|
||||
: new Customization(id, row.FeatureID, row.Icon, (ushort)row.RowId);
|
||||
? new CustomizationData(id, (CustomizationByteValue)(index + 1), value)
|
||||
: new CustomizationData(id, (CustomizationByteValue)row.FeatureID, row.Icon, (ushort)row.RowId);
|
||||
}
|
||||
|
||||
// Get List sizes.
|
||||
|
|
@ -400,10 +403,10 @@ public partial class CustomizationOptions
|
|||
}
|
||||
|
||||
// Get face paints from the hair sheet via reflection.
|
||||
private Customization[] GetFacePaints(SubRace race, Gender gender)
|
||||
private CustomizationData[] GetFacePaints(SubRace race, Gender gender)
|
||||
{
|
||||
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||
var paintList = new List<Customization>(row.Unknown37);
|
||||
var paintList = new List<CustomizationData>(row.Unknown37);
|
||||
|
||||
// Number of available face paints is at Unknown37.
|
||||
for (var i = 0; i < row.Unknown37; ++i)
|
||||
|
|
@ -419,29 +422,30 @@ 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 Customization(CustomizationId.FacePaint, paintRow.FeatureID, paintRow.Icon, (ushort)paintRow.RowId)
|
||||
: new Customization(CustomizationId.FacePaint, (byte)i, customizeIdx));
|
||||
? new CustomizationData(CustomizationId.FacePaint, (CustomizationByteValue)paintRow.FeatureID, paintRow.Icon,
|
||||
(ushort)paintRow.RowId)
|
||||
: new CustomizationData(CustomizationId.FacePaint, (CustomizationByteValue)i, customizeIdx));
|
||||
}
|
||||
|
||||
return paintList.ToArray();
|
||||
}
|
||||
|
||||
// Specific icons for tails or ears.
|
||||
private Customization[] GetTailEarShapes(CharaMakeParams row)
|
||||
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<Customization>();
|
||||
?? Array.Empty<CustomizationData>();
|
||||
|
||||
// Specific icons for faces.
|
||||
private Customization[] GetFaces(CharaMakeParams row)
|
||||
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<Customization>();
|
||||
?? Array.Empty<CustomizationData>();
|
||||
|
||||
// Specific icons for Hrothgar patterns.
|
||||
private Customization[] HrothgarFurPattern(CharaMakeParams row)
|
||||
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<Customization>();
|
||||
?? Array.Empty<CustomizationData>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.VisualBasic;
|
||||
using OtterGui;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Customization;
|
||||
|
|
@ -15,16 +15,15 @@ public class CustomizationSet
|
|||
{
|
||||
Gender = gender;
|
||||
Clan = clan;
|
||||
_settingAvailable = clan.ToRace() == Race.Hrothgar && gender == Gender.Female
|
||||
Race = clan.ToRace();
|
||||
_settingAvailable = Race == Race.Hrothgar && gender == Gender.Female
|
||||
? 0u
|
||||
: DefaultAvailable;
|
||||
}
|
||||
|
||||
public Gender Gender { get; }
|
||||
public SubRace Clan { get; }
|
||||
|
||||
public Race Race
|
||||
=> Clan.ToRace();
|
||||
public Race Race { get; }
|
||||
|
||||
private uint _settingAvailable;
|
||||
|
||||
|
|
@ -34,11 +33,18 @@ public class CustomizationSet
|
|||
public bool IsAvailable(CustomizationId id)
|
||||
=> (_settingAvailable & (1u << (int)id)) != 0;
|
||||
|
||||
public int NumEyebrows { get; internal init; }
|
||||
public int NumEyeShapes { get; internal init; }
|
||||
public int NumNoseShapes { get; internal init; }
|
||||
public int NumJawShapes { get; internal init; }
|
||||
public int NumMouthShapes { get; internal init; }
|
||||
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)
|
||||
{
|
||||
|
|
@ -49,45 +55,53 @@ public class CustomizationSet
|
|||
return sb.ToString();
|
||||
}
|
||||
|
||||
// Meta
|
||||
public IReadOnlyList<string> OptionName { get; internal set; } = null!;
|
||||
|
||||
public IReadOnlyList<string> OptionName { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> Faces { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> HairStyles { get; internal init; } = null!;
|
||||
public IReadOnlyList<IReadOnlyList<Customization>> HairByFace { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> TailEarShapes { get; internal init; } = null!;
|
||||
public IReadOnlyList<IReadOnlyList<Customization>> FeaturesTattoos { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> FacePaints { get; internal init; } = null!;
|
||||
|
||||
public IReadOnlyList<Customization> SkinColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> HairColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> HighlightColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> EyeColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> TattooColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> FacePaintColorsLight { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> FacePaintColorsDark { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> LipColorsLight { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> LipColorsDark { get; internal init; } = null!;
|
||||
public string Option(CustomizationId id)
|
||||
=> OptionName[(int)id];
|
||||
|
||||
public IReadOnlyList<CharaMakeParams.MenuType> Types { get; internal set; } = null!;
|
||||
public IReadOnlyDictionary<CharaMakeParams.MenuType, CustomizationId[]> Order { get; internal set; } = null!;
|
||||
|
||||
|
||||
public string Option(CustomizationId id)
|
||||
=> OptionName[(int)id];
|
||||
// Always list selector.
|
||||
public int NumEyebrows { get; internal init; }
|
||||
public int NumEyeShapes { get; internal init; }
|
||||
public int NumNoseShapes { get; internal init; }
|
||||
public int NumJawShapes { get; internal init; }
|
||||
public int NumMouthShapes { get; internal init; }
|
||||
|
||||
public Customization FacialFeature(int faceIdx, int idx)
|
||||
|
||||
// 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)
|
||||
{
|
||||
faceIdx = HrothgarFaceHack((byte) faceIdx) - 1;
|
||||
if (faceIdx < FeaturesTattoos.Count)
|
||||
return FeaturesTattoos[HrothgarFaceHack((byte)faceIdx)][idx];
|
||||
|
||||
return FeaturesTattoos[0][idx];
|
||||
face = HrothgarFaceHack(face);
|
||||
var faceIdx = Faces.IndexOf(p => p.Value == face);
|
||||
return FeaturesTattoos[faceIdx != -1 ? faceIdx : 0][idx];
|
||||
}
|
||||
|
||||
private byte HrothgarFaceHack(byte value)
|
||||
=> value is > 4 and < 9 && Clan.ToRace() == Race.Hrothgar ? (byte)(value - 4) : value;
|
||||
|
||||
public int DataByValue(CustomizationId id, byte value, out Customization? custom)
|
||||
// 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 int DataByValue(CustomizationId id, CustomizationByteValue value, out CustomizationData? custom)
|
||||
{
|
||||
var type = id.ToType();
|
||||
custom = null;
|
||||
|
|
@ -95,16 +109,16 @@ public class CustomizationSet
|
|||
{
|
||||
if (value < Count(id))
|
||||
{
|
||||
custom = new Customization(id, value, 0, value);
|
||||
return value;
|
||||
custom = new CustomizationData(id, value, 0, value.Value);
|
||||
return value.Value;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int Get(IEnumerable<Customization> list, byte v, ref Customization? output)
|
||||
int Get(IEnumerable<CustomizationData> list, CustomizationByteValue v, ref CustomizationData? output)
|
||||
{
|
||||
var (val, idx) = list.Cast<Customization?>().Select((c, i) => (c, i)).FirstOrDefault(c => c.c!.Value.Value == v);
|
||||
var (val, idx) = list.Cast<CustomizationData?>().WithIndex().FirstOrDefault(p => p.Item1!.Value.Value == v);
|
||||
if (val == null)
|
||||
return -1;
|
||||
|
||||
|
|
@ -132,21 +146,24 @@ public class CustomizationSet
|
|||
};
|
||||
}
|
||||
|
||||
public Customization Data(CustomizationId id, int idx, byte face = 0)
|
||||
public CustomizationData Data(CustomizationId id, int idx)
|
||||
=> Data(id, idx, CustomizationByteValue.Zero);
|
||||
|
||||
public CustomizationData Data(CustomizationId id, int idx, CustomizationByteValue face)
|
||||
{
|
||||
if (idx > Count(id, face = HrothgarFaceHack(face)))
|
||||
if (idx >= Count(id, face = HrothgarFaceHack(face)))
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
return id switch
|
||||
{
|
||||
CustomizationId.Face => Faces[idx],
|
||||
CustomizationId.Hairstyle => face < HairByFace.Count ? HairByFace[face][idx] : HairStyles[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],
|
||||
|
|
@ -159,7 +176,7 @@ public class CustomizationSet
|
|||
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),
|
||||
_ => new CustomizationData(0, CustomizationByteValue.Zero),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -179,7 +196,10 @@ public class CustomizationSet
|
|||
return dict;
|
||||
}
|
||||
|
||||
public int Count(CustomizationId id, byte face = 0)
|
||||
public int Count(CustomizationId id)
|
||||
=> Count(id, CustomizationByteValue.Zero);
|
||||
|
||||
public int Count(CustomizationId id, CustomizationByteValue face)
|
||||
{
|
||||
if (!IsAvailable(id))
|
||||
return 0;
|
||||
|
|
@ -190,7 +210,7 @@ public class CustomizationSet
|
|||
return id switch
|
||||
{
|
||||
CustomizationId.Face => Faces.Count,
|
||||
CustomizationId.Hairstyle => (face = HrothgarFaceHack(face)) < HairByFace.Count ? HairByFace[face].Count : 0,
|
||||
CustomizationId.Hairstyle => (face = HrothgarFaceHack(face)) < HairByFace.Count ? HairByFace[face.Value].Count : 0,
|
||||
CustomizationId.HighlightsOnFlag => 2,
|
||||
CustomizationId.SkinColor => SkinColors.Count,
|
||||
CustomizationId.EyeColorR => EyeColors.Count,
|
||||
|
|
@ -212,16 +232,6 @@ public class CustomizationSet
|
|||
};
|
||||
}
|
||||
|
||||
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);
|
||||
private CustomizationByteValue HrothgarFaceHack(CustomizationByteValue value)
|
||||
=> Race == Race.Hrothgar && value.Value is > 4 and < 9 ? value - 4 : value;
|
||||
}
|
||||
|
|
|
|||
358
Glamourer.GameData/Customization/Customize.cs
Normal file
358
Glamourer.GameData/Customization/Customize.cs
Normal file
|
|
@ -0,0 +1,358 @@
|
|||
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 Customize(CustomizeData* data)
|
||||
=> Data = data;
|
||||
|
||||
public Race Race
|
||||
{
|
||||
get => (Race)Data->Data[0];
|
||||
set => Data->Data[0] = (byte)value;
|
||||
}
|
||||
|
||||
// Skip Unknown Gender
|
||||
public Gender Gender
|
||||
{
|
||||
get => (Gender)(Data->Data[1] + 1);
|
||||
set => Data->Data[1] = (byte)(value - 1);
|
||||
}
|
||||
|
||||
public CustomizationByteValue 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;
|
||||
}
|
||||
|
||||
public SubRace Clan
|
||||
{
|
||||
get => (SubRace)Data->Data[4];
|
||||
set => Data->Data[4] = (byte)value;
|
||||
}
|
||||
|
||||
public CustomizationByteValue Face
|
||||
{
|
||||
get => (CustomizationByteValue)Data->Data[5];
|
||||
set => Data->Data[5] = value.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 CustomizationByteValue SkinColor
|
||||
{
|
||||
get => (CustomizationByteValue)Data->Data[8];
|
||||
set => Data->Data[8] = value.Value;
|
||||
}
|
||||
|
||||
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 bool Equals(Customize other)
|
||||
=> CustomizeData.Equals(Data, other.Data);
|
||||
|
||||
public CustomizationByteValue this[CustomizationId id]
|
||||
{
|
||||
get => Get(id);
|
||||
set => Set(id, value);
|
||||
}
|
||||
|
||||
private static 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();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void Load(Customize other)
|
||||
=> Data->Read(other.Data);
|
||||
|
||||
public void Write(IntPtr target)
|
||||
=> Data->Write((void*)target);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue