This commit is contained in:
Ottermandias 2022-08-23 18:06:28 +02:00
parent cb2e2f0128
commit 941bba1518
39 changed files with 2569 additions and 1579 deletions

View file

@ -6,59 +6,59 @@ namespace Glamourer.Customization;
public unsafe struct Customize
{
private readonly CustomizeData* _data;
public readonly CustomizeData* Data;
public Customize(CustomizeData* data)
=> _data = data;
=> Data = data;
public Race Race
{
get => (Race)_data->Data[0];
set => _data->Data[0] = (byte)value;
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);
get => (Gender)(Data->Data[1] + 1);
set => Data->Data[1] = (byte)(value - 1);
}
public ref byte BodyType
=> ref _data->Data[2];
=> ref Data->Data[2];
public ref byte Height
=> ref _data->Data[3];
=> ref Data->Data[3];
public SubRace Clan
{
get => (SubRace)_data->Data[4];
set => _data->Data[4] = (byte)value;
get => (SubRace)Data->Data[4];
set => Data->Data[4] = (byte)value;
}
public ref byte Face
=> ref _data->Data[5];
=> ref Data->Data[5];
public ref byte Hairstyle
=> ref _data->Data[6];
=> ref Data->Data[6];
public bool HighlightsOn
{
get => _data->Data[7] >> 7 == 1;
set => _data->Data[7] = (byte)(value ? _data->Data[7] | 0x80 : _data->Data[7] & 0x7F);
get => Data->Data[7] >> 7 == 1;
set => Data->Data[7] = (byte)(value ? Data->Data[7] | 0x80 : Data->Data[7] & 0x7F);
}
public ref byte SkinColor
=> ref _data->Data[8];
=> ref Data->Data[8];
public ref byte EyeColorRight
=> ref _data->Data[9];
=> ref Data->Data[9];
public ref byte HairColor
=> ref _data->Data[10];
=> ref Data->Data[10];
public ref byte HighlightsColor
=> ref _data->Data[11];
=> ref Data->Data[11];
public readonly ref struct FacialFeatureStruct
{
@ -84,73 +84,73 @@ public unsafe struct Customize
}
public FacialFeatureStruct FacialFeatures
=> new(_data->Data + 12);
=> new(Data->Data + 12);
public ref byte TattooColor
=> ref _data->Data[13];
=> ref Data->Data[13];
public ref byte Eyebrows
=> ref _data->Data[14];
=> ref Data->Data[14];
public ref byte EyeColorLeft
=> ref _data->Data[15];
=> ref Data->Data[15];
public byte EyeShape
{
get => (byte)(_data->Data[16] & 0x7F);
set => _data->Data[16] = (byte)((value & 0x7F) | (_data->Data[16] & 0x80));
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);
get => Data->Data[16] >> 7 == 1;
set => Data->Data[16] = (byte)(value ? Data->Data[16] | 0x80 : Data->Data[16] & 0x7F);
}
public ref byte Nose
=> ref _data->Data[17];
=> ref Data->Data[17];
public ref byte Jaw
=> ref _data->Data[18];
=> ref Data->Data[18];
public byte Mouth
{
get => (byte)(_data->Data[19] & 0x7F);
set => _data->Data[19] = (byte)((value & 0x7F) | (_data->Data[19] & 0x80));
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);
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];
=> ref Data->Data[20];
public ref byte MuscleMass
=> ref _data->Data[21];
=> ref Data->Data[21];
public ref byte TailShape
=> ref _data->Data[22];
=> ref Data->Data[22];
public ref byte BustSize
=> ref _data->Data[23];
=> ref Data->Data[23];
public byte FacePaint
{
get => (byte)(_data->Data[24] & 0x7F);
set => _data->Data[24] = (byte)((value & 0x7F) | (_data->Data[24] & 0x80));
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);
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];
=> ref Data->Data[25];
public static readonly CustomizeData Default = GenerateDefault();
public static readonly CustomizeData Empty = new();
@ -165,12 +165,12 @@ public unsafe struct Customize
CustomizationId.Clan => (byte)Clan,
CustomizationId.Face => Face,
CustomizationId.Hairstyle => Hairstyle,
CustomizationId.HighlightsOnFlag => _data->Data[7],
CustomizationId.HighlightsOnFlag => Data->Data[7],
CustomizationId.SkinColor => SkinColor,
CustomizationId.EyeColorR => EyeColorRight,
CustomizationId.HairColor => HairColor,
CustomizationId.HighlightColor => HighlightsColor,
CustomizationId.FacialFeaturesTattoos => _data->Data[12],
CustomizationId.FacialFeaturesTattoos => Data->Data[12],
CustomizationId.TattooColor => TattooColor,
CustomizationId.Eyebrows => Eyebrows,
CustomizationId.EyeColorL => EyeColorLeft,
@ -204,7 +204,7 @@ public unsafe struct Customize
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.FacialFeaturesTattoos: Data->Data[12] = value; break;
case CustomizationId.TattooColor: TattooColor = value; break;
case CustomizationId.Eyebrows: Eyebrows = value; break;
case CustomizationId.EyeColorL: EyeColorLeft = value; break;
@ -224,7 +224,7 @@ public unsafe struct Customize
}
public bool Equals(Customize other)
=> throw new NotImplementedException();
=> CustomizeData.Equals(Data, other.Data);
public byte this[CustomizationId id]
{
@ -268,5 +268,8 @@ public unsafe struct Customize
}
public void Load(Customize other)
=> _data->Read(other._data);
=> Data->Read(other.Data);
public void Write(IntPtr target)
=> Data->Write((void*)target);
}

View file

@ -137,8 +137,8 @@ public partial class CustomizationOptions
public CustomizationSet GetSet(SubRace race, Gender gender)
{
var (skin, hair) = GetColors(race, gender);
var row = _listSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
var row = _listSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
var hrothgar = race.ToRace() == Race.Hrothgar;
// Create the initial set with all the easily accessible parameters available for anyone.
var set = new CustomizationSet(race, gender)
{
@ -148,8 +148,8 @@ public partial class CustomizationOptions
EyeColors = _eyeColorPicker,
HighlightColors = _highlightPicker,
TattooColors = _tattooColorPicker,
LipColorsDark = race.ToRace() == Race.Hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark,
LipColorsLight = race.ToRace() == Race.Hrothgar ? Array.Empty<Customization>() : _lipColorPickerLight,
LipColorsDark = hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark,
LipColorsLight = hrothgar ? Array.Empty<Customization>() : _lipColorPickerLight,
FacePaintColorsDark = _facePaintColorPickerDark,
FacePaintColorsLight = _facePaintColorPickerLight,
Faces = GetFaces(row),
@ -164,9 +164,9 @@ public partial class CustomizationOptions
SetAvailability(set, row);
SetFacialFeatures(set, row);
SetHairByFace(set);
SetMenuTypes(set, row);
SetNames(set, row);
return set;
}
@ -218,6 +218,32 @@ public partial class CustomizationOptions
.Select((c, i) => new Customization(id, (byte)(light ? 128 + i : 0 + i), c, (ushort)(offset + i)))
.ToArray();
private void SetHairByFace(CustomizationSet set)
{
if (set.Race != Race.Hrothgar)
{
set.HairByFace = Enumerable.Repeat(set.HairStyles, set.Faces.Count + 1).ToArray();
return;
}
var tmp = new IReadOnlyList<Customization>[set.Faces.Count + 1];
tmp[0] = set.HairStyles;
for (var i = 1; i <= set.Faces.Count; ++i)
{
bool Valid(Customization c)
{
var data = _customizeSheet.GetRow(c.CustomizeId)?.Unknown6 ?? 0;
return data == 0 || data == i + set.Faces.Count;
}
tmp[i] = set.HairStyles.Where(Valid).ToArray();
}
set.HairByFace = tmp;
}
private static void SetMenuTypes(CustomizationSet set, CharaMakeParams row)
{
// Set up the menu types for all customizations.
@ -270,6 +296,7 @@ public partial class CustomizationOptions
{
var count = set.Faces.Count;
var featureDict = new List<IReadOnlyList<Customization>>(count);
for (var i = 0; i < count; ++i)
{
var legacyTattoo = new Customization(CustomizationId.FacialFeaturesTattoos, 1 << 7, 137905, (ushort)((i + 1) * 8));

View file

@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualBasic;
using Penumbra.GameData.Enums;
namespace Glamourer.Customization;
@ -52,6 +53,7 @@ public class CustomizationSet
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!;
@ -74,13 +76,22 @@ public class CustomizationSet
=> OptionName[(int)id];
public Customization FacialFeature(int faceIdx, int idx)
=> FeaturesTattoos[faceIdx - 1][idx];
{
faceIdx = HrothgarFaceHack((byte) faceIdx) - 1;
if (faceIdx < FeaturesTattoos.Count)
return FeaturesTattoos[HrothgarFaceHack((byte)faceIdx)][idx];
return FeaturesTattoos[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)
{
var type = id.ToType();
custom = null;
if (type == CharaMakeParams.MenuType.Percentage || type == CharaMakeParams.MenuType.ListSelector)
if (type is CharaMakeParams.MenuType.Percentage or CharaMakeParams.MenuType.ListSelector)
{
if (value < Count(id))
{
@ -91,9 +102,9 @@ public class CustomizationSet
return -1;
}
int Get(IEnumerable<Customization> list, ref Customization? output)
int Get(IEnumerable<Customization> list, byte v, ref Customization? output)
{
var (val, idx) = list.Cast<Customization?>().Select((c, i) => (c, i)).FirstOrDefault(c => c.c!.Value.Value == value);
var (val, idx) = list.Cast<Customization?>().Select((c, i) => (c, i)).FirstOrDefault(c => c.c!.Value.Value == v);
if (val == null)
return -1;
@ -103,27 +114,27 @@ public class CustomizationSet
return id switch
{
CustomizationId.SkinColor => Get(SkinColors, ref custom),
CustomizationId.EyeColorL => Get(EyeColors, ref custom),
CustomizationId.EyeColorR => Get(EyeColors, ref custom),
CustomizationId.HairColor => Get(HairColors, ref custom),
CustomizationId.HighlightColor => Get(HighlightColors, ref custom),
CustomizationId.TattooColor => Get(TattooColors, ref custom),
CustomizationId.LipColor => Get(LipColorsDark.Concat(LipColorsLight), ref custom),
CustomizationId.FacePaintColor => Get(FacePaintColorsDark.Concat(FacePaintColorsLight), ref custom),
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, ref custom),
CustomizationId.Hairstyle => Get(HairStyles, ref custom),
CustomizationId.TailEarShape => Get(TailEarShapes, ref custom),
CustomizationId.FacePaint => Get(FacePaints, ref custom),
CustomizationId.FacialFeaturesTattoos => Get(FeaturesTattoos[0], 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),
};
}
public Customization Data(CustomizationId id, int idx)
public Customization Data(CustomizationId id, int idx, byte face = 0)
{
if (idx > Count(id))
if (idx > Count(id, face = HrothgarFaceHack(face)))
throw new IndexOutOfRangeException();
switch (id.ToType())
@ -135,7 +146,7 @@ public class CustomizationSet
return id switch
{
CustomizationId.Face => Faces[idx],
CustomizationId.Hairstyle => HairStyles[idx],
CustomizationId.Hairstyle => face < HairByFace.Count ? HairByFace[face][idx] : HairStyles[idx],
CustomizationId.TailEarShape => TailEarShapes[idx],
CustomizationId.FacePaint => FacePaints[idx],
CustomizationId.FacialFeaturesTattoos => FeaturesTattoos[0][idx],
@ -162,10 +173,13 @@ public class CustomizationSet
ret[(int)CustomizationId.EyeColorL] = CustomizationId.EyeColorR;
ret[(int)CustomizationId.EyeColorR] = CustomizationId.TattooColor;
return ret.Skip(2).Where(set.IsAvailable).GroupBy(set.Type).ToDictionary(k => k.Key, k => k.ToArray());
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>());
return dict;
}
public int Count(CustomizationId id)
public int Count(CustomizationId id, byte face = 0)
{
if (!IsAvailable(id))
return 0;
@ -176,7 +190,7 @@ public class CustomizationSet
return id switch
{
CustomizationId.Face => Faces.Count,
CustomizationId.Hairstyle => HairStyles.Count,
CustomizationId.Hairstyle => (face = HrothgarFaceHack(face)) < HairByFace.Count ? HairByFace[face].Count : 0,
CustomizationId.HighlightsOnFlag => 2,
CustomizationId.SkinColor => SkinColors.Count,
CustomizationId.EyeColorR => EyeColors.Count,