mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 10:17:23 +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);
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ using Dalamud.Data;
|
|||
using Glamourer.Structs;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Item = Glamourer.Structs.Item;
|
||||
using Stain = Glamourer.Structs.Stain;
|
||||
|
||||
|
|
@ -13,7 +14,7 @@ namespace Glamourer;
|
|||
|
||||
public static class GameData
|
||||
{
|
||||
private static Dictionary<byte, Stain>? _stains;
|
||||
private static Dictionary<StainId, Stain>? _stains;
|
||||
private static Dictionary<EquipSlot, List<Item>>? _itemsBySlot;
|
||||
private static Dictionary<byte, Job>? _jobs;
|
||||
private static Dictionary<ushort, JobGroup>? _jobGroups;
|
||||
|
|
@ -26,13 +27,13 @@ public static class GameData
|
|||
public static ModelData Models(DataManager dataManager)
|
||||
=> _models ??= new ModelData(dataManager);
|
||||
|
||||
public static IReadOnlyDictionary<byte, Stain> Stains(DataManager dataManager)
|
||||
public static IReadOnlyDictionary<StainId, Stain> Stains(DataManager dataManager)
|
||||
{
|
||||
if (_stains != null)
|
||||
return _stains;
|
||||
|
||||
var sheet = dataManager.GetExcelSheet<Lumina.Excel.GeneratedSheets.Stain>()!;
|
||||
_stains = sheet.Where(s => s.Color != 0).ToDictionary(s => (byte)s.RowId, s => new Stain((byte)s.RowId, s));
|
||||
_stains = sheet.Where(s => s.Color != 0).ToDictionary(s => (StainId)s.RowId, s => new Stain((byte)s.RowId, s));
|
||||
return _stains;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,61 +20,25 @@ EndProject
|
|||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{A5439F6B-83C1-4078-9371-354A147FF554}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{A5439F6B-83C1-4078-9371-354A147FF554}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A5439F6B-83C1-4078-9371-354A147FF554}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{A5439F6B-83C1-4078-9371-354A147FF554}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{A5439F6B-83C1-4078-9371-354A147FF554}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{A5439F6B-83C1-4078-9371-354A147FF554}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{A5439F6B-83C1-4078-9371-354A147FF554}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A5439F6B-83C1-4078-9371-354A147FF554}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{A5439F6B-83C1-4078-9371-354A147FF554}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{A5439F6B-83C1-4078-9371-354A147FF554}.Release|x64.Build.0 = Release|Any CPU
|
||||
{A5439F6B-83C1-4078-9371-354A147FF554}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{A5439F6B-83C1-4078-9371-354A147FF554}.Release|x86.Build.0 = Release|Any CPU
|
||||
{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Release|x64.Build.0 = Release|Any CPU
|
||||
{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Release|x86.Build.0 = Release|Any CPU
|
||||
{9BEE2336-AA93-4669-8EEA-4756B3B2D024}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{9BEE2336-AA93-4669-8EEA-4756B3B2D024}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{9BEE2336-AA93-4669-8EEA-4756B3B2D024}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{9BEE2336-AA93-4669-8EEA-4756B3B2D024}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{9BEE2336-AA93-4669-8EEA-4756B3B2D024}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{9BEE2336-AA93-4669-8EEA-4756B3B2D024}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{9BEE2336-AA93-4669-8EEA-4756B3B2D024}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{9BEE2336-AA93-4669-8EEA-4756B3B2D024}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{9BEE2336-AA93-4669-8EEA-4756B3B2D024}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{9BEE2336-AA93-4669-8EEA-4756B3B2D024}.Release|x64.Build.0 = Release|Any CPU
|
||||
{9BEE2336-AA93-4669-8EEA-4756B3B2D024}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{9BEE2336-AA93-4669-8EEA-4756B3B2D024}.Release|x86.Build.0 = Release|Any CPU
|
||||
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|x64.Build.0 = Debug|Any CPU
|
||||
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Debug|x86.Build.0 = Debug|Any CPU
|
||||
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Release|x64.ActiveCfg = Release|Any CPU
|
||||
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Release|x64.Build.0 = Release|Any CPU
|
||||
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Release|x86.ActiveCfg = Release|Any CPU
|
||||
{6A4F7788-DB91-41B6-A264-7FD9CCACD7AA}.Release|x86.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
|||
|
|
@ -1,17 +1,13 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using System.Reflection;
|
||||
using Dalamud.Game.Command;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Api;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Gui;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.State;
|
||||
using OtterGui.Log;
|
||||
using Penumbra.GameData;
|
||||
|
||||
namespace Glamourer;
|
||||
|
|
@ -32,6 +28,7 @@ public class Glamourer : IDalamudPlugin
|
|||
|
||||
|
||||
public static GlamourerConfig Config = null!;
|
||||
public static Logger Log = null!;
|
||||
|
||||
public static IObjectIdentifier Identifier = null!;
|
||||
public static PenumbraAttach Penumbra = null!;
|
||||
|
|
@ -55,6 +52,7 @@ public class Glamourer : IDalamudPlugin
|
|||
try
|
||||
{
|
||||
Dalamud.Initialize(pluginInterface);
|
||||
Log = new Logger();
|
||||
|
||||
Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData, Dalamud.ClientState.ClientLanguage);
|
||||
RestrictedGear = GameData.RestrictedGear(Dalamud.GameData);
|
||||
|
|
|
|||
65
Glamourer/Gui/Customization/CustomizationDrawer.Color.cs
Normal file
65
Glamourer/Gui/Customization/CustomizationDrawer.Color.cs
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
using System.Numerics;
|
||||
using Glamourer.Customization;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Raii;
|
||||
|
||||
namespace Glamourer.Gui.Customization;
|
||||
|
||||
internal partial class CustomizationDrawer
|
||||
{
|
||||
private const string ColorPickerPopupName = "ColorPicker";
|
||||
|
||||
private void DrawColorPicker(CustomizationId id)
|
||||
{
|
||||
using var _ = SetId(id);
|
||||
var (current, custom) = GetCurrentCustomization(id);
|
||||
var color = ImGui.ColorConvertU32ToFloat4(custom.Color);
|
||||
|
||||
// Print 1-based index instead of 0.
|
||||
if (ImGui.ColorButton($"{current + 1}##color", color, ImGuiColorEditFlags.None, _framedIconSize))
|
||||
ImGui.OpenPopup(ColorPickerPopupName);
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
using (var group = ImRaii.Group())
|
||||
{
|
||||
DataInputInt(current);
|
||||
ImGui.TextUnformatted(_currentOption);
|
||||
}
|
||||
|
||||
DrawColorPickerPopup();
|
||||
}
|
||||
|
||||
private void DrawColorPickerPopup()
|
||||
{
|
||||
using var popup = ImRaii.Popup(ColorPickerPopupName, ImGuiWindowFlags.AlwaysAutoResize);
|
||||
if (!popup)
|
||||
return;
|
||||
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
for (var i = 0; i < _currentCount; ++i)
|
||||
{
|
||||
var custom = _set.Data(_currentId, i, _customize[CustomizationId.Face]);
|
||||
if (ImGui.ColorButton((i + 1).ToString(), ImGui.ColorConvertU32ToFloat4(custom.Color)))
|
||||
{
|
||||
UpdateValue(custom.Value);
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
if (i % 8 != 7)
|
||||
ImGui.SameLine();
|
||||
}
|
||||
}
|
||||
|
||||
// Obtain the current customization and print a warning if it is not known.
|
||||
private (int, CustomizationData) GetCurrentCustomization(CustomizationId id)
|
||||
{
|
||||
var current = _set.DataByValue(id, _customize[id], out var custom);
|
||||
if (!_set.IsAvailable(id) || current >= 0)
|
||||
return (current, custom!.Value);
|
||||
|
||||
Glamourer.Log.Warning($"Read invalid customization value {_customize[id]} for {id}.");
|
||||
return (0, _set.Data(id, 0));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Dalamud.Interface;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Util;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Gui.Customization;
|
||||
|
||||
internal partial class CustomizationDrawer
|
||||
{
|
||||
private void DrawRaceGenderSelector()
|
||||
{
|
||||
DrawGenderSelector();
|
||||
ImGui.SameLine();
|
||||
using var group = ImRaii.Group();
|
||||
DrawRaceCombo();
|
||||
var gender = Glamourer.Customization.GetName(CustomName.Gender);
|
||||
var clan = Glamourer.Customization.GetName(CustomName.Clan);
|
||||
ImGui.TextUnformatted($"{gender} & {clan}");
|
||||
}
|
||||
|
||||
private void DrawGenderSelector()
|
||||
{
|
||||
using var font = ImRaii.PushFont(UiBuilder.IconFont);
|
||||
var icon = _customize.Gender == Gender.Male ? FontAwesomeIcon.Mars : FontAwesomeIcon.Venus;
|
||||
var restricted = _customize.Race == Race.Hrothgar;
|
||||
if (restricted)
|
||||
icon = FontAwesomeIcon.MarsDouble;
|
||||
|
||||
if (!ImGuiUtil.DrawDisabledButton(icon.ToIconString(), _framedIconSize, string.Empty, restricted, true))
|
||||
return;
|
||||
|
||||
var gender = _customize.Gender == Gender.Male ? Gender.Female : Gender.Male;
|
||||
if (!_customize.ChangeGender(_equip, gender))
|
||||
return;
|
||||
|
||||
foreach (var actor in _actors.Where(a => a))
|
||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw, false);
|
||||
}
|
||||
|
||||
private void DrawRaceCombo()
|
||||
{
|
||||
ImGui.SetNextItemWidth(_raceSelectorWidth);
|
||||
using var combo = ImRaii.Combo("##subRaceCombo", _customize.ClanName());
|
||||
if (!combo)
|
||||
return;
|
||||
|
||||
foreach (var subRace in Enum.GetValues<SubRace>().Skip(1)) // Skip Unknown
|
||||
{
|
||||
if (!ImGui.Selectable(CustomizeExtensions.ClanName(subRace, _customize.Gender), subRace == _customize.Clan)
|
||||
|| !_customize.ChangeRace(_equip, subRace))
|
||||
continue;
|
||||
|
||||
foreach (var actor in _actors.Where(a => a && a.DrawObject))
|
||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
109
Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs
Normal file
109
Glamourer/Gui/Customization/CustomizationDrawer.Icon.cs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Glamourer.Customization;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Gui.Customization;
|
||||
|
||||
internal partial class CustomizationDrawer
|
||||
{
|
||||
private const string IconSelectorPopup = "Style Picker";
|
||||
|
||||
private void DrawIconSelector(CustomizationId id)
|
||||
{
|
||||
using var _ = SetId(id);
|
||||
using var bigGroup = ImRaii.Group();
|
||||
var label = _currentOption;
|
||||
|
||||
var current = _set.DataByValue(id, _currentByte, out var custom);
|
||||
if (current < 0)
|
||||
{
|
||||
label = $"{_currentOption} (Custom #{_customize[id]})";
|
||||
current = 0;
|
||||
custom = _set.Data(id, 0);
|
||||
}
|
||||
|
||||
var icon = Glamourer.Customization.GetIcon(custom!.Value.IconId);
|
||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
|
||||
ImGui.OpenPopup(IconSelectorPopup);
|
||||
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||
|
||||
ImGui.SameLine();
|
||||
using (var group = ImRaii.Group())
|
||||
{
|
||||
if (_currentId == CustomizationId.Face)
|
||||
FaceInputInt(current);
|
||||
else
|
||||
DataInputInt(current);
|
||||
ImGui.TextUnformatted($"{label} ({custom.Value.Value})");
|
||||
}
|
||||
|
||||
DrawIconPickerPopup();
|
||||
}
|
||||
|
||||
private void UpdateFace(CustomizationData data)
|
||||
{
|
||||
// Hrothgar Hack
|
||||
var value = _set.Race == Race.Hrothgar ? data.Value + 4 : data.Value;
|
||||
if (_customize.Face == value)
|
||||
return;
|
||||
|
||||
_customize.Face = value;
|
||||
foreach (var actor in _actors)
|
||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw, false);
|
||||
}
|
||||
|
||||
private void FaceInputInt(int currentIndex)
|
||||
{
|
||||
++currentIndex;
|
||||
ImGui.SetNextItemWidth(_inputIntSize);
|
||||
if (ImGui.InputInt("##text", ref currentIndex, 1, 1))
|
||||
{
|
||||
currentIndex = Math.Clamp(currentIndex - 1, 0, _currentCount - 1);
|
||||
var data = _set.Data(_currentId, currentIndex, _customize.Face);
|
||||
UpdateFace(data);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]");
|
||||
}
|
||||
|
||||
private void DrawIconPickerPopup()
|
||||
{
|
||||
using var popup = ImRaii.Popup(IconSelectorPopup, ImGuiWindowFlags.AlwaysAutoResize);
|
||||
if (!popup)
|
||||
return;
|
||||
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
for (var i = 0; i < _currentCount; ++i)
|
||||
{
|
||||
var custom = _set.Data(_currentId, i, _customize.Face);
|
||||
var icon = Glamourer.Customization.GetIcon(custom.IconId);
|
||||
using (var _ = ImRaii.Group())
|
||||
{
|
||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
|
||||
{
|
||||
if (_currentId == CustomizationId.Face)
|
||||
UpdateFace(custom);
|
||||
else
|
||||
UpdateValue(custom.Value);
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||
|
||||
var text = custom.Value.ToString();
|
||||
var textWidth = ImGui.CalcTextSize(text).X;
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (_iconSize.X - textWidth + 2 * ImGui.GetStyle().FramePadding.X) / 2);
|
||||
ImGui.TextUnformatted(text);
|
||||
}
|
||||
|
||||
if (i % 8 != 7)
|
||||
ImGui.SameLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
148
Glamourer/Gui/Customization/CustomizationDrawer.Main.cs
Normal file
148
Glamourer/Gui/Customization/CustomizationDrawer.Main.cs
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Interop;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Gui.Customization;
|
||||
|
||||
internal partial class CustomizationDrawer
|
||||
{
|
||||
private static readonly Vector4 RedTint = new(0.6f, 0.3f, 0.3f, 1f);
|
||||
private static readonly ImGuiScene.TextureWrap? LegacyTattoo;
|
||||
|
||||
private readonly Vector2 _iconSize;
|
||||
private readonly Vector2 _framedIconSize;
|
||||
private readonly float _inputIntSize;
|
||||
private readonly float _comboSelectorSize;
|
||||
private readonly float _raceSelectorWidth;
|
||||
|
||||
private Customize _customize;
|
||||
private CharacterEquip _equip;
|
||||
private IReadOnlyCollection<Actor> _actors = Array.Empty<Actor>();
|
||||
private CustomizationSet _set = null!;
|
||||
|
||||
private CustomizationDrawer()
|
||||
{
|
||||
_iconSize = new Vector2(ImGui.GetTextLineHeightWithSpacing() * 2);
|
||||
_framedIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding;
|
||||
_inputIntSize = 2 * _framedIconSize.X + ImGui.GetStyle().ItemSpacing.X;
|
||||
_comboSelectorSize = 4 * _framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X;
|
||||
_raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X;
|
||||
}
|
||||
|
||||
static CustomizationDrawer()
|
||||
=> LegacyTattoo = GetLegacyTattooIcon();
|
||||
|
||||
public static void Dispose()
|
||||
=> LegacyTattoo?.Dispose();
|
||||
|
||||
private static ImGuiScene.TextureWrap? GetLegacyTattooIcon()
|
||||
{
|
||||
using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Glamourer.LegacyTattoo.raw");
|
||||
if (resource == null)
|
||||
return null;
|
||||
|
||||
var rawImage = new byte[resource.Length];
|
||||
var length = resource.Read(rawImage, 0, (int)resource.Length);
|
||||
return length == resource.Length
|
||||
? Dalamud.PluginInterface.UiBuilder.LoadImageRaw(rawImage, 192, 192, 4)
|
||||
: null;
|
||||
}
|
||||
|
||||
public static void Draw(Customize customize, CharacterEquip equip, IReadOnlyCollection<Actor> actors, bool locked)
|
||||
{
|
||||
var d = new CustomizationDrawer()
|
||||
{
|
||||
_customize = customize,
|
||||
_equip = equip,
|
||||
_actors = actors,
|
||||
};
|
||||
|
||||
|
||||
if (!ImGui.CollapsingHeader("Character Customization"))
|
||||
return;
|
||||
|
||||
using var disabled = ImRaii.Disabled(locked);
|
||||
|
||||
d.DrawRaceGenderSelector();
|
||||
|
||||
d._set = Glamourer.Customization.GetList(customize.Clan, customize.Gender);
|
||||
|
||||
foreach (var id in d._set.Order[CharaMakeParams.MenuType.Percentage])
|
||||
d.PercentageSelector(id);
|
||||
|
||||
Functions.IteratePairwise(d._set.Order[CharaMakeParams.MenuType.IconSelector], d.DrawIconSelector, ImGui.SameLine);
|
||||
|
||||
d.DrawMultiIconSelector();
|
||||
|
||||
foreach (var id in d._set.Order[CharaMakeParams.MenuType.ListSelector])
|
||||
d.DrawListSelector(id);
|
||||
|
||||
Functions.IteratePairwise(d._set.Order[CharaMakeParams.MenuType.ColorPicker], d.DrawColorPicker, ImGui.SameLine);
|
||||
|
||||
d.Checkbox(d._set.Option(CustomizationId.HighlightsOnFlag), customize.HighlightsOn, b => customize.HighlightsOn = b);
|
||||
var xPos = d._inputIntSize + d._framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X;
|
||||
ImGui.SameLine(xPos);
|
||||
d.Checkbox($"{Glamourer.Customization.GetName(CustomName.Reverse)} {d._set.Option(CustomizationId.FacePaint)}",
|
||||
customize.FacePaintReversed, b => customize.FacePaintReversed = b);
|
||||
d.Checkbox($"{Glamourer.Customization.GetName(CustomName.IrisSmall)} {Glamourer.Customization.GetName(CustomName.IrisSize)}",
|
||||
customize.SmallIris, b => customize.SmallIris = b);
|
||||
|
||||
if (customize.Race != Race.Hrothgar)
|
||||
{
|
||||
ImGui.SameLine(xPos);
|
||||
d.Checkbox(d._set.Option(CustomizationId.LipColor), customize.Lipstick, b => customize.Lipstick = b);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Draw(Customize customize, IReadOnlyCollection<Actor> actors, bool locked = false)
|
||||
=> Draw(customize, CharacterEquip.Null, actors, locked);
|
||||
|
||||
public static void Draw(Customize customize, CharacterEquip equip, bool locked = false)
|
||||
=> Draw(customize, equip, Array.Empty<Actor>(), locked);
|
||||
|
||||
public static void Draw(Customize customize, bool locked = false)
|
||||
=> Draw(customize, CharacterEquip.Null, Array.Empty<Actor>(), locked);
|
||||
|
||||
// Set state for drawing of current customization.
|
||||
private CustomizationId _currentId;
|
||||
private CustomizationByteValue _currentByte = CustomizationByteValue.Zero;
|
||||
private int _currentCount;
|
||||
private string _currentOption = string.Empty;
|
||||
|
||||
// Prepare a new customization option.
|
||||
private ImRaii.Id SetId(CustomizationId id)
|
||||
{
|
||||
_currentId = id;
|
||||
_currentByte = _customize[id];
|
||||
_currentCount = _set.Count(id, _customize.Face);
|
||||
_currentOption = _set.Option(id);
|
||||
return ImRaii.PushId((int)id);
|
||||
}
|
||||
|
||||
// Update the current id with a value,
|
||||
// also update actors if any.
|
||||
private void UpdateValue(CustomizationByteValue value)
|
||||
{
|
||||
if (_customize[_currentId] == value)
|
||||
return;
|
||||
|
||||
_customize[_currentId] = value;
|
||||
UpdateActors();
|
||||
}
|
||||
|
||||
// Update all relevant Actors by calling the UpdateCustomize game function.
|
||||
private void UpdateActors()
|
||||
{
|
||||
foreach (var actor in _actors.Where(a => a && a.DrawObject))
|
||||
Glamourer.RedrawManager.UpdateCustomize(actor.DrawObject, _customize);
|
||||
}
|
||||
}
|
||||
51
Glamourer/Gui/Customization/CustomizationDrawer.Multi.cs
Normal file
51
Glamourer/Gui/Customization/CustomizationDrawer.Multi.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Glamourer.Customization;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
|
||||
namespace Glamourer.Gui.Customization;
|
||||
|
||||
internal partial class CustomizationDrawer
|
||||
{
|
||||
// Only used for facial features, so fixed ID.
|
||||
private void DrawMultiIconSelector()
|
||||
{
|
||||
using var _ = SetId(CustomizationId.FacialFeaturesTattoos);
|
||||
using var bigGroup = ImRaii.Group();
|
||||
|
||||
DrawMultiIcons();
|
||||
ImGui.SameLine();
|
||||
using var group = ImRaii.Group();
|
||||
ImGui.Dummy(new Vector2(0, ImGui.GetTextLineHeightWithSpacing() + ImGui.GetStyle().ItemSpacing.Y / 2));
|
||||
|
||||
_currentCount = 256;
|
||||
PercentageInputInt();
|
||||
|
||||
ImGui.TextUnformatted(_set.Option(CustomizationId.FacialFeaturesTattoos));
|
||||
}
|
||||
|
||||
private void DrawMultiIcons()
|
||||
{
|
||||
using var _ = ImRaii.Group();
|
||||
for (var i = 0; i < _currentCount; ++i)
|
||||
{
|
||||
var enabled = _customize.FacialFeatures[i];
|
||||
var feature = _set.FacialFeature(_customize.Face, i);
|
||||
var icon = i == _currentCount - 1
|
||||
? LegacyTattoo ?? Glamourer.Customization.GetIcon(feature.IconId)
|
||||
: Glamourer.Customization.GetIcon(feature.IconId);
|
||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X,
|
||||
Vector4.Zero, enabled ? Vector4.One : RedTint))
|
||||
{
|
||||
_customize.FacialFeatures.Set(i, !enabled);
|
||||
UpdateActors();
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||
if (i % 4 != 3)
|
||||
ImGui.SameLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
104
Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs
Normal file
104
Glamourer/Gui/Customization/CustomizationDrawer.Simple.cs
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Glamourer.Customization;
|
||||
using ImGuiNET;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
|
||||
namespace Glamourer.Gui.Customization;
|
||||
|
||||
internal partial class CustomizationDrawer
|
||||
{
|
||||
private void DrawListSelector(CustomizationId id)
|
||||
{
|
||||
using var _ = SetId(id);
|
||||
using var bigGroup = ImRaii.Group();
|
||||
|
||||
ListCombo();
|
||||
ImGui.SameLine();
|
||||
ListInputInt();
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_currentOption);
|
||||
}
|
||||
|
||||
private void ListCombo()
|
||||
{
|
||||
ImGui.SetNextItemWidth(_comboSelectorSize * ImGui.GetIO().FontGlobalScale);
|
||||
using var combo = ImRaii.Combo("##combo", $"{_currentOption} #{_currentByte.Value + 1}");
|
||||
|
||||
if (!combo)
|
||||
return;
|
||||
|
||||
for (var i = 0; i < _currentCount; ++i)
|
||||
{
|
||||
if (ImGui.Selectable($"{_currentOption} #{i + 1}##combo", i == _currentByte.Value))
|
||||
UpdateValue((CustomizationByteValue)i);
|
||||
}
|
||||
}
|
||||
|
||||
private void ListInputInt()
|
||||
{
|
||||
var tmp = _currentByte.Value + 1;
|
||||
ImGui.SetNextItemWidth(_inputIntSize);
|
||||
if (ImGui.InputInt("##text", ref tmp, 1, 1) && tmp > 0 && tmp <= _currentCount)
|
||||
UpdateValue((CustomizationByteValue)Math.Clamp(tmp - 1, 0, _currentCount - 1));
|
||||
ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]");
|
||||
}
|
||||
|
||||
private void PercentageSelector(CustomizationId id)
|
||||
{
|
||||
using var _ = SetId(id);
|
||||
using var bigGroup = ImRaii.Group();
|
||||
|
||||
DrawPercentageSlider();
|
||||
ImGui.SameLine();
|
||||
PercentageInputInt();
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_currentOption);
|
||||
}
|
||||
|
||||
private void DrawPercentageSlider()
|
||||
{
|
||||
var tmp = (int)_currentByte.Value;
|
||||
ImGui.SetNextItemWidth(_comboSelectorSize);
|
||||
if (ImGui.SliderInt("##slider", ref tmp, 0, _currentCount - 1, "%i", ImGuiSliderFlags.AlwaysClamp))
|
||||
UpdateValue((CustomizationByteValue)tmp);
|
||||
}
|
||||
|
||||
private void PercentageInputInt()
|
||||
{
|
||||
var tmp = (int)_currentByte.Value;
|
||||
ImGui.SetNextItemWidth(_inputIntSize);
|
||||
if (ImGui.InputInt("##text", ref tmp, 1, 1))
|
||||
UpdateValue((CustomizationByteValue)Math.Clamp(tmp, 0, _currentCount - 1));
|
||||
ImGuiUtil.HoverTooltip($"Input Range: [0, {_currentCount - 1}]");
|
||||
}
|
||||
|
||||
|
||||
// Draw one of the four checkboxes for single bool customization options.
|
||||
private void Checkbox(string label, bool current, Action<bool> setter)
|
||||
{
|
||||
var tmp = current;
|
||||
if (ImGui.Checkbox(label, ref tmp) && tmp != current)
|
||||
{
|
||||
setter(tmp);
|
||||
foreach (var actor in _actors.Where(a => a && a.DrawObject))
|
||||
Glamourer.RedrawManager.UpdateCustomize(actor.DrawObject, _customize);
|
||||
}
|
||||
}
|
||||
|
||||
// Integral input for an icon- or color based item.
|
||||
private void DataInputInt(int currentIndex)
|
||||
{
|
||||
++currentIndex;
|
||||
ImGui.SetNextItemWidth(_inputIntSize);
|
||||
if (ImGui.InputInt("##text", ref currentIndex, 1, 1))
|
||||
{
|
||||
currentIndex = Math.Clamp(currentIndex - 1, 0, _currentCount - 1);
|
||||
var data = _set.Data(_currentId, currentIndex, _customize.Face);
|
||||
UpdateValue(data.Value);
|
||||
}
|
||||
ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,5 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Interface;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Structs;
|
||||
|
|
@ -10,8 +9,6 @@ using OtterGui;
|
|||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using static Glamourer.Interop.Actor;
|
||||
using static Lumina.Data.Parsing.Layer.LayerCommon;
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
|
|
@ -19,13 +16,16 @@ internal partial class Interface
|
|||
{
|
||||
//public class EquipmentDrawer
|
||||
//{
|
||||
// private static
|
||||
// private static readonly IReadOnlyDictionary<StainId, Stain> Stains;
|
||||
//
|
||||
// private Race _race;
|
||||
// private Gender _gender;
|
||||
// private CharacterEquip _equip;
|
||||
// private IReadOnlyCollection<Actor> _actors = Array.Empty<Actor>();
|
||||
//
|
||||
// static EquipmentDrawer()
|
||||
// => Stains = GameData.Stains(Dalamud.GameData);
|
||||
//
|
||||
// public static void Draw(Customize customize, CharacterEquip equip, IReadOnlyCollection<Actor> actors, bool locked)
|
||||
// {
|
||||
// var d = new EquipmentDrawer()
|
||||
|
|
@ -44,13 +44,10 @@ internal partial class Interface
|
|||
// private bool DrawStainSelector(ComboWithFilter<Stain> stainCombo, EquipSlot slot, StainId stainIdx)
|
||||
// {
|
||||
// stainCombo.PostPreview = null;
|
||||
// if (_stains.TryGetValue((byte)stainIdx, out var stain))
|
||||
// {
|
||||
// var previewPush = PushColor(stain, ImGuiCol.FrameBg);
|
||||
// stainCombo.PostPreview = () => ImGui.PopStyleColor(previewPush);
|
||||
// }
|
||||
//
|
||||
// var found = Stains.TryGetValue(stainIdx, out var stain);
|
||||
// using var color = ImRaii.PushColor(ImGuiCol.FrameBg, stain.RgbaColor, found);
|
||||
// var change = stainCombo.Draw(string.Empty, out var newStain) && !newStain.RowIndex.Equals(stainIdx);
|
||||
// if ()
|
||||
// if (!change && (byte)stainIdx != 0)
|
||||
// {
|
||||
// ImGuiUtil.HoverTooltip("Right-click to clear.");
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Gui.Customization;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.State;
|
||||
using ImGuiNET;
|
||||
|
|
@ -57,12 +58,8 @@ internal partial class Interface
|
|||
if (_currentData.Valid)
|
||||
_currentSave.Update(_currentData.Objects[0]);
|
||||
|
||||
var d = _currentData.Objects[0].DrawObject.Pointer;
|
||||
var x = (*(delegate* unmanaged<Human*, byte>**)d)[50](d);
|
||||
ImGui.Text($"{x} {_currentData.Objects[0].ModelId}");
|
||||
if (x == 1)
|
||||
CustomizationDrawer.Draw(_currentSave.Data.Customize, _currentSave.Data.Equipment, _currentData.Objects,
|
||||
_identifier is Actor.SpecialIdentifier);
|
||||
CustomizationDrawer.Draw(_currentSave.Data.Customize, _currentSave.Data.Equipment, _currentData.Objects,
|
||||
_identifier is Actor.SpecialIdentifier);
|
||||
}
|
||||
|
||||
private const uint RedHeaderColor = 0xFF1818C0;
|
||||
|
|
|
|||
|
|
@ -1,402 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Logging;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Util;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Race = Penumbra.GameData.Enums.Race;
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
internal partial class Interface
|
||||
{
|
||||
private class CustomizationDrawer
|
||||
{
|
||||
private Customize _customize;
|
||||
private CharacterEquip _equip;
|
||||
private IReadOnlyCollection<Actor> _actors = Array.Empty<Actor>();
|
||||
private CustomizationSet _set = null!;
|
||||
|
||||
public static void Draw(Customize customize, CharacterEquip equip, IReadOnlyCollection<Actor> actors, bool locked)
|
||||
{
|
||||
var d = new CustomizationDrawer()
|
||||
{
|
||||
_customize = customize,
|
||||
_equip = equip,
|
||||
_actors = actors,
|
||||
};
|
||||
|
||||
|
||||
if (!ImGui.CollapsingHeader("Character Customization"))
|
||||
return;
|
||||
|
||||
using var disabled = ImRaii.Disabled(locked);
|
||||
|
||||
d.DrawRaceGenderSelector();
|
||||
|
||||
d._set = Glamourer.Customization.GetList(customize.Clan, customize.Gender);
|
||||
|
||||
foreach (var id in d._set.Order[CharaMakeParams.MenuType.Percentage])
|
||||
d.PercentageSelector(id);
|
||||
|
||||
Functions.IteratePairwise(d._set.Order[CharaMakeParams.MenuType.IconSelector], d.DrawIconSelector, ImGui.SameLine);
|
||||
|
||||
d.DrawMultiIconSelector();
|
||||
|
||||
foreach (var id in d._set.Order[CharaMakeParams.MenuType.ListSelector])
|
||||
d.DrawListSelector(id);
|
||||
|
||||
Functions.IteratePairwise(d._set.Order[CharaMakeParams.MenuType.ColorPicker], d.DrawColorPicker, ImGui.SameLine);
|
||||
|
||||
d.Checkbox(d._set.Option(CustomizationId.HighlightsOnFlag), customize.HighlightsOn, b => customize.HighlightsOn = b);
|
||||
var xPos = _inputIntSize + _framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X;
|
||||
ImGui.SameLine(xPos);
|
||||
d.Checkbox($"{Glamourer.Customization.GetName(CustomName.Reverse)} {d._set.Option(CustomizationId.FacePaint)}",
|
||||
customize.FacePaintReversed, b => customize.FacePaintReversed = b);
|
||||
d.Checkbox($"{Glamourer.Customization.GetName(CustomName.IrisSmall)} {Glamourer.Customization.GetName(CustomName.IrisSize)}",
|
||||
customize.SmallIris, b => customize.SmallIris = b);
|
||||
|
||||
if (customize.Race != Race.Hrothgar)
|
||||
{
|
||||
ImGui.SameLine(xPos);
|
||||
d.Checkbox(d._set.Option(CustomizationId.LipColor), customize.Lipstick, b => customize.Lipstick = b);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawRaceGenderSelector()
|
||||
{
|
||||
DrawGenderSelector();
|
||||
ImGui.SameLine();
|
||||
using var group = ImRaii.Group();
|
||||
DrawRaceCombo();
|
||||
var gender = Glamourer.Customization.GetName(CustomName.Gender);
|
||||
var clan = Glamourer.Customization.GetName(CustomName.Clan);
|
||||
ImGui.TextUnformatted($"{gender} & {clan}");
|
||||
}
|
||||
|
||||
private void DrawGenderSelector()
|
||||
{
|
||||
using var font = ImRaii.PushFont(UiBuilder.IconFont);
|
||||
var icon = _customize.Gender == Gender.Male ? FontAwesomeIcon.Mars : FontAwesomeIcon.Venus;
|
||||
var restricted = _customize.Race == Race.Hrothgar;
|
||||
if (restricted)
|
||||
icon = FontAwesomeIcon.MarsDouble;
|
||||
|
||||
if (!ImGuiUtil.DrawDisabledButton(icon.ToIconString(), _framedIconSize, string.Empty, restricted, true))
|
||||
return;
|
||||
|
||||
var gender = _customize.Gender == Gender.Male ? Gender.Female : Gender.Male;
|
||||
if (!_customize.ChangeGender(_equip, gender))
|
||||
return;
|
||||
|
||||
foreach (var actor in _actors.Where(a => a))
|
||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw, false);
|
||||
}
|
||||
|
||||
private void DrawRaceCombo()
|
||||
{
|
||||
ImGui.SetNextItemWidth(_raceSelectorWidth);
|
||||
using var combo = ImRaii.Combo("##subRaceCombo", _customize.ClanName());
|
||||
if (!combo)
|
||||
return;
|
||||
|
||||
foreach (var subRace in Enum.GetValues<SubRace>().Skip(1)) // Skip Unknown
|
||||
{
|
||||
if (ImGui.Selectable(CustomizeExtensions.ClanName(subRace, _customize.Gender), subRace == _customize.Clan)
|
||||
&& _customize.ChangeRace(_equip, subRace))
|
||||
foreach (var actor in _actors.Where(a => a && a.DrawObject))
|
||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw, false);
|
||||
}
|
||||
}
|
||||
|
||||
private void Checkbox(string label, bool current, Action<bool> setter)
|
||||
{
|
||||
var tmp = current;
|
||||
if (ImGui.Checkbox($"##{label}", ref tmp) && tmp != current)
|
||||
{
|
||||
setter(tmp);
|
||||
foreach (var actor in _actors.Where(a => a && a.DrawObject))
|
||||
Glamourer.RedrawManager.UpdateCustomize(actor.DrawObject, _customize);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(label);
|
||||
}
|
||||
|
||||
private void PercentageSelector(CustomizationId id)
|
||||
{
|
||||
using var bigGroup = ImRaii.Group();
|
||||
using var _ = ImRaii.PushId((int)id);
|
||||
int value = _customize[id];
|
||||
var count = _set.Count(id);
|
||||
ImGui.SetNextItemWidth(_comboSelectorSize);
|
||||
|
||||
void OnChange(int v)
|
||||
{
|
||||
_customize[id] = (byte)v;
|
||||
foreach (var actor in _actors.Where(a => a && a.DrawObject))
|
||||
Glamourer.RedrawManager.UpdateCustomize(actor.DrawObject, _customize);
|
||||
}
|
||||
|
||||
if (ImGui.SliderInt("##slider", ref value, 0, count - 1, "%i", ImGuiSliderFlags.AlwaysClamp))
|
||||
OnChange(value);
|
||||
|
||||
ImGui.SameLine();
|
||||
InputInt("##input", --value, 0, count - 1, OnChange);
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_set.OptionName[(int)id]);
|
||||
}
|
||||
|
||||
private static void InputInt(string label, int startValue, int minValue, int maxValue, Action<int> setter)
|
||||
{
|
||||
var tmp = startValue + 1;
|
||||
ImGui.SetNextItemWidth(_inputIntSize);
|
||||
if (ImGui.InputInt(label, ref tmp, 1, 1, ImGuiInputTextFlags.EnterReturnsTrue)
|
||||
&& tmp != startValue + 1
|
||||
&& tmp >= minValue
|
||||
&& tmp <= maxValue)
|
||||
setter(tmp);
|
||||
|
||||
ImGuiUtil.HoverTooltip($"Input Range: [{minValue}, {maxValue}]");
|
||||
}
|
||||
|
||||
private void DrawIconSelector(CustomizationId id)
|
||||
{
|
||||
const string popupName = "Style Picker";
|
||||
|
||||
using var bigGroup = ImRaii.Group();
|
||||
using var _ = ImRaii.PushId((int)id);
|
||||
var count = _set.Count(id, _customize.Face);
|
||||
var label = _set.Option(id);
|
||||
|
||||
var current = _set.DataByValue(id, _customize[id], out var custom);
|
||||
if (current < 0)
|
||||
{
|
||||
label = $"{label} (Custom #{_customize[id]})";
|
||||
current = 0;
|
||||
custom = _set.Data(id, 0);
|
||||
}
|
||||
|
||||
var icon = Glamourer.Customization.GetIcon(custom!.Value.IconId);
|
||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
|
||||
ImGui.OpenPopup(popupName);
|
||||
|
||||
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||
|
||||
void OnChange(int v)
|
||||
{
|
||||
var value = _set.Data(id, v - 1).Value;
|
||||
// Hrothgar hack
|
||||
if (_set.Race == Race.Hrothgar && id == CustomizationId.Face)
|
||||
value += 4;
|
||||
|
||||
if (_customize[id] == value)
|
||||
return;
|
||||
|
||||
_customize[id] = value;
|
||||
foreach (var actor in _actors.Where(a => a && a.DrawObject))
|
||||
Glamourer.RedrawManager.UpdateCustomize(actor.DrawObject, _customize);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
using var group = ImRaii.Group();
|
||||
InputInt("##text", current, 1, count, OnChange);
|
||||
|
||||
ImGui.TextUnformatted($"{label} ({custom.Value.Value})");
|
||||
|
||||
DrawIconPickerPopup(popupName, id, OnChange);
|
||||
}
|
||||
|
||||
private void DrawIconPickerPopup(string label, CustomizationId id, Action<int> setter)
|
||||
{
|
||||
using var popup = ImRaii.Popup(label, ImGuiWindowFlags.AlwaysAutoResize);
|
||||
if (!popup)
|
||||
return;
|
||||
|
||||
var count = _set.Count(id, _customize.Face);
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
var custom = _set.Data(id, i, _customize.Face);
|
||||
var icon = Glamourer.Customization.GetIcon(custom.IconId);
|
||||
using var group = ImRaii.Group();
|
||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
|
||||
{
|
||||
setter(custom.Value);
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||
|
||||
var text = custom.Value.ToString();
|
||||
var textWidth = ImGui.CalcTextSize(text).X;
|
||||
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + (_iconSize.X - textWidth + 2 * ImGui.GetStyle().FramePadding.X) / 2);
|
||||
ImGui.TextUnformatted(text);
|
||||
group.Dispose();
|
||||
|
||||
if (i % 8 != 7)
|
||||
ImGui.SameLine();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawColorPicker(CustomizationId id)
|
||||
{
|
||||
const string popupName = "Color Picker";
|
||||
using var _ = ImRaii.PushId((int)id);
|
||||
var count = _set.Count(id);
|
||||
var label = _set.Option(id);
|
||||
var (current, custom) = GetCurrentCustomization(id);
|
||||
|
||||
if (ImGui.ColorButton($"{current + 1}##color", ImGui.ColorConvertU32ToFloat4(custom.Color), ImGuiColorEditFlags.None,
|
||||
_framedIconSize))
|
||||
ImGui.OpenPopup(popupName);
|
||||
|
||||
ImGui.SameLine();
|
||||
|
||||
void OnChange(int v)
|
||||
{
|
||||
_customize[id] = _set.Data(id, v).Value;
|
||||
foreach (var actor in _actors.Where(a => a && a.DrawObject))
|
||||
Glamourer.RedrawManager.UpdateCustomize(actor.DrawObject, _customize);
|
||||
}
|
||||
|
||||
using (var group = ImRaii.Group())
|
||||
{
|
||||
InputInt("##text", current, 1, count, OnChange);
|
||||
ImGui.TextUnformatted(label);
|
||||
}
|
||||
|
||||
DrawColorPickerPopup(popupName, id, OnChange);
|
||||
}
|
||||
|
||||
private (int, Customization.Customization) GetCurrentCustomization(CustomizationId id)
|
||||
{
|
||||
var current = _set.DataByValue(id, _customize[id], out var custom);
|
||||
if (_set.IsAvailable(id) && current < 0)
|
||||
{
|
||||
PluginLog.Warning($"Read invalid customization value {_customize[id]} for {id}.");
|
||||
current = 0;
|
||||
custom = _set.Data(id, 0);
|
||||
}
|
||||
|
||||
return (current, custom!.Value);
|
||||
}
|
||||
|
||||
private void DrawColorPickerPopup(string label, CustomizationId id, Action<int> setter)
|
||||
{
|
||||
using var popup = ImRaii.Popup(label, ImGuiWindowFlags.AlwaysAutoResize);
|
||||
if (!popup)
|
||||
return;
|
||||
|
||||
var count = _set.Count(id);
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
|
||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
var custom = _set.Data(id, i);
|
||||
if (ImGui.ColorButton((i + 1).ToString(), ImGui.ColorConvertU32ToFloat4(custom.Color)))
|
||||
{
|
||||
setter(custom.Value);
|
||||
ImGui.CloseCurrentPopup();
|
||||
}
|
||||
|
||||
if (i % 8 != 7)
|
||||
ImGui.SameLine();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawMultiIconSelector()
|
||||
{
|
||||
using var bigGroup = ImRaii.Group();
|
||||
using var _ = ImRaii.PushId((int)CustomizationId.FacialFeaturesTattoos);
|
||||
|
||||
void OnChange(int v)
|
||||
{
|
||||
_customize[CustomizationId.FacialFeaturesTattoos] = (byte)v;
|
||||
foreach (var actor in _actors.Where(a => a && a.DrawObject))
|
||||
Glamourer.RedrawManager.UpdateCustomize(actor.DrawObject, _customize);
|
||||
}
|
||||
|
||||
DrawMultiIcons();
|
||||
ImGui.SameLine();
|
||||
int value = _customize[CustomizationId.FacialFeaturesTattoos];
|
||||
using var group = ImRaii.Group();
|
||||
ImGui.Dummy(new Vector2(0, ImGui.GetTextLineHeightWithSpacing() + ImGui.GetStyle().ItemSpacing.Y / 2));
|
||||
InputInt(string.Empty, --value, 0, 255, OnChange);
|
||||
|
||||
ImGui.TextUnformatted(_set.Option(CustomizationId.FacialFeaturesTattoos));
|
||||
}
|
||||
|
||||
private void DrawMultiIcons()
|
||||
{
|
||||
using var _ = ImRaii.Group();
|
||||
var face = _customize.Face;
|
||||
|
||||
var ret = false;
|
||||
var count = _set.Count(CustomizationId.FacialFeaturesTattoos);
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
var enabled = _customize.FacialFeatures[i];
|
||||
var feature = _set.FacialFeature(face, i);
|
||||
var icon = i == count - 1
|
||||
? LegacyTattoo ?? Glamourer.Customization.GetIcon(feature.IconId)
|
||||
: Glamourer.Customization.GetIcon(feature.IconId);
|
||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X,
|
||||
Vector4.Zero, enabled ? Vector4.One : RedTint))
|
||||
{
|
||||
_customize.FacialFeatures.Set(i, !enabled);
|
||||
foreach (var actor in _actors.Where(a => a && a.DrawObject))
|
||||
Glamourer.RedrawManager.UpdateCustomize(actor.DrawObject, _customize);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||
if (i % 4 != 3)
|
||||
ImGui.SameLine();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawListSelector(CustomizationId id)
|
||||
{
|
||||
using var _ = ImRaii.PushId((int)id);
|
||||
using var bigGroup = ImRaii.Group();
|
||||
int current = _customize[id];
|
||||
var count = _set.Count(id);
|
||||
|
||||
void OnChange(int v)
|
||||
{
|
||||
_customize[id] = (byte)v;
|
||||
foreach (var actor in _actors.Where(a => a && a.DrawObject))
|
||||
Glamourer.RedrawManager.UpdateCustomize(actor.DrawObject, _customize);
|
||||
}
|
||||
|
||||
ImGui.SetNextItemWidth(_comboSelectorSize * ImGui.GetIO().FontGlobalScale);
|
||||
using (var combo = ImRaii.Combo("##combo", $"{_set.Option(id)} #{current + 1}"))
|
||||
{
|
||||
if (combo)
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
if (!ImGui.Selectable($"{_set.Option(id)} #{i + 1}##combo", i == current) || i == current)
|
||||
continue;
|
||||
|
||||
OnChange(i);
|
||||
}
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
InputInt("##text", current, 1, count, OnChange);
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.TextUnformatted(_set.Option(id));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Numerics;
|
||||
using Glamourer.Gui.Customization;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.State;
|
||||
using ImGuiNET;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,4 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using ImGuiNET;
|
||||
|
||||
|
|
@ -8,41 +6,12 @@ namespace Glamourer.Gui;
|
|||
|
||||
internal partial class Interface
|
||||
{
|
||||
private static readonly ImGuiScene.TextureWrap? LegacyTattoo = GetLegacyTattooIcon();
|
||||
private static readonly Vector4 RedTint = new(0.6f, 0.3f, 0.3f, 1f);
|
||||
|
||||
private static Vector2 _iconSize = Vector2.Zero;
|
||||
private static Vector2 _framedIconSize = Vector2.Zero;
|
||||
private static Vector2 _spacing = Vector2.Zero;
|
||||
private static float _actorSelectorWidth;
|
||||
private static float _inputIntSize;
|
||||
private static float _comboSelectorSize;
|
||||
private static float _raceSelectorWidth;
|
||||
|
||||
private static void UpdateState()
|
||||
{
|
||||
// General
|
||||
_spacing = _spacing with { Y = ImGui.GetTextLineHeightWithSpacing() / 2 };
|
||||
_actorSelectorWidth = 200 * ImGuiHelpers.GlobalScale;
|
||||
|
||||
// Customize
|
||||
_iconSize = new Vector2(ImGui.GetTextLineHeightWithSpacing() * 2);
|
||||
_framedIconSize = _iconSize + 2 * ImGui.GetStyle().FramePadding;
|
||||
_inputIntSize = 2 * _framedIconSize.X + ImGui.GetStyle().ItemSpacing.X;
|
||||
_comboSelectorSize = 4 * _framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X;
|
||||
_raceSelectorWidth = _inputIntSize + _comboSelectorSize - _framedIconSize.X;
|
||||
}
|
||||
|
||||
private static ImGuiScene.TextureWrap? GetLegacyTattooIcon()
|
||||
{
|
||||
using var resource = Assembly.GetExecutingAssembly().GetManifestResourceStream("Glamourer.LegacyTattoo.raw");
|
||||
if (resource == null)
|
||||
return null;
|
||||
|
||||
var rawImage = new byte[resource.Length];
|
||||
var length = resource.Read(rawImage, 0, (int)resource.Length);
|
||||
return length == resource.Length
|
||||
? Dalamud.PluginInterface.UiBuilder.LoadImageRaw(rawImage, 192, 192, 4)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ using System.Linq;
|
|||
using System.Numerics;
|
||||
using Dalamud.Interface.Windowing;
|
||||
using Dalamud.Logging;
|
||||
using Glamourer.Gui.Customization;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Raii;
|
||||
|
||||
|
|
@ -56,6 +57,7 @@ internal partial class Interface : Window, IDisposable
|
|||
public void Dispose()
|
||||
{
|
||||
Dalamud.PluginInterface.UiBuilder.OpenConfigUi -= Toggle;
|
||||
CustomizationDrawer.Dispose();
|
||||
}
|
||||
|
||||
private static string GetLabel()
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ public struct CharacterData
|
|||
|
||||
public interface ICharacterData
|
||||
{
|
||||
public CharacterData Data { get; }
|
||||
public ref CharacterData Data { get; }
|
||||
|
||||
//public bool ApplyModel();
|
||||
//public bool ApplyCustomize(Customize target);
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ namespace Glamourer.State;
|
|||
|
||||
public unsafe class CurrentDesign : ICharacterData
|
||||
{
|
||||
public CharacterData Data
|
||||
=> _drawData;
|
||||
public ref CharacterData Data
|
||||
=> ref _drawData;
|
||||
|
||||
private CharacterData _drawData;
|
||||
private CharacterData _initialData;
|
||||
|
|
|
|||
|
|
@ -90,12 +90,12 @@ public static unsafe class CustomizeExtensions
|
|||
customize.Load(newCustomize);
|
||||
}
|
||||
|
||||
public static bool ChangeCustomization(this Customize customize, CharacterEquip equip, CustomizationId id, byte value)
|
||||
public static bool ChangeCustomization(this Customize customize, CharacterEquip equip, CustomizationId id, CustomizationByteValue value)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case CustomizationId.Race: return customize.ChangeRace(equip, (SubRace)value);
|
||||
case CustomizationId.Gender: return customize.ChangeGender(equip, (Gender)value);
|
||||
case CustomizationId.Race: return customize.ChangeRace(equip, (SubRace)value.Value);
|
||||
case CustomizationId.Gender: return customize.ChangeGender(equip, (Gender)value.Value);
|
||||
}
|
||||
|
||||
if (customize[id] == value)
|
||||
|
|
@ -113,17 +113,17 @@ public static unsafe class CustomizeExtensions
|
|||
{
|
||||
switch (id)
|
||||
{
|
||||
case CustomizationId.Race: break;
|
||||
case CustomizationId.Clan: break;
|
||||
case CustomizationId.BodyType: break;
|
||||
case CustomizationId.Gender: break;
|
||||
case CustomizationId.FacialFeaturesTattoos: break;
|
||||
case CustomizationId.HighlightsOnFlag: break;
|
||||
case CustomizationId.Face: break;
|
||||
case CustomizationId.Race: break;
|
||||
case CustomizationId.Clan: break;
|
||||
case CustomizationId.BodyType: break;
|
||||
case CustomizationId.Gender: break;
|
||||
case CustomizationId.FacialFeaturesTattoos: break;
|
||||
case CustomizationId.HighlightsOnFlag: break;
|
||||
case CustomizationId.Face: break;
|
||||
default:
|
||||
var count = set.Count(id);
|
||||
if (set.DataByValue(id, customize[id], out _) < 0)
|
||||
customize[id] = count == 0 ? (byte)0 : set.Data(id, 0).Value;
|
||||
customize[id] = count == 0 ? CustomizationByteValue.Zero : set.Data(id, 0).Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -131,7 +131,7 @@ public static unsafe class CustomizeExtensions
|
|||
|
||||
private static void FixRestrictedGear(Customize customize, CharacterEquip equip, Gender gender, Race race)
|
||||
{
|
||||
if (race == customize.Race && gender == customize.Gender)
|
||||
if (!equip || race == customize.Race && gender == customize.Gender)
|
||||
return;
|
||||
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue