mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +01:00
Some more reworking
This commit is contained in:
parent
6a4b5fc3b2
commit
dad146d043
41 changed files with 1714 additions and 1320 deletions
|
|
@ -22,21 +22,23 @@ public class CharaMakeParams : ExcelRow
|
||||||
IconSelector = 1,
|
IconSelector = 1,
|
||||||
ColorPicker = 2,
|
ColorPicker = 2,
|
||||||
DoubleColorPicker = 3,
|
DoubleColorPicker = 3,
|
||||||
MultiIconSelector = 4,
|
IconCheckmark = 4,
|
||||||
Percentage = 5,
|
Percentage = 5,
|
||||||
|
Checkmark = 6, // custom
|
||||||
|
Nothing = 7, // custom
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct Menu
|
public struct Menu
|
||||||
{
|
{
|
||||||
public uint Id;
|
public uint Id;
|
||||||
public byte InitVal;
|
public byte InitVal;
|
||||||
public MenuType Type;
|
public MenuType Type;
|
||||||
public byte Size;
|
public byte Size;
|
||||||
public byte LookAt;
|
public byte LookAt;
|
||||||
public uint Mask;
|
public uint Mask;
|
||||||
public CustomizationId Customization;
|
public uint Customize;
|
||||||
public uint[] Values;
|
public uint[] Values;
|
||||||
public byte[] Graphic;
|
public byte[] Graphic;
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct FacialFeatures
|
public struct FacialFeatures
|
||||||
|
|
@ -51,7 +53,7 @@ public class CharaMakeParams : ExcelRow
|
||||||
public Menu[] Menus { get; set; } = new Menu[NumMenus];
|
public Menu[] Menus { get; set; } = new Menu[NumMenus];
|
||||||
public byte[] Voices { get; set; } = new byte[NumVoices];
|
public byte[] Voices { get; set; } = new byte[NumVoices];
|
||||||
public FacialFeatures[] FacialFeatureByFace { get; set; } = new FacialFeatures[NumFaces];
|
public FacialFeatures[] FacialFeatureByFace { get; set; } = new FacialFeatures[NumFaces];
|
||||||
|
|
||||||
public CharaMakeType.CharaMakeTypeUnkData3347Obj[] Equip { get; set; } = new CharaMakeType.CharaMakeTypeUnkData3347Obj[NumEquip];
|
public CharaMakeType.CharaMakeTypeUnkData3347Obj[] Equip { get; set; } = new CharaMakeType.CharaMakeTypeUnkData3347Obj[NumEquip];
|
||||||
|
|
||||||
public override void PopulateData(RowParser parser, Lumina.GameData gameData, Language language)
|
public override void PopulateData(RowParser parser, Lumina.GameData gameData, Language language)
|
||||||
|
|
@ -64,15 +66,15 @@ public class CharaMakeParams : ExcelRow
|
||||||
var currentOffset = 0;
|
var currentOffset = 0;
|
||||||
for (var i = 0; i < NumMenus; ++i)
|
for (var i = 0; i < NumMenus; ++i)
|
||||||
{
|
{
|
||||||
currentOffset = 3 + i;
|
currentOffset = 3 + i;
|
||||||
Menus[i].Id = parser.ReadColumn<uint>(0 * NumMenus + currentOffset);
|
Menus[i].Id = parser.ReadColumn<uint>(0 * NumMenus + currentOffset);
|
||||||
Menus[i].InitVal = parser.ReadColumn<byte>(1 * NumMenus + currentOffset);
|
Menus[i].InitVal = parser.ReadColumn<byte>(1 * NumMenus + currentOffset);
|
||||||
Menus[i].Type = (MenuType)parser.ReadColumn<byte>(2 * NumMenus + currentOffset);
|
Menus[i].Type = (MenuType)parser.ReadColumn<byte>(2 * NumMenus + currentOffset);
|
||||||
Menus[i].Size = parser.ReadColumn<byte>(3 * NumMenus + currentOffset);
|
Menus[i].Size = parser.ReadColumn<byte>(3 * NumMenus + currentOffset);
|
||||||
Menus[i].LookAt = parser.ReadColumn<byte>(4 * NumMenus + currentOffset);
|
Menus[i].LookAt = parser.ReadColumn<byte>(4 * NumMenus + currentOffset);
|
||||||
Menus[i].Mask = parser.ReadColumn<uint>(5 * NumMenus + currentOffset);
|
Menus[i].Mask = parser.ReadColumn<uint>(5 * NumMenus + currentOffset);
|
||||||
Menus[i].Customization = (CustomizationId)parser.ReadColumn<uint>(6 * NumMenus + currentOffset);
|
Menus[i].Customize = parser.ReadColumn<uint>(6 * NumMenus + currentOffset);
|
||||||
Menus[i].Values = new uint[Menus[i].Size];
|
Menus[i].Values = new uint[Menus[i].Size];
|
||||||
|
|
||||||
switch (Menus[i].Type)
|
switch (Menus[i].Type)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,107 +0,0 @@
|
||||||
using System;
|
|
||||||
using Penumbra.GameData.Enums;
|
|
||||||
|
|
||||||
namespace Glamourer.Customization;
|
|
||||||
|
|
||||||
public enum CustomizationId : byte
|
|
||||||
{
|
|
||||||
Race = 0,
|
|
||||||
Gender = 1,
|
|
||||||
BodyType = 2,
|
|
||||||
Height = 3,
|
|
||||||
Clan = 4,
|
|
||||||
Face = 5,
|
|
||||||
Hairstyle = 6,
|
|
||||||
HighlightsOnFlag = 7,
|
|
||||||
SkinColor = 8,
|
|
||||||
EyeColorR = 9,
|
|
||||||
HairColor = 10,
|
|
||||||
HighlightColor = 11,
|
|
||||||
FacialFeaturesTattoos = 12, // Bitmask, 1-7 per face, 8 is 1.0 tattoo
|
|
||||||
TattooColor = 13,
|
|
||||||
Eyebrows = 14,
|
|
||||||
EyeColorL = 15,
|
|
||||||
EyeShape = 16, // Flag 128 for Small
|
|
||||||
Nose = 17,
|
|
||||||
Jaw = 18,
|
|
||||||
Mouth = 19, // Flag 128 for Lip Color set
|
|
||||||
LipColor = 20, // Flag 128 for Light instead of Dark
|
|
||||||
MuscleToneOrTailEarLength = 21,
|
|
||||||
TailEarShape = 22,
|
|
||||||
BustSize = 23,
|
|
||||||
FacePaint = 24,
|
|
||||||
FacePaintColor = 25, // Flag 128 for Light instead of Dark.
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class CustomizationExtensions
|
|
||||||
{
|
|
||||||
public static string ToDefaultName(this CustomizationId customizationId)
|
|
||||||
=> customizationId switch
|
|
||||||
{
|
|
||||||
CustomizationId.Race => "Race",
|
|
||||||
CustomizationId.Gender => "Gender",
|
|
||||||
CustomizationId.BodyType => "Body Type",
|
|
||||||
CustomizationId.Height => "Height",
|
|
||||||
CustomizationId.Clan => "Clan",
|
|
||||||
CustomizationId.Face => "Head Style",
|
|
||||||
CustomizationId.Hairstyle => "Hair Style",
|
|
||||||
CustomizationId.HighlightsOnFlag => "Highlights",
|
|
||||||
CustomizationId.SkinColor => "Skin Color",
|
|
||||||
CustomizationId.EyeColorR => "Right Eye Color",
|
|
||||||
CustomizationId.HairColor => "Hair Color",
|
|
||||||
CustomizationId.HighlightColor => "Highlights Color",
|
|
||||||
CustomizationId.FacialFeaturesTattoos => "Facial Features",
|
|
||||||
CustomizationId.TattooColor => "Tattoo Color",
|
|
||||||
CustomizationId.Eyebrows => "Eyebrow Style",
|
|
||||||
CustomizationId.EyeColorL => "Left Eye Color",
|
|
||||||
CustomizationId.EyeShape => "Eye Shape",
|
|
||||||
CustomizationId.Nose => "Nose Style",
|
|
||||||
CustomizationId.Jaw => "Jaw Style",
|
|
||||||
CustomizationId.Mouth => "Mouth Style",
|
|
||||||
CustomizationId.MuscleToneOrTailEarLength => "Muscle Tone",
|
|
||||||
CustomizationId.TailEarShape => "Tail Shape",
|
|
||||||
CustomizationId.BustSize => "Bust Size",
|
|
||||||
CustomizationId.FacePaint => "Face Paint",
|
|
||||||
CustomizationId.FacePaintColor => "Face Paint Color",
|
|
||||||
CustomizationId.LipColor => "Lip Color",
|
|
||||||
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(customizationId), customizationId, null),
|
|
||||||
};
|
|
||||||
|
|
||||||
public static CharaMakeParams.MenuType ToType(this CustomizationId customizationId, Race race = Race.Hyur)
|
|
||||||
=> customizationId switch
|
|
||||||
{
|
|
||||||
CustomizationId.Race => CharaMakeParams.MenuType.IconSelector,
|
|
||||||
CustomizationId.Gender => CharaMakeParams.MenuType.IconSelector,
|
|
||||||
CustomizationId.BodyType => CharaMakeParams.MenuType.IconSelector,
|
|
||||||
CustomizationId.Height => CharaMakeParams.MenuType.Percentage,
|
|
||||||
CustomizationId.Clan => CharaMakeParams.MenuType.IconSelector,
|
|
||||||
CustomizationId.Face => CharaMakeParams.MenuType.IconSelector,
|
|
||||||
CustomizationId.Hairstyle => CharaMakeParams.MenuType.IconSelector,
|
|
||||||
CustomizationId.HighlightsOnFlag => CharaMakeParams.MenuType.ListSelector,
|
|
||||||
CustomizationId.SkinColor => CharaMakeParams.MenuType.ColorPicker,
|
|
||||||
CustomizationId.EyeColorR => CharaMakeParams.MenuType.ColorPicker,
|
|
||||||
CustomizationId.HairColor => CharaMakeParams.MenuType.ColorPicker,
|
|
||||||
CustomizationId.HighlightColor => CharaMakeParams.MenuType.ColorPicker,
|
|
||||||
CustomizationId.FacialFeaturesTattoos => CharaMakeParams.MenuType.MultiIconSelector,
|
|
||||||
CustomizationId.TattooColor => CharaMakeParams.MenuType.ColorPicker,
|
|
||||||
CustomizationId.Eyebrows => CharaMakeParams.MenuType.ListSelector,
|
|
||||||
CustomizationId.EyeColorL => CharaMakeParams.MenuType.ColorPicker,
|
|
||||||
CustomizationId.EyeShape => CharaMakeParams.MenuType.ListSelector,
|
|
||||||
CustomizationId.Nose => CharaMakeParams.MenuType.ListSelector,
|
|
||||||
CustomizationId.Jaw => CharaMakeParams.MenuType.ListSelector,
|
|
||||||
CustomizationId.Mouth => CharaMakeParams.MenuType.ListSelector,
|
|
||||||
CustomizationId.MuscleToneOrTailEarLength => CharaMakeParams.MenuType.Percentage,
|
|
||||||
CustomizationId.BustSize => CharaMakeParams.MenuType.Percentage,
|
|
||||||
CustomizationId.FacePaint => CharaMakeParams.MenuType.IconSelector,
|
|
||||||
CustomizationId.FacePaintColor => CharaMakeParams.MenuType.ColorPicker,
|
|
||||||
|
|
||||||
CustomizationId.TailEarShape => race is Race.Elezen or Race.Lalafell
|
|
||||||
? CharaMakeParams.MenuType.ListSelector
|
|
||||||
: CharaMakeParams.MenuType.IconSelector,
|
|
||||||
CustomizationId.LipColor => race == Race.Hrothgar
|
|
||||||
? CharaMakeParams.MenuType.IconSelector
|
|
||||||
: CharaMakeParams.MenuType.ColorPicker,
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(customizationId), customizationId, null),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -13,9 +13,9 @@ namespace Glamourer.Customization
|
||||||
private CustomizationManager()
|
private CustomizationManager()
|
||||||
{ }
|
{ }
|
||||||
|
|
||||||
public static ICustomizationManager Create(DalamudPluginInterface pi, DataManager gameData, ClientLanguage language)
|
public static ICustomizationManager Create(DalamudPluginInterface pi, DataManager gameData)
|
||||||
{
|
{
|
||||||
_options ??= new CustomizationOptions(pi, gameData, language);
|
_options ??= new CustomizationOptions(pi, gameData);
|
||||||
return new CustomizationManager();
|
return new CustomizationManager();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,9 +64,9 @@ public partial class CustomizationOptions
|
||||||
public string GetName(CustomName name)
|
public string GetName(CustomName name)
|
||||||
=> _names[(int)name];
|
=> _names[(int)name];
|
||||||
|
|
||||||
internal CustomizationOptions(DalamudPluginInterface pi, DataManager gameData, ClientLanguage language)
|
internal CustomizationOptions(DalamudPluginInterface pi, DataManager gameData)
|
||||||
{
|
{
|
||||||
var tmp = new TemporaryData(gameData, this, language);
|
var tmp = new TemporaryData(gameData, this);
|
||||||
_icons = new IconStorage(pi, gameData, _customizationSets.Length * 50);
|
_icons = new IconStorage(pi, gameData, _customizationSets.Length * 50);
|
||||||
Valid = tmp.Valid;
|
Valid = tmp.Valid;
|
||||||
SetNames(gameData, tmp);
|
SetNames(gameData, tmp);
|
||||||
|
|
@ -149,15 +149,15 @@ public partial class CustomizationOptions
|
||||||
HighlightColors = _highlightPicker,
|
HighlightColors = _highlightPicker,
|
||||||
TattooColors = _tattooColorPicker,
|
TattooColors = _tattooColorPicker,
|
||||||
LipColorsDark = hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark,
|
LipColorsDark = hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark,
|
||||||
LipColorsLight = hrothgar ? Array.Empty<CustomizationData>() : _lipColorPickerLight,
|
LipColorsLight = hrothgar ? Array.Empty<CustomizeData>() : _lipColorPickerLight,
|
||||||
FacePaintColorsDark = _facePaintColorPickerDark,
|
FacePaintColorsDark = _facePaintColorPickerDark,
|
||||||
FacePaintColorsLight = _facePaintColorPickerLight,
|
FacePaintColorsLight = _facePaintColorPickerLight,
|
||||||
Faces = GetFaces(row),
|
Faces = GetFaces(row),
|
||||||
NumEyebrows = GetListSize(row, CustomizationId.Eyebrows),
|
NumEyebrows = GetListSize(row, CustomizeIndex.Eyebrows),
|
||||||
NumEyeShapes = GetListSize(row, CustomizationId.EyeShape),
|
NumEyeShapes = GetListSize(row, CustomizeIndex.EyeShape),
|
||||||
NumNoseShapes = GetListSize(row, CustomizationId.Nose),
|
NumNoseShapes = GetListSize(row, CustomizeIndex.Nose),
|
||||||
NumJawShapes = GetListSize(row, CustomizationId.Jaw),
|
NumJawShapes = GetListSize(row, CustomizeIndex.Jaw),
|
||||||
NumMouthShapes = GetListSize(row, CustomizationId.Mouth),
|
NumMouthShapes = GetListSize(row, CustomizeIndex.Mouth),
|
||||||
FacePaints = GetFacePaints(race, gender),
|
FacePaints = GetFacePaints(race, gender),
|
||||||
TailEarShapes = GetTailEarShapes(row),
|
TailEarShapes = GetTailEarShapes(row),
|
||||||
};
|
};
|
||||||
|
|
@ -171,7 +171,7 @@ public partial class CustomizationOptions
|
||||||
return set;
|
return set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TemporaryData(DataManager gameData, CustomizationOptions options, ClientLanguage language)
|
public TemporaryData(DataManager gameData, CustomizationOptions options)
|
||||||
{
|
{
|
||||||
_options = options;
|
_options = options;
|
||||||
_cmpFile = new CmpFile(gameData);
|
_cmpFile = new CmpFile(gameData);
|
||||||
|
|
@ -181,18 +181,18 @@ public partial class CustomizationOptions
|
||||||
.MakeGenericMethod(typeof(CharaMakeParams)).Invoke(gameData.Excel, new object?[]
|
.MakeGenericMethod(typeof(CharaMakeParams)).Invoke(gameData.Excel, new object?[]
|
||||||
{
|
{
|
||||||
"charamaketype",
|
"charamaketype",
|
||||||
language.ToLumina(),
|
gameData.Language.ToLumina(),
|
||||||
null,
|
null,
|
||||||
}) as ExcelSheet<CharaMakeParams>;
|
}) as ExcelSheet<CharaMakeParams>;
|
||||||
_listSheet = tmp!;
|
_listSheet = tmp!;
|
||||||
_hairSheet = gameData.GetExcelSheet<HairMakeType>()!;
|
_hairSheet = gameData.GetExcelSheet<HairMakeType>()!;
|
||||||
_highlightPicker = CreateColorPicker(CustomizationId.HighlightColor, 256, 192);
|
_highlightPicker = CreateColorPicker(CustomizeIndex.HighlightsColor, 256, 192);
|
||||||
_lipColorPickerDark = CreateColorPicker(CustomizationId.LipColor, 512, 96);
|
_lipColorPickerDark = CreateColorPicker(CustomizeIndex.LipColor, 512, 96);
|
||||||
_lipColorPickerLight = CreateColorPicker(CustomizationId.LipColor, 1024, 96, true);
|
_lipColorPickerLight = CreateColorPicker(CustomizeIndex.LipColor, 1024, 96, true);
|
||||||
_eyeColorPicker = CreateColorPicker(CustomizationId.EyeColorL, 0, 192);
|
_eyeColorPicker = CreateColorPicker(CustomizeIndex.EyeColorLeft, 0, 192);
|
||||||
_facePaintColorPickerDark = CreateColorPicker(CustomizationId.FacePaintColor, 640, 96);
|
_facePaintColorPickerDark = CreateColorPicker(CustomizeIndex.FacePaintColor, 640, 96);
|
||||||
_facePaintColorPickerLight = CreateColorPicker(CustomizationId.FacePaintColor, 1152, 96, true);
|
_facePaintColorPickerLight = CreateColorPicker(CustomizeIndex.FacePaintColor, 1152, 96, true);
|
||||||
_tattooColorPicker = CreateColorPicker(CustomizationId.TattooColor, 0, 192);
|
_tattooColorPicker = CreateColorPicker(CustomizeIndex.TattooColor, 0, 192);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Required sheets.
|
// Required sheets.
|
||||||
|
|
@ -203,19 +203,19 @@ public partial class CustomizationOptions
|
||||||
private readonly CmpFile _cmpFile;
|
private readonly CmpFile _cmpFile;
|
||||||
|
|
||||||
// Those values are shared between all races.
|
// Those values are shared between all races.
|
||||||
private readonly CustomizationData[] _highlightPicker;
|
private readonly CustomizeData[] _highlightPicker;
|
||||||
private readonly CustomizationData[] _eyeColorPicker;
|
private readonly CustomizeData[] _eyeColorPicker;
|
||||||
private readonly CustomizationData[] _facePaintColorPickerDark;
|
private readonly CustomizeData[] _facePaintColorPickerDark;
|
||||||
private readonly CustomizationData[] _facePaintColorPickerLight;
|
private readonly CustomizeData[] _facePaintColorPickerLight;
|
||||||
private readonly CustomizationData[] _lipColorPickerDark;
|
private readonly CustomizeData[] _lipColorPickerDark;
|
||||||
private readonly CustomizationData[] _lipColorPickerLight;
|
private readonly CustomizeData[] _lipColorPickerLight;
|
||||||
private readonly CustomizationData[] _tattooColorPicker;
|
private readonly CustomizeData[] _tattooColorPicker;
|
||||||
|
|
||||||
private readonly CustomizationOptions _options;
|
private readonly CustomizationOptions _options;
|
||||||
|
|
||||||
private CustomizationData[] CreateColorPicker(CustomizationId id, int offset, int num, bool light = false)
|
private CustomizeData[] CreateColorPicker(CustomizeIndex index, int offset, int num, bool light = false)
|
||||||
=> _cmpFile.GetSlice(offset, num)
|
=> _cmpFile.GetSlice(offset, num)
|
||||||
.Select((c, i) => new CustomizationData(id, (CustomizationByteValue)(light ? 128 + i : 0 + i), c, (ushort)(offset + i)))
|
.Select((c, i) => new CustomizeData(index, (CustomizeValue)(light ? 128 + i : 0 + i), c, (ushort)(offset + i)))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -227,12 +227,12 @@ public partial class CustomizationOptions
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var tmp = new IReadOnlyList<CustomizationData>[set.Faces.Count + 1];
|
var tmp = new IReadOnlyList<CustomizeData>[set.Faces.Count + 1];
|
||||||
tmp[0] = set.HairStyles;
|
tmp[0] = set.HairStyles;
|
||||||
|
|
||||||
for (var i = 1; i <= set.Faces.Count; ++i)
|
for (var i = 1; i <= set.Faces.Count; ++i)
|
||||||
{
|
{
|
||||||
bool Valid(CustomizationData c)
|
bool Valid(CustomizeData c)
|
||||||
{
|
{
|
||||||
var data = _customizeSheet.GetRow(c.CustomizeId)?.Unknown6 ?? 0;
|
var data = _customizeSheet.GetRow(c.CustomizeId)?.Unknown6 ?? 0;
|
||||||
return data == 0 || data == i + set.Faces.Count;
|
return data == 0 || data == i + set.Faces.Count;
|
||||||
|
|
@ -247,22 +247,38 @@ public partial class CustomizationOptions
|
||||||
private static void SetMenuTypes(CustomizationSet set, CharaMakeParams row)
|
private static void SetMenuTypes(CustomizationSet set, CharaMakeParams row)
|
||||||
{
|
{
|
||||||
// Set up the menu types for all customizations.
|
// Set up the menu types for all customizations.
|
||||||
set.Types = ((CustomizationId[])Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
set.Types = Enum.GetValues<CustomizeIndex>().Select(c =>
|
||||||
{
|
{
|
||||||
// Those types are not correctly given in the menu, so special case them to color pickers.
|
// Those types are not correctly given in the menu, so special case them to color pickers.
|
||||||
switch (c)
|
switch (c)
|
||||||
{
|
{
|
||||||
case CustomizationId.HighlightColor:
|
case CustomizeIndex.HighlightsColor:
|
||||||
case CustomizationId.EyeColorL:
|
case CustomizeIndex.EyeColorLeft:
|
||||||
case CustomizationId.EyeColorR:
|
case CustomizeIndex.EyeColorRight:
|
||||||
return CharaMakeParams.MenuType.ColorPicker;
|
return CharaMakeParams.MenuType.ColorPicker;
|
||||||
|
case CustomizeIndex.BodyType: return CharaMakeParams.MenuType.Nothing;
|
||||||
|
case CustomizeIndex.FacePaintReversed:
|
||||||
|
case CustomizeIndex.Highlights:
|
||||||
|
case CustomizeIndex.SmallIris:
|
||||||
|
case CustomizeIndex.Lipstick:
|
||||||
|
return CharaMakeParams.MenuType.Checkmark;
|
||||||
|
case CustomizeIndex.FacialFeature1:
|
||||||
|
case CustomizeIndex.FacialFeature2:
|
||||||
|
case CustomizeIndex.FacialFeature3:
|
||||||
|
case CustomizeIndex.FacialFeature4:
|
||||||
|
case CustomizeIndex.FacialFeature5:
|
||||||
|
case CustomizeIndex.FacialFeature6:
|
||||||
|
case CustomizeIndex.FacialFeature7:
|
||||||
|
case CustomizeIndex.LegacyTattoo:
|
||||||
|
return CharaMakeParams.MenuType.IconCheckmark;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var gameId = c.ToByteAndMask().ByteIdx;
|
||||||
// Otherwise find the first menu corresponding to the id.
|
// Otherwise find the first menu corresponding to the id.
|
||||||
// If there is none, assume a list.
|
// If there is none, assume a list.
|
||||||
var menu = row.Menus
|
var menu = row.Menus
|
||||||
.Cast<CharaMakeParams.Menu?>()
|
.Cast<CharaMakeParams.Menu?>()
|
||||||
.FirstOrDefault(m => m!.Value.Customization == c);
|
.FirstOrDefault(m => m!.Value.Customize == gameId);
|
||||||
return menu?.Type ?? CharaMakeParams.MenuType.ListSelector;
|
return menu?.Type ?? CharaMakeParams.MenuType.ListSelector;
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
set.Order = CustomizationSet.ComputeOrder(set);
|
set.Order = CustomizationSet.ComputeOrder(set);
|
||||||
|
|
@ -271,59 +287,96 @@ public partial class CustomizationOptions
|
||||||
// Set customizations available if they have any options.
|
// Set customizations available if they have any options.
|
||||||
private static void SetAvailability(CustomizationSet set, CharaMakeParams row)
|
private static void SetAvailability(CustomizationSet set, CharaMakeParams row)
|
||||||
{
|
{
|
||||||
void Set(bool available, CustomizationId flag)
|
if (set.Race == Race.Hrothgar && set.Gender == Gender.Female)
|
||||||
|
return;
|
||||||
|
|
||||||
|
void Set(bool available, CustomizeIndex flag)
|
||||||
{
|
{
|
||||||
if (available)
|
if (available)
|
||||||
set.SetAvailable(flag);
|
set.SetAvailable(flag);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Both are percentages that are either unavailable or 0-100.
|
Set(true, CustomizeIndex.Height);
|
||||||
Set(GetListSize(row, CustomizationId.BustSize) > 0, CustomizationId.BustSize);
|
Set(set.Faces.Count > 0, CustomizeIndex.Face);
|
||||||
Set(GetListSize(row, CustomizationId.MuscleToneOrTailEarLength) > 0, CustomizationId.MuscleToneOrTailEarLength);
|
Set(true, CustomizeIndex.Hairstyle);
|
||||||
Set(set.NumEyebrows > 0, CustomizationId.Eyebrows);
|
Set(true, CustomizeIndex.Highlights);
|
||||||
Set(set.NumEyeShapes > 0, CustomizationId.EyeShape);
|
Set(true, CustomizeIndex.SkinColor);
|
||||||
Set(set.NumNoseShapes > 0, CustomizationId.Nose);
|
Set(true, CustomizeIndex.EyeColorRight);
|
||||||
Set(set.NumJawShapes > 0, CustomizationId.Jaw);
|
Set(true, CustomizeIndex.HairColor);
|
||||||
Set(set.NumMouthShapes > 0, CustomizationId.Mouth);
|
Set(true, CustomizeIndex.HighlightsColor);
|
||||||
Set(set.TailEarShapes.Count > 0, CustomizationId.TailEarShape);
|
Set(true, CustomizeIndex.TattooColor);
|
||||||
Set(set.Faces.Count > 0, CustomizationId.Face);
|
Set(set.NumEyebrows > 0, CustomizeIndex.Eyebrows);
|
||||||
Set(set.FacePaints.Count > 0, CustomizationId.FacePaint);
|
Set(true, CustomizeIndex.EyeColorLeft);
|
||||||
Set(set.FacePaints.Count > 0, CustomizationId.FacePaintColor);
|
Set(set.NumEyeShapes > 0, CustomizeIndex.EyeShape);
|
||||||
|
Set(set.NumNoseShapes > 0, CustomizeIndex.Nose);
|
||||||
|
Set(set.NumJawShapes > 0, CustomizeIndex.Jaw);
|
||||||
|
Set(set.NumMouthShapes > 0, CustomizeIndex.Mouth);
|
||||||
|
Set(set.LipColorsDark.Count > 0, CustomizeIndex.LipColor);
|
||||||
|
Set(GetListSize(row, CustomizeIndex.MuscleMass) > 0, CustomizeIndex.MuscleMass);
|
||||||
|
Set(set.TailEarShapes.Count > 0, CustomizeIndex.TailShape);
|
||||||
|
Set(GetListSize(row, CustomizeIndex.BustSize) > 0, CustomizeIndex.BustSize);
|
||||||
|
Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaint);
|
||||||
|
Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaintColor);
|
||||||
|
Set(true, CustomizeIndex.FacialFeature1);
|
||||||
|
Set(true, CustomizeIndex.FacialFeature2);
|
||||||
|
Set(true, CustomizeIndex.FacialFeature3);
|
||||||
|
Set(true, CustomizeIndex.FacialFeature4);
|
||||||
|
Set(true, CustomizeIndex.FacialFeature5);
|
||||||
|
Set(true, CustomizeIndex.FacialFeature6);
|
||||||
|
Set(true, CustomizeIndex.FacialFeature7);
|
||||||
|
Set(true, CustomizeIndex.LegacyTattoo);
|
||||||
|
Set(true, CustomizeIndex.SmallIris);
|
||||||
|
Set(set.Race != Race.Hrothgar, CustomizeIndex.Lipstick);
|
||||||
|
Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaintReversed);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a list of lists of facial features and the legacy tattoo.
|
// Create a list of lists of facial features and the legacy tattoo.
|
||||||
private static void SetFacialFeatures(CustomizationSet set, CharaMakeParams row)
|
private static void SetFacialFeatures(CustomizationSet set, CharaMakeParams row)
|
||||||
{
|
{
|
||||||
var count = set.Faces.Count;
|
var count = set.Faces.Count;
|
||||||
var featureDict = new List<IReadOnlyList<CustomizationData>>(count);
|
set.FacialFeature1 = new List<(CustomizeData, CustomizeData)>(count);
|
||||||
|
|
||||||
|
static (CustomizeData, CustomizeData) Create(CustomizeIndex i, uint data)
|
||||||
|
=> (new CustomizeData(i, CustomizeValue.Zero, data, 0), new CustomizeData(i, CustomizeValue.Max, data, 1));
|
||||||
|
|
||||||
|
set.LegacyTattoo = Create(CustomizeIndex.LegacyTattoo, 137905);
|
||||||
|
|
||||||
|
var tmp = Enumerable.Repeat(0, 7).Select(_ => new (CustomizeData, CustomizeData)[count + 1]).ToArray();
|
||||||
for (var i = 0; i < count; ++i)
|
for (var i = 0; i < count; ++i)
|
||||||
{
|
{
|
||||||
var legacyTattoo = new CustomizationData(CustomizationId.FacialFeaturesTattoos, (CustomizationByteValue)(1 << 7), 137905,
|
var data = row.FacialFeatureByFace[i].Icons;
|
||||||
(ushort)((i + 1) * 8));
|
tmp[0][i + 1] = Create(CustomizeIndex.FacialFeature1, data[0]);
|
||||||
featureDict.Add(row.FacialFeatureByFace[i].Icons.Select((val, idx)
|
tmp[1][i + 1] = Create(CustomizeIndex.FacialFeature2, data[1]);
|
||||||
=> new CustomizationData(CustomizationId.FacialFeaturesTattoos, (CustomizationByteValue)(1 << idx), val,
|
tmp[2][i + 1] = Create(CustomizeIndex.FacialFeature3, data[2]);
|
||||||
(ushort)(i * 8 + idx)))
|
tmp[3][i + 1] = Create(CustomizeIndex.FacialFeature4, data[3]);
|
||||||
.Append(legacyTattoo)
|
tmp[4][i + 1] = Create(CustomizeIndex.FacialFeature5, data[4]);
|
||||||
.ToArray());
|
tmp[5][i + 1] = Create(CustomizeIndex.FacialFeature6, data[5]);
|
||||||
|
tmp[6][i + 1] = Create(CustomizeIndex.FacialFeature7, data[6]);
|
||||||
}
|
}
|
||||||
|
|
||||||
set.FeaturesTattoos = featureDict.ToArray();
|
set.FacialFeature1 = tmp[0];
|
||||||
|
set.FacialFeature2 = tmp[1];
|
||||||
|
set.FacialFeature3 = tmp[2];
|
||||||
|
set.FacialFeature4 = tmp[3];
|
||||||
|
set.FacialFeature5 = tmp[4];
|
||||||
|
set.FacialFeature6 = tmp[5];
|
||||||
|
set.FacialFeature7 = tmp[6];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the names for the given set of parameters.
|
// Set the names for the given set of parameters.
|
||||||
private void SetNames(CustomizationSet set, CharaMakeParams row)
|
private void SetNames(CustomizationSet set, CharaMakeParams row)
|
||||||
{
|
{
|
||||||
var nameArray = ((CustomizationId[])Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
var nameArray = Enum.GetValues<CustomizeIndex>().Select(c =>
|
||||||
{
|
{
|
||||||
// Find the first menu that corresponds to the Id.
|
// Find the first menu that corresponds to the Id.
|
||||||
|
var byteId = c.ToByteAndMask().ByteIdx;
|
||||||
var menu = row.Menus
|
var menu = row.Menus
|
||||||
.Cast<CharaMakeParams.Menu?>()
|
.Cast<CharaMakeParams.Menu?>()
|
||||||
.FirstOrDefault(m => m!.Value.Customization == c);
|
.FirstOrDefault(m => m!.Value.Customize == byteId);
|
||||||
if (menu == null)
|
if (menu == null)
|
||||||
{
|
{
|
||||||
// If none exists and the id corresponds to highlights, set the Highlights name.
|
// If none exists and the id corresponds to highlights, set the Highlights name.
|
||||||
if (c == CustomizationId.HighlightsOnFlag)
|
if (c == CustomizeIndex.Highlights)
|
||||||
return Lobby.GetRow(237)?.Text.ToDalamudString().ToString() ?? "Highlights";
|
return Lobby.GetRow(237)?.Text.ToDalamudString().ToString() ?? "Highlights";
|
||||||
|
|
||||||
// Otherwise there is an error and we use the default name.
|
// Otherwise there is an error and we use the default name.
|
||||||
|
|
@ -331,7 +384,7 @@ public partial class CustomizationOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
// Facial Features and Tattoos is created by combining two strings.
|
// Facial Features and Tattoos is created by combining two strings.
|
||||||
if (c == CustomizationId.FacialFeaturesTattoos)
|
if (c is >= CustomizeIndex.FacialFeature1 and <= CustomizeIndex.LegacyTattoo)
|
||||||
return
|
return
|
||||||
$"{Lobby.GetRow(1741)?.Text.ToDalamudString().ToString() ?? "Facial Features"} & {Lobby.GetRow(1742)?.Text.ToDalamudString().ToString() ?? "Tattoos"}";
|
$"{Lobby.GetRow(1741)?.Text.ToDalamudString().ToString() ?? "Facial Features"} & {Lobby.GetRow(1742)?.Text.ToDalamudString().ToString() ?? "Tattoos"}";
|
||||||
|
|
||||||
|
|
@ -341,14 +394,14 @@ public partial class CustomizationOptions
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
// Add names for both eye colors.
|
// Add names for both eye colors.
|
||||||
nameArray[(int)CustomizationId.EyeColorL] = nameArray[(int)CustomizationId.EyeColorR];
|
nameArray[(int)CustomizeIndex.EyeColorLeft] = nameArray[(int)CustomizeIndex.EyeColorRight];
|
||||||
nameArray[(int)CustomizationId.EyeColorR] = _options.GetName(CustomName.OddEyes);
|
nameArray[(int)CustomizeIndex.EyeColorRight] = _options.GetName(CustomName.OddEyes);
|
||||||
|
|
||||||
set.OptionName = nameArray;
|
set.OptionName = nameArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtain available skin and hair colors for the given subrace and gender.
|
// Obtain available skin and hair colors for the given subrace and gender.
|
||||||
private (CustomizationData[], CustomizationData[]) GetColors(SubRace race, Gender gender)
|
private (CustomizeData[], CustomizeData[]) GetColors(SubRace race, Gender gender)
|
||||||
{
|
{
|
||||||
if (race is > SubRace.Veena or SubRace.Unknown)
|
if (race is > SubRace.Veena or SubRace.Unknown)
|
||||||
throw new ArgumentOutOfRangeException(nameof(race), race, null);
|
throw new ArgumentOutOfRangeException(nameof(race), race, null);
|
||||||
|
|
@ -356,16 +409,16 @@ public partial class CustomizationOptions
|
||||||
var gv = gender == Gender.Male ? 0 : 1;
|
var gv = gender == Gender.Male ? 0 : 1;
|
||||||
var idx = ((int)race * 2 + gv) * 5 + 3;
|
var idx = ((int)race * 2 + gv) * 5 + 3;
|
||||||
|
|
||||||
return (CreateColorPicker(CustomizationId.SkinColor, idx << 8, 192),
|
return (CreateColorPicker(CustomizeIndex.SkinColor, idx << 8, 192),
|
||||||
CreateColorPicker(CustomizationId.HairColor, (idx + 1) << 8, 192));
|
CreateColorPicker(CustomizeIndex.HairColor, (idx + 1) << 8, 192));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtain available hairstyles via reflection from the Hair sheet for the given subrace and gender.
|
// Obtain available hairstyles via reflection from the Hair sheet for the given subrace and gender.
|
||||||
private CustomizationData[] GetHairStyles(SubRace race, Gender gender)
|
private CustomizeData[] GetHairStyles(SubRace race, Gender gender)
|
||||||
{
|
{
|
||||||
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||||
// Unknown30 is the number of available hairstyles.
|
// Unknown30 is the number of available hairstyles.
|
||||||
var hairList = new List<CustomizationData>(row.Unknown30);
|
var hairList = new List<CustomizeData>(row.Unknown30);
|
||||||
// Hairstyles can be found starting at Unknown66.
|
// Hairstyles can be found starting at Unknown66.
|
||||||
for (var i = 0; i < row.Unknown30; ++i)
|
for (var i = 0; i < row.Unknown30; ++i)
|
||||||
{
|
{
|
||||||
|
|
@ -378,35 +431,36 @@ public partial class CustomizationOptions
|
||||||
// Hair Row from CustomizeSheet might not be set in case of unlockable hair.
|
// Hair Row from CustomizeSheet might not be set in case of unlockable hair.
|
||||||
var hairRow = _customizeSheet.GetRow(customizeIdx);
|
var hairRow = _customizeSheet.GetRow(customizeIdx);
|
||||||
hairList.Add(hairRow != null
|
hairList.Add(hairRow != null
|
||||||
? new CustomizationData(CustomizationId.Hairstyle, (CustomizationByteValue)hairRow.FeatureID, hairRow.Icon,
|
? new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon,
|
||||||
(ushort)hairRow.RowId)
|
(ushort)hairRow.RowId)
|
||||||
: new CustomizationData(CustomizationId.Hairstyle, (CustomizationByteValue)i, customizeIdx));
|
: new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx));
|
||||||
}
|
}
|
||||||
|
|
||||||
return hairList.ToArray();
|
return hairList.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get Features.
|
// Get Features.
|
||||||
private CustomizationData FromValueAndIndex(CustomizationId id, uint value, int index)
|
private CustomizeData FromValueAndIndex(CustomizeIndex id, uint value, int index)
|
||||||
{
|
{
|
||||||
var row = _customizeSheet.GetRow(value);
|
var row = _customizeSheet.GetRow(value);
|
||||||
return row == null
|
return row == null
|
||||||
? new CustomizationData(id, (CustomizationByteValue)(index + 1), value)
|
? new CustomizeData(id, (CustomizeValue)(index + 1), value)
|
||||||
: new CustomizationData(id, (CustomizationByteValue)row.FeatureID, row.Icon, (ushort)row.RowId);
|
: new CustomizeData(id, (CustomizeValue)row.FeatureID, row.Icon, (ushort)row.RowId);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get List sizes.
|
// Get List sizes.
|
||||||
private static int GetListSize(CharaMakeParams row, CustomizationId id)
|
private static int GetListSize(CharaMakeParams row, CustomizeIndex index)
|
||||||
{
|
{
|
||||||
var menu = row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == id);
|
var gameId = index.ToByteAndMask().ByteIdx;
|
||||||
|
var menu = row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customize == gameId);
|
||||||
return menu?.Size ?? 0;
|
return menu?.Size ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get face paints from the hair sheet via reflection.
|
// Get face paints from the hair sheet via reflection.
|
||||||
private CustomizationData[] GetFacePaints(SubRace race, Gender gender)
|
private CustomizeData[] GetFacePaints(SubRace race, Gender gender)
|
||||||
{
|
{
|
||||||
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||||
var paintList = new List<CustomizationData>(row.Unknown37);
|
var paintList = new List<CustomizeData>(row.Unknown37);
|
||||||
|
|
||||||
// Number of available face paints is at Unknown37.
|
// Number of available face paints is at Unknown37.
|
||||||
for (var i = 0; i < row.Unknown37; ++i)
|
for (var i = 0; i < row.Unknown37; ++i)
|
||||||
|
|
@ -422,30 +476,33 @@ public partial class CustomizationOptions
|
||||||
var paintRow = _customizeSheet.GetRow(customizeIdx);
|
var paintRow = _customizeSheet.GetRow(customizeIdx);
|
||||||
// Facepaint Row from CustomizeSheet might not be set in case of unlockable facepaints.
|
// Facepaint Row from CustomizeSheet might not be set in case of unlockable facepaints.
|
||||||
paintList.Add(paintRow != null
|
paintList.Add(paintRow != null
|
||||||
? new CustomizationData(CustomizationId.FacePaint, (CustomizationByteValue)paintRow.FeatureID, paintRow.Icon,
|
? new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)paintRow.FeatureID, paintRow.Icon,
|
||||||
(ushort)paintRow.RowId)
|
(ushort)paintRow.RowId)
|
||||||
: new CustomizationData(CustomizationId.FacePaint, (CustomizationByteValue)i, customizeIdx));
|
: new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)i, customizeIdx));
|
||||||
}
|
}
|
||||||
|
|
||||||
return paintList.ToArray();
|
return paintList.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specific icons for tails or ears.
|
// Specific icons for tails or ears.
|
||||||
private CustomizationData[] GetTailEarShapes(CharaMakeParams row)
|
private CustomizeData[] GetTailEarShapes(CharaMakeParams row)
|
||||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.TailEarShape)?.Values
|
=> row.Menus.Cast<CharaMakeParams.Menu?>()
|
||||||
.Select((v, i) => FromValueAndIndex(CustomizationId.TailEarShape, v, i)).ToArray()
|
.FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.TailShape.ToByteAndMask().ByteIdx)?.Values
|
||||||
?? Array.Empty<CustomizationData>();
|
.Select((v, i) => FromValueAndIndex(CustomizeIndex.TailShape, v, i)).ToArray()
|
||||||
|
?? Array.Empty<CustomizeData>();
|
||||||
|
|
||||||
// Specific icons for faces.
|
// Specific icons for faces.
|
||||||
private CustomizationData[] GetFaces(CharaMakeParams row)
|
private CustomizeData[] GetFaces(CharaMakeParams row)
|
||||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.Face)?.Values
|
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.Face.ToByteAndMask().ByteIdx)
|
||||||
.Select((v, i) => FromValueAndIndex(CustomizationId.Face, v, i)).ToArray()
|
?.Values
|
||||||
?? Array.Empty<CustomizationData>();
|
.Select((v, i) => FromValueAndIndex(CustomizeIndex.Face, v, i)).ToArray()
|
||||||
|
?? Array.Empty<CustomizeData>();
|
||||||
|
|
||||||
// Specific icons for Hrothgar patterns.
|
// Specific icons for Hrothgar patterns.
|
||||||
private CustomizationData[] HrothgarFurPattern(CharaMakeParams row)
|
private CustomizeData[] HrothgarFurPattern(CharaMakeParams row)
|
||||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.LipColor)?.Values
|
=> row.Menus.Cast<CharaMakeParams.Menu?>()
|
||||||
.Select((v, i) => FromValueAndIndex(CustomizationId.LipColor, v, i)).ToArray()
|
.FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.LipColor.ToByteAndMask().ByteIdx)?.Values
|
||||||
?? Array.Empty<CustomizationData>();
|
.Select((v, i) => FromValueAndIndex(CustomizeIndex.LipColor, v, i)).ToArray()
|
||||||
|
?? Array.Empty<CustomizeData>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Xml.XPath;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
|
|
@ -13,56 +14,32 @@ public class CustomizationSet
|
||||||
{
|
{
|
||||||
internal CustomizationSet(SubRace clan, Gender gender)
|
internal CustomizationSet(SubRace clan, Gender gender)
|
||||||
{
|
{
|
||||||
Gender = gender;
|
Gender = gender;
|
||||||
Clan = clan;
|
Clan = clan;
|
||||||
Race = clan.ToRace();
|
Race = clan.ToRace();
|
||||||
_settingAvailable = Race == Race.Hrothgar && gender == Gender.Female
|
_settingAvailable = 0;
|
||||||
? 0u
|
|
||||||
: DefaultAvailable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Gender Gender { get; }
|
public Gender Gender { get; }
|
||||||
public SubRace Clan { get; }
|
public SubRace Clan { get; }
|
||||||
public Race Race { get; }
|
public Race Race { get; }
|
||||||
|
|
||||||
private uint _settingAvailable;
|
private CustomizeFlag _settingAvailable;
|
||||||
|
|
||||||
internal void SetAvailable(CustomizationId id)
|
internal void SetAvailable(CustomizeIndex index)
|
||||||
=> _settingAvailable |= 1u << (int)id;
|
=> _settingAvailable |= index.ToFlag();
|
||||||
|
|
||||||
public bool IsAvailable(CustomizationId id)
|
public bool IsAvailable(CustomizeIndex index)
|
||||||
=> (_settingAvailable & (1u << (int)id)) != 0;
|
=> _settingAvailable.HasFlag(index.ToFlag());
|
||||||
|
|
||||||
private const uint DefaultAvailable =
|
|
||||||
(1u << (int)CustomizationId.Height)
|
|
||||||
| (1u << (int)CustomizationId.Hairstyle)
|
|
||||||
| (1u << (int)CustomizationId.SkinColor)
|
|
||||||
| (1u << (int)CustomizationId.EyeColorR)
|
|
||||||
| (1u << (int)CustomizationId.EyeColorL)
|
|
||||||
| (1u << (int)CustomizationId.HairColor)
|
|
||||||
| (1u << (int)CustomizationId.HighlightColor)
|
|
||||||
| (1u << (int)CustomizationId.FacialFeaturesTattoos)
|
|
||||||
| (1u << (int)CustomizationId.TattooColor)
|
|
||||||
| (1u << (int)CustomizationId.LipColor)
|
|
||||||
| (1u << (int)CustomizationId.Height);
|
|
||||||
|
|
||||||
public string ToHumanReadable(Customize customizationData)
|
|
||||||
{
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
foreach (var id in Enum.GetValues<CustomizationId>().Where(IsAvailable))
|
|
||||||
sb.AppendFormat("{0,-20}", Option(id)).Append(customizationData[id]);
|
|
||||||
|
|
||||||
return sb.ToString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Meta
|
// Meta
|
||||||
public IReadOnlyList<string> OptionName { get; internal set; } = null!;
|
public IReadOnlyList<string> OptionName { get; internal set; } = null!;
|
||||||
|
|
||||||
public string Option(CustomizationId id)
|
public string Option(CustomizeIndex index)
|
||||||
=> OptionName[(int)id];
|
=> OptionName[(int)index];
|
||||||
|
|
||||||
public IReadOnlyList<CharaMakeParams.MenuType> Types { get; internal set; } = null!;
|
public IReadOnlyList<CharaMakeParams.MenuType> Types { get; internal set; } = null!;
|
||||||
public IReadOnlyDictionary<CharaMakeParams.MenuType, CustomizationId[]> Order { get; internal set; } = null!;
|
public IReadOnlyDictionary<CharaMakeParams.MenuType, CustomizeIndex[]> Order { get; internal set; } = null!;
|
||||||
|
|
||||||
|
|
||||||
// Always list selector.
|
// Always list selector.
|
||||||
|
|
@ -74,164 +51,224 @@ public class CustomizationSet
|
||||||
|
|
||||||
|
|
||||||
// Always Icon Selector
|
// Always Icon Selector
|
||||||
public IReadOnlyList<CustomizationData> Faces { get; internal init; } = null!;
|
public IReadOnlyList<CustomizeData> Faces { get; internal init; } = null!;
|
||||||
public IReadOnlyList<CustomizationData> HairStyles { get; internal init; } = null!;
|
public IReadOnlyList<CustomizeData> HairStyles { get; internal init; } = null!;
|
||||||
public IReadOnlyList<IReadOnlyList<CustomizationData>> HairByFace { get; internal set; } = null!;
|
public IReadOnlyList<IReadOnlyList<CustomizeData>> HairByFace { get; internal set; } = null!;
|
||||||
public IReadOnlyList<CustomizationData> TailEarShapes { get; internal init; } = null!;
|
public IReadOnlyList<CustomizeData> TailEarShapes { get; internal init; } = null!;
|
||||||
public IReadOnlyList<IReadOnlyList<CustomizationData>> FeaturesTattoos { get; internal set; } = null!;
|
public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature1 { get; internal set; } = null!;
|
||||||
public IReadOnlyList<CustomizationData> FacePaints { get; internal init; } = null!;
|
public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature2 { get; internal set; } = null!;
|
||||||
|
public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature3 { get; internal set; } = null!;
|
||||||
public CustomizationData FacialFeature(CustomizationByteValue face, int idx)
|
public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature4 { get; internal set; } = null!;
|
||||||
{
|
public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature5 { get; internal set; } = null!;
|
||||||
face = HrothgarFaceHack(face);
|
public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature6 { get; internal set; } = null!;
|
||||||
var faceIdx = Faces.IndexOf(p => p.Value == face);
|
public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature7 { get; internal set; } = null!;
|
||||||
return FeaturesTattoos[faceIdx != -1 ? faceIdx : 0][idx];
|
public (CustomizeData, CustomizeData) LegacyTattoo { get; internal set; }
|
||||||
}
|
public IReadOnlyList<CustomizeData> FacePaints { get; internal init; } = null!;
|
||||||
|
|
||||||
|
|
||||||
// Always Color Selector
|
// Always Color Selector
|
||||||
public IReadOnlyList<CustomizationData> SkinColors { get; internal init; } = null!;
|
public IReadOnlyList<CustomizeData> SkinColors { get; internal init; } = null!;
|
||||||
public IReadOnlyList<CustomizationData> HairColors { get; internal init; } = null!;
|
public IReadOnlyList<CustomizeData> HairColors { get; internal init; } = null!;
|
||||||
public IReadOnlyList<CustomizationData> HighlightColors { get; internal init; } = null!;
|
public IReadOnlyList<CustomizeData> HighlightColors { get; internal init; } = null!;
|
||||||
public IReadOnlyList<CustomizationData> EyeColors { get; internal init; } = null!;
|
public IReadOnlyList<CustomizeData> EyeColors { get; internal init; } = null!;
|
||||||
public IReadOnlyList<CustomizationData> TattooColors { get; internal init; } = null!;
|
public IReadOnlyList<CustomizeData> TattooColors { get; internal init; } = null!;
|
||||||
public IReadOnlyList<CustomizationData> FacePaintColorsLight { get; internal init; } = null!;
|
public IReadOnlyList<CustomizeData> FacePaintColorsLight { get; internal init; } = null!;
|
||||||
public IReadOnlyList<CustomizationData> FacePaintColorsDark { get; internal init; } = null!;
|
public IReadOnlyList<CustomizeData> FacePaintColorsDark { get; internal init; } = null!;
|
||||||
public IReadOnlyList<CustomizationData> LipColorsLight { get; internal init; } = null!;
|
public IReadOnlyList<CustomizeData> LipColorsLight { get; internal init; } = null!;
|
||||||
public IReadOnlyList<CustomizationData> LipColorsDark { get; internal init; } = null!;
|
public IReadOnlyList<CustomizeData> LipColorsDark { get; internal init; } = null!;
|
||||||
|
|
||||||
|
|
||||||
public int DataByValue(CustomizationId id, CustomizationByteValue value, out CustomizationData? custom)
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
public int DataByValue(CustomizeIndex index, CustomizeValue value, out CustomizeData? custom, CustomizeValue face)
|
||||||
{
|
{
|
||||||
var type = id.ToType();
|
var type = Types[(int)index];
|
||||||
custom = null;
|
|
||||||
if (type is CharaMakeParams.MenuType.Percentage or CharaMakeParams.MenuType.ListSelector)
|
int GetInteger(out CustomizeData? custom)
|
||||||
{
|
{
|
||||||
if (value < Count(id))
|
if (value < Count(index))
|
||||||
{
|
{
|
||||||
custom = new CustomizationData(id, value, 0, value.Value);
|
custom = new CustomizeData(index, value, 0, value.Value);
|
||||||
return value.Value;
|
return value.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
custom = null;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int Get(IEnumerable<CustomizationData> list, CustomizationByteValue v, ref CustomizationData? output)
|
static int GetBool(CustomizeIndex index, CustomizeValue value, out CustomizeData? custom)
|
||||||
{
|
{
|
||||||
var (val, idx) = list.Cast<CustomizationData?>().WithIndex().FirstOrDefault(p => p.Item1!.Value.Value == v);
|
if (value == CustomizeValue.Zero)
|
||||||
|
{
|
||||||
|
custom = new CustomizeData(index, CustomizeValue.Zero, 0, 0);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var (_, mask) = index.ToByteAndMask();
|
||||||
|
if (value.Value == mask)
|
||||||
|
{
|
||||||
|
custom = new CustomizeData(index, new CustomizeValue(mask), 0, 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
custom = null;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int Invalid(out CustomizeData? custom)
|
||||||
|
{
|
||||||
|
custom = null;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int Get(IEnumerable<CustomizeData> list, CustomizeValue v, out CustomizeData? output)
|
||||||
|
{
|
||||||
|
var (val, idx) = list.Cast<CustomizeData?>().WithIndex().FirstOrDefault(p => p.Item1!.Value.Value == v);
|
||||||
if (val == null)
|
if (val == null)
|
||||||
|
{
|
||||||
|
output = null;
|
||||||
return -1;
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
output = val;
|
output = val;
|
||||||
return idx;
|
return idx;
|
||||||
}
|
}
|
||||||
|
|
||||||
return id switch
|
return type switch
|
||||||
{
|
{
|
||||||
CustomizationId.SkinColor => Get(SkinColors, value, ref custom),
|
CharaMakeParams.MenuType.ListSelector => GetInteger(out custom),
|
||||||
CustomizationId.EyeColorL => Get(EyeColors, value, ref custom),
|
CharaMakeParams.MenuType.IconSelector => index switch
|
||||||
CustomizationId.EyeColorR => Get(EyeColors, value, ref custom),
|
{
|
||||||
CustomizationId.HairColor => Get(HairColors, value, ref custom),
|
CustomizeIndex.Face => Get(Faces, HrothgarFaceHack(value), out custom),
|
||||||
CustomizationId.HighlightColor => Get(HighlightColors, value, ref custom),
|
CustomizeIndex.Hairstyle => Get((face = HrothgarFaceHack(face)).Value < HairByFace.Count ? HairByFace[face.Value] : HairStyles, value, out custom),
|
||||||
CustomizationId.TattooColor => Get(TattooColors, value, ref custom),
|
CustomizeIndex.TailShape => Get(TailEarShapes, value, out custom),
|
||||||
CustomizationId.LipColor => Get(LipColorsDark.Concat(LipColorsLight), value, ref custom),
|
CustomizeIndex.FacePaint => Get(FacePaints, value, out custom),
|
||||||
CustomizationId.FacePaintColor => Get(FacePaintColorsDark.Concat(FacePaintColorsLight), value, ref custom),
|
CustomizeIndex.LipColor => Get(LipColorsDark, value, out custom),
|
||||||
|
_ => Invalid(out custom),
|
||||||
CustomizationId.Face => Get(Faces, HrothgarFaceHack(value), ref custom),
|
},
|
||||||
CustomizationId.Hairstyle => Get(HairStyles, value, ref custom),
|
CharaMakeParams.MenuType.ColorPicker => index switch
|
||||||
CustomizationId.TailEarShape => Get(TailEarShapes, value, ref custom),
|
{
|
||||||
CustomizationId.FacePaint => Get(FacePaints, value, ref custom),
|
CustomizeIndex.SkinColor => Get(SkinColors, value, out custom),
|
||||||
CustomizationId.FacialFeaturesTattoos => Get(FeaturesTattoos[0], value, ref custom),
|
CustomizeIndex.EyeColorLeft => Get(EyeColors, value, out custom),
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(id), id, null),
|
CustomizeIndex.EyeColorRight => Get(EyeColors, value, out custom),
|
||||||
|
CustomizeIndex.HairColor => Get(HairColors, value, out custom),
|
||||||
|
CustomizeIndex.HighlightsColor => Get(HighlightColors, value, out custom),
|
||||||
|
CustomizeIndex.TattooColor => Get(TattooColors, value, out custom),
|
||||||
|
CustomizeIndex.LipColor => Get(LipColorsDark.Concat(LipColorsLight), value, out custom),
|
||||||
|
CustomizeIndex.FacePaintColor => Get(FacePaintColorsDark.Concat(FacePaintColorsLight), value, out custom),
|
||||||
|
_ => Invalid(out custom),
|
||||||
|
},
|
||||||
|
CharaMakeParams.MenuType.DoubleColorPicker => index switch
|
||||||
|
{
|
||||||
|
CustomizeIndex.LipColor => Get(LipColorsDark.Concat(LipColorsLight), value, out custom),
|
||||||
|
CustomizeIndex.FacePaintColor => Get(FacePaintColorsDark.Concat(FacePaintColorsLight), value, out custom),
|
||||||
|
_ => Invalid(out custom),
|
||||||
|
},
|
||||||
|
CharaMakeParams.MenuType.IconCheckmark => GetBool(index, value, out custom),
|
||||||
|
CharaMakeParams.MenuType.Percentage => GetInteger(out custom),
|
||||||
|
CharaMakeParams.MenuType.Checkmark => GetBool(index, value, out custom),
|
||||||
|
_ => Invalid(out custom),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public CustomizationData Data(CustomizationId id, int idx)
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
=> Data(id, idx, CustomizationByteValue.Zero);
|
public CustomizeData Data(CustomizeIndex index, int idx)
|
||||||
|
=> Data(index, idx, CustomizeValue.Zero);
|
||||||
|
|
||||||
public CustomizationData Data(CustomizationId id, int idx, CustomizationByteValue face)
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
public CustomizeData Data(CustomizeIndex index, int idx, CustomizeValue face)
|
||||||
{
|
{
|
||||||
if (idx >= Count(id, face = HrothgarFaceHack(face)))
|
if (idx >= Count(index, face = HrothgarFaceHack(face)))
|
||||||
throw new IndexOutOfRangeException();
|
throw new IndexOutOfRangeException();
|
||||||
|
|
||||||
switch (id.ToType())
|
switch (Types[(int)index])
|
||||||
{
|
{
|
||||||
case CharaMakeParams.MenuType.Percentage: return new CustomizationData(id, (CustomizationByteValue)idx, 0, (ushort)idx);
|
case CharaMakeParams.MenuType.Percentage: return new CustomizeData(index, (CustomizeValue)idx, 0, (ushort)idx);
|
||||||
case CharaMakeParams.MenuType.ListSelector: return new CustomizationData(id, (CustomizationByteValue)idx, 0, (ushort)idx);
|
case CharaMakeParams.MenuType.ListSelector: return new CustomizeData(index, (CustomizeValue)idx, 0, (ushort)idx);
|
||||||
|
case CharaMakeParams.MenuType.Checkmark: return new CustomizeData(index, CustomizeValue.Bool(idx != 0), 0, (ushort)idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
return id switch
|
return index switch
|
||||||
{
|
{
|
||||||
CustomizationId.Face => Faces[idx],
|
CustomizeIndex.Face => Faces[idx],
|
||||||
CustomizationId.Hairstyle => face < HairByFace.Count ? HairByFace[face.Value][idx] : HairStyles[idx],
|
CustomizeIndex.Hairstyle => face < HairByFace.Count ? HairByFace[face.Value][idx] : HairStyles[idx],
|
||||||
CustomizationId.TailEarShape => TailEarShapes[idx],
|
CustomizeIndex.TailShape => TailEarShapes[idx],
|
||||||
CustomizationId.FacePaint => FacePaints[idx],
|
CustomizeIndex.FacePaint => FacePaints[idx],
|
||||||
CustomizationId.FacialFeaturesTattoos => FeaturesTattoos[0][idx],
|
CustomizeIndex.FacialFeature1 => idx == 0 ? FacialFeature1[face.Value].Item1 : FacialFeature1[face.Value].Item2,
|
||||||
|
CustomizeIndex.FacialFeature2 => idx == 0 ? FacialFeature2[face.Value].Item1 : FacialFeature2[face.Value].Item2,
|
||||||
CustomizationId.SkinColor => SkinColors[idx],
|
CustomizeIndex.FacialFeature3 => idx == 0 ? FacialFeature3[face.Value].Item1 : FacialFeature3[face.Value].Item2,
|
||||||
CustomizationId.EyeColorL => EyeColors[idx],
|
CustomizeIndex.FacialFeature4 => idx == 0 ? FacialFeature4[face.Value].Item1 : FacialFeature4[face.Value].Item2,
|
||||||
CustomizationId.EyeColorR => EyeColors[idx],
|
CustomizeIndex.FacialFeature5 => idx == 0 ? FacialFeature5[face.Value].Item1 : FacialFeature5[face.Value].Item2,
|
||||||
CustomizationId.HairColor => HairColors[idx],
|
CustomizeIndex.FacialFeature6 => idx == 0 ? FacialFeature6[face.Value].Item1 : FacialFeature6[face.Value].Item2,
|
||||||
CustomizationId.HighlightColor => HighlightColors[idx],
|
CustomizeIndex.FacialFeature7 => idx == 0 ? FacialFeature7[face.Value].Item1 : FacialFeature7[face.Value].Item2,
|
||||||
CustomizationId.TattooColor => TattooColors[idx],
|
CustomizeIndex.LegacyTattoo => idx == 0 ? LegacyTattoo.Item1 : LegacyTattoo.Item2,
|
||||||
CustomizationId.LipColor => idx < 96 ? LipColorsDark[idx] : LipColorsLight[idx - 96],
|
CustomizeIndex.SkinColor => SkinColors[idx],
|
||||||
CustomizationId.FacePaintColor => idx < 96 ? FacePaintColorsDark[idx] : FacePaintColorsLight[idx - 96],
|
CustomizeIndex.EyeColorLeft => EyeColors[idx],
|
||||||
_ => new CustomizationData(0, CustomizationByteValue.Zero),
|
CustomizeIndex.EyeColorRight => EyeColors[idx],
|
||||||
|
CustomizeIndex.HairColor => HairColors[idx],
|
||||||
|
CustomizeIndex.HighlightsColor => HighlightColors[idx],
|
||||||
|
CustomizeIndex.TattooColor => TattooColors[idx],
|
||||||
|
CustomizeIndex.LipColor => idx < 96 ? LipColorsDark[idx] : LipColorsLight[idx - 96],
|
||||||
|
CustomizeIndex.FacePaintColor => idx < 96 ? FacePaintColorsDark[idx] : FacePaintColorsLight[idx - 96],
|
||||||
|
_ => new CustomizeData(0, CustomizeValue.Zero),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public CharaMakeParams.MenuType Type(CustomizationId id)
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
=> Types[(int)id];
|
public CharaMakeParams.MenuType Type(CustomizeIndex index)
|
||||||
|
=> Types[(int)index];
|
||||||
|
|
||||||
internal static IReadOnlyDictionary<CharaMakeParams.MenuType, CustomizationId[]> ComputeOrder(CustomizationSet set)
|
internal static IReadOnlyDictionary<CharaMakeParams.MenuType, CustomizeIndex[]> ComputeOrder(CustomizationSet set)
|
||||||
{
|
{
|
||||||
var ret = (CustomizationId[])Enum.GetValues(typeof(CustomizationId));
|
var ret = Enum.GetValues<CustomizeIndex>().SkipLast(1).ToArray();
|
||||||
ret[(int)CustomizationId.TattooColor] = CustomizationId.EyeColorL;
|
ret[(int)CustomizeIndex.TattooColor] = CustomizeIndex.EyeColorLeft;
|
||||||
ret[(int)CustomizationId.EyeColorL] = CustomizationId.EyeColorR;
|
ret[(int)CustomizeIndex.EyeColorLeft] = CustomizeIndex.EyeColorRight;
|
||||||
ret[(int)CustomizationId.EyeColorR] = CustomizationId.TattooColor;
|
ret[(int)CustomizeIndex.EyeColorRight] = CustomizeIndex.TattooColor;
|
||||||
|
|
||||||
var dict = ret.Skip(2).Where(set.IsAvailable).GroupBy(set.Type).ToDictionary(k => k.Key, k => k.ToArray());
|
var dict = ret.Skip(2).Where(set.IsAvailable).GroupBy(set.Type).ToDictionary(k => k.Key, k => k.ToArray());
|
||||||
foreach (var type in Enum.GetValues<CharaMakeParams.MenuType>())
|
foreach (var type in Enum.GetValues<CharaMakeParams.MenuType>())
|
||||||
dict.TryAdd(type, Array.Empty<CustomizationId>());
|
dict.TryAdd(type, Array.Empty<CustomizeIndex>());
|
||||||
return dict;
|
return dict;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Count(CustomizationId id)
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
=> Count(id, CustomizationByteValue.Zero);
|
public int Count(CustomizeIndex index)
|
||||||
|
=> Count(index, CustomizeValue.Zero);
|
||||||
|
|
||||||
public int Count(CustomizationId id, CustomizationByteValue face)
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
public int Count(CustomizeIndex index, CustomizeValue face)
|
||||||
{
|
{
|
||||||
if (!IsAvailable(id))
|
if (!IsAvailable(index))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (id.ToType() == CharaMakeParams.MenuType.Percentage)
|
return Type(index) switch
|
||||||
return 101;
|
|
||||||
|
|
||||||
return id switch
|
|
||||||
{
|
{
|
||||||
CustomizationId.Face => Faces.Count,
|
CharaMakeParams.MenuType.Percentage => 101,
|
||||||
CustomizationId.Hairstyle => (face = HrothgarFaceHack(face)) < HairByFace.Count ? HairByFace[face.Value].Count : 0,
|
CharaMakeParams.MenuType.IconCheckmark => 2,
|
||||||
CustomizationId.HighlightsOnFlag => 2,
|
CharaMakeParams.MenuType.Checkmark => 2,
|
||||||
CustomizationId.SkinColor => SkinColors.Count,
|
_ => index switch
|
||||||
CustomizationId.EyeColorR => EyeColors.Count,
|
{
|
||||||
CustomizationId.HairColor => HairColors.Count,
|
CustomizeIndex.Face => Faces.Count,
|
||||||
CustomizationId.HighlightColor => HighlightColors.Count,
|
CustomizeIndex.Hairstyle => (face = HrothgarFaceHack(face)) < HairByFace.Count ? HairByFace[face.Value].Count : 0,
|
||||||
CustomizationId.FacialFeaturesTattoos => 8,
|
CustomizeIndex.SkinColor => SkinColors.Count,
|
||||||
CustomizationId.TattooColor => TattooColors.Count,
|
CustomizeIndex.EyeColorRight => EyeColors.Count,
|
||||||
CustomizationId.Eyebrows => NumEyebrows,
|
CustomizeIndex.HairColor => HairColors.Count,
|
||||||
CustomizationId.EyeColorL => EyeColors.Count,
|
CustomizeIndex.HighlightsColor => HighlightColors.Count,
|
||||||
CustomizationId.EyeShape => NumEyeShapes,
|
CustomizeIndex.TattooColor => TattooColors.Count,
|
||||||
CustomizationId.Nose => NumNoseShapes,
|
CustomizeIndex.Eyebrows => NumEyebrows,
|
||||||
CustomizationId.Jaw => NumJawShapes,
|
CustomizeIndex.EyeColorLeft => EyeColors.Count,
|
||||||
CustomizationId.Mouth => NumMouthShapes,
|
CustomizeIndex.EyeShape => NumEyeShapes,
|
||||||
CustomizationId.LipColor => LipColorsLight.Count + LipColorsDark.Count,
|
CustomizeIndex.Nose => NumNoseShapes,
|
||||||
CustomizationId.TailEarShape => TailEarShapes.Count,
|
CustomizeIndex.Jaw => NumJawShapes,
|
||||||
CustomizationId.FacePaint => FacePaints.Count,
|
CustomizeIndex.Mouth => NumMouthShapes,
|
||||||
CustomizationId.FacePaintColor => FacePaintColorsLight.Count + FacePaintColorsDark.Count,
|
CustomizeIndex.LipColor => LipColorsLight.Count + LipColorsDark.Count,
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(id), id, null),
|
CustomizeIndex.TailShape => TailEarShapes.Count,
|
||||||
|
CustomizeIndex.FacePaint => FacePaints.Count,
|
||||||
|
CustomizeIndex.FacePaintColor => FacePaintColorsLight.Count + FacePaintColorsDark.Count,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(index), index, null),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private CustomizationByteValue HrothgarFaceHack(CustomizationByteValue value)
|
private CustomizeValue HrothgarFaceHack(CustomizeValue value)
|
||||||
=> Race == Race.Hrothgar && value.Value is > 4 and < 9 ? value - 4 : value;
|
=> Race == Race.Hrothgar && value.Value is > 4 and < 9 ? value - 4 : value;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,352 +1,87 @@
|
||||||
using System;
|
using System;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
|
||||||
|
|
||||||
namespace Glamourer.Customization;
|
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 unsafe struct Customize
|
||||||
{
|
{
|
||||||
public readonly CustomizeData* Data;
|
public readonly Penumbra.GameData.Structs.CustomizeData* Data;
|
||||||
|
|
||||||
public Customize(CustomizeData* data)
|
public Customize(Penumbra.GameData.Structs.CustomizeData* data)
|
||||||
=> Data = data;
|
=> Data = data;
|
||||||
|
|
||||||
public Race Race
|
public Race Race
|
||||||
{
|
{
|
||||||
get => (Race)Data->Data[0];
|
get => (Race)Data->Get(CustomizeIndex.Race).Value;
|
||||||
set => Data->Data[0] = (byte)value;
|
set => Data->Set(CustomizeIndex.Race, (CustomizeValue)(byte)value);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip Unknown Gender
|
|
||||||
public Gender Gender
|
public Gender Gender
|
||||||
{
|
{
|
||||||
get => (Gender)(Data->Data[1] + 1);
|
get => (Gender)Data->Get(CustomizeIndex.Gender).Value + 1;
|
||||||
set => Data->Data[1] = (byte)(value - 1);
|
set => Data->Set(CustomizeIndex.Gender, (CustomizeValue)(byte)value - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CustomizationByteValue BodyType
|
public CustomizeValue BodyType
|
||||||
{
|
{
|
||||||
get => (CustomizationByteValue)Data->Data[2];
|
get => Data->Get(CustomizeIndex.BodyType);
|
||||||
set => Data->Data[2] = value.Value;
|
set => Data->Set(CustomizeIndex.BodyType, value);
|
||||||
}
|
|
||||||
|
|
||||||
public CustomizationByteValue Height
|
|
||||||
{
|
|
||||||
get => (CustomizationByteValue)Data->Data[3];
|
|
||||||
set => Data->Data[3] = value.Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SubRace Clan
|
public SubRace Clan
|
||||||
{
|
{
|
||||||
get => (SubRace)Data->Data[4];
|
get => (SubRace)Data->Get(CustomizeIndex.Clan).Value;
|
||||||
set => Data->Data[4] = (byte)value;
|
set => Data->Set(CustomizeIndex.Clan, (CustomizeValue)(byte)value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CustomizationByteValue Face
|
public CustomizeValue Face
|
||||||
{
|
{
|
||||||
get => (CustomizationByteValue)Data->Data[5];
|
get => Data->Get(CustomizeIndex.Face);
|
||||||
set => Data->Data[5] = value.Value;
|
set => Data->Set(CustomizeIndex.Face, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public CustomizationByteValue Hairstyle
|
|
||||||
{
|
|
||||||
get => (CustomizationByteValue)Data->Data[6];
|
|
||||||
set => Data->Data[6] = value.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool HighlightsOn
|
public static readonly Penumbra.GameData.Structs.CustomizeData Default = GenerateDefault();
|
||||||
{
|
public static readonly Penumbra.GameData.Structs.CustomizeData Empty = new();
|
||||||
get => Data->Data[7] >> 7 == 1;
|
|
||||||
set => Data->Data[7] = (byte)(value ? Data->Data[7] | 0x80 : Data->Data[7] & 0x7F);
|
|
||||||
}
|
|
||||||
|
|
||||||
public CustomizationByteValue SkinColor
|
public CustomizeValue Get(CustomizeIndex index)
|
||||||
{
|
=> Data->Get(index);
|
||||||
get => (CustomizationByteValue)Data->Data[8];
|
|
||||||
set => Data->Data[8] = value.Value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CustomizationByteValue EyeColorRight
|
public void Set(CustomizeIndex flag, CustomizeValue index)
|
||||||
{
|
=> Data->Set(flag, index);
|
||||||
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)
|
public bool Equals(Customize other)
|
||||||
=> CustomizeData.Equals(Data, other.Data);
|
=> Penumbra.GameData.Structs.CustomizeData.Equals(Data, other.Data);
|
||||||
|
|
||||||
public CustomizationByteValue this[CustomizationId id]
|
public CustomizeValue this[CustomizeIndex index]
|
||||||
{
|
{
|
||||||
get => Get(id);
|
get => Get(index);
|
||||||
set => Set(id, value);
|
set => Set(index, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static CustomizeData GenerateDefault()
|
private static Penumbra.GameData.Structs.CustomizeData GenerateDefault()
|
||||||
{
|
{
|
||||||
var ret = new CustomizeData();
|
var ret = new Penumbra.GameData.Structs.CustomizeData();
|
||||||
var customize = new Customize(&ret)
|
ret.Set(CustomizeIndex.BodyType, (CustomizeValue)1);
|
||||||
{
|
ret.Set(CustomizeIndex.Height, (CustomizeValue)50);
|
||||||
Race = Race.Hyur,
|
ret.Set(CustomizeIndex.Face, (CustomizeValue)1);
|
||||||
Gender = Gender.Male,
|
ret.Set(CustomizeIndex.Hairstyle, (CustomizeValue)1);
|
||||||
BodyType = (CustomizationByteValue)1,
|
ret.Set(CustomizeIndex.SkinColor, (CustomizeValue)1);
|
||||||
Height = (CustomizationByteValue)50,
|
ret.Set(CustomizeIndex.EyeColorRight, (CustomizeValue)1);
|
||||||
Clan = SubRace.Midlander,
|
ret.Set(CustomizeIndex.HighlightsColor, (CustomizeValue)1);
|
||||||
Face = (CustomizationByteValue)1,
|
ret.Set(CustomizeIndex.TattooColor, (CustomizeValue)1);
|
||||||
Hairstyle = (CustomizationByteValue)1,
|
ret.Set(CustomizeIndex.Eyebrows, (CustomizeValue)1);
|
||||||
HighlightsOn = false,
|
ret.Set(CustomizeIndex.EyeColorLeft, (CustomizeValue)1);
|
||||||
SkinColor = (CustomizationByteValue)1,
|
ret.Set(CustomizeIndex.EyeShape, (CustomizeValue)1);
|
||||||
EyeColorRight = (CustomizationByteValue)1,
|
ret.Set(CustomizeIndex.Nose, (CustomizeValue)1);
|
||||||
HighlightsColor = (CustomizationByteValue)1,
|
ret.Set(CustomizeIndex.Jaw, (CustomizeValue)1);
|
||||||
TattooColor = (CustomizationByteValue)1,
|
ret.Set(CustomizeIndex.Mouth, (CustomizeValue)1);
|
||||||
Eyebrows = (CustomizationByteValue)1,
|
ret.Set(CustomizeIndex.LipColor, (CustomizeValue)1);
|
||||||
EyeColorLeft = (CustomizationByteValue)1,
|
ret.Set(CustomizeIndex.MuscleMass, (CustomizeValue)50);
|
||||||
EyeShape = (CustomizationByteValue)1,
|
ret.Set(CustomizeIndex.TailShape, (CustomizeValue)1);
|
||||||
Nose = (CustomizationByteValue)1,
|
ret.Set(CustomizeIndex.BustSize, (CustomizeValue)50);
|
||||||
Jaw = (CustomizationByteValue)1,
|
ret.Set(CustomizeIndex.FacePaint, (CustomizeValue)1);
|
||||||
Mouth = (CustomizationByteValue)1,
|
ret.Set(CustomizeIndex.FacePaintColor, (CustomizeValue)1);
|
||||||
LipColor = (CustomizationByteValue)1,
|
|
||||||
MuscleMass = (CustomizationByteValue)50,
|
|
||||||
TailShape = (CustomizationByteValue)1,
|
|
||||||
BustSize = (CustomizationByteValue)50,
|
|
||||||
FacePaint = (CustomizationByteValue)1,
|
|
||||||
FacePaintColor = (CustomizationByteValue)1,
|
|
||||||
};
|
|
||||||
customize.FacialFeatures.Clear();
|
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -355,4 +90,10 @@ public unsafe struct Customize
|
||||||
|
|
||||||
public void Write(IntPtr target)
|
public void Write(IntPtr target)
|
||||||
=> Data->Write((void*)target);
|
=> Data->Write((void*)target);
|
||||||
|
|
||||||
|
public bool LoadBase64(string data)
|
||||||
|
=> Data->LoadBase64(data);
|
||||||
|
|
||||||
|
public string WriteBase64()
|
||||||
|
=> Data->WriteBase64();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,13 @@ namespace Glamourer.Customization;
|
||||||
// Any customization value can be represented in 8 bytes by its ID,
|
// 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.
|
// a byte value, an optional value-id and an optional icon or color.
|
||||||
[StructLayout(LayoutKind.Explicit)]
|
[StructLayout(LayoutKind.Explicit)]
|
||||||
public readonly struct CustomizationData
|
public readonly struct CustomizeData
|
||||||
{
|
{
|
||||||
[FieldOffset(0)]
|
[FieldOffset(0)]
|
||||||
public readonly CustomizationId Id;
|
public readonly CustomizeIndex Index;
|
||||||
|
|
||||||
[FieldOffset(1)]
|
[FieldOffset(1)]
|
||||||
public readonly CustomizationByteValue Value;
|
public readonly CustomizeValue Value;
|
||||||
|
|
||||||
[FieldOffset(2)]
|
[FieldOffset(2)]
|
||||||
public readonly ushort CustomizeId;
|
public readonly ushort CustomizeId;
|
||||||
|
|
@ -22,9 +22,9 @@ public readonly struct CustomizationData
|
||||||
[FieldOffset(4)]
|
[FieldOffset(4)]
|
||||||
public readonly uint Color;
|
public readonly uint Color;
|
||||||
|
|
||||||
public CustomizationData(CustomizationId id, CustomizationByteValue value, uint data = 0, ushort customizeId = 0)
|
public CustomizeData(CustomizeIndex index, CustomizeValue value, uint data = 0, ushort customizeId = 0)
|
||||||
{
|
{
|
||||||
Id = id;
|
Index = index;
|
||||||
Value = value;
|
Value = value;
|
||||||
IconId = data;
|
IconId = data;
|
||||||
Color = data;
|
Color = data;
|
||||||
92
Glamourer.GameData/Customization/CustomizeFlag.cs
Normal file
92
Glamourer.GameData/Customization/CustomizeFlag.cs
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Glamourer.Customization;
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum CustomizeFlag : ulong
|
||||||
|
{
|
||||||
|
Invalid = 0,
|
||||||
|
Race = 1ul << CustomizeIndex.Race,
|
||||||
|
Gender = 1ul << CustomizeIndex.Gender,
|
||||||
|
BodyType = 1ul << CustomizeIndex.BodyType,
|
||||||
|
Height = 1ul << CustomizeIndex.Height,
|
||||||
|
Clan = 1ul << CustomizeIndex.Clan,
|
||||||
|
Face = 1ul << CustomizeIndex.Face,
|
||||||
|
Hairstyle = 1ul << CustomizeIndex.Hairstyle,
|
||||||
|
Highlights = 1ul << CustomizeIndex.Highlights,
|
||||||
|
SkinColor = 1ul << CustomizeIndex.SkinColor,
|
||||||
|
EyeColorRight = 1ul << CustomizeIndex.EyeColorRight,
|
||||||
|
HairColor = 1ul << CustomizeIndex.HairColor,
|
||||||
|
HighlightsColor = 1ul << CustomizeIndex.HighlightsColor,
|
||||||
|
FacialFeature1 = 1ul << CustomizeIndex.FacialFeature1,
|
||||||
|
FacialFeature2 = 1ul << CustomizeIndex.FacialFeature2,
|
||||||
|
FacialFeature3 = 1ul << CustomizeIndex.FacialFeature3,
|
||||||
|
FacialFeature4 = 1ul << CustomizeIndex.FacialFeature4,
|
||||||
|
FacialFeature5 = 1ul << CustomizeIndex.FacialFeature5,
|
||||||
|
FacialFeature6 = 1ul << CustomizeIndex.FacialFeature6,
|
||||||
|
FacialFeature7 = 1ul << CustomizeIndex.FacialFeature7,
|
||||||
|
LegacyTattoo = 1ul << CustomizeIndex.LegacyTattoo,
|
||||||
|
TattooColor = 1ul << CustomizeIndex.TattooColor,
|
||||||
|
Eyebrows = 1ul << CustomizeIndex.Eyebrows,
|
||||||
|
EyeColorLeft = 1ul << CustomizeIndex.EyeColorLeft,
|
||||||
|
EyeShape = 1ul << CustomizeIndex.EyeShape,
|
||||||
|
SmallIris = 1ul << CustomizeIndex.SmallIris,
|
||||||
|
Nose = 1ul << CustomizeIndex.Nose,
|
||||||
|
Jaw = 1ul << CustomizeIndex.Jaw,
|
||||||
|
Mouth = 1ul << CustomizeIndex.Mouth,
|
||||||
|
Lipstick = 1ul << CustomizeIndex.Lipstick,
|
||||||
|
LipColor = 1ul << CustomizeIndex.LipColor,
|
||||||
|
MuscleMass = 1ul << CustomizeIndex.MuscleMass,
|
||||||
|
TailShape = 1ul << CustomizeIndex.TailShape,
|
||||||
|
BustSize = 1ul << CustomizeIndex.BustSize,
|
||||||
|
FacePaint = 1ul << CustomizeIndex.FacePaint,
|
||||||
|
FacePaintReversed = 1ul << CustomizeIndex.FacePaintReversed,
|
||||||
|
FacePaintColor = 1ul << CustomizeIndex.FacePaintColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CustomizeFlagExtensions
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
public static CustomizeIndex ToIndex(this CustomizeFlag flag)
|
||||||
|
=> flag switch
|
||||||
|
{
|
||||||
|
CustomizeFlag.Race => CustomizeIndex.Race,
|
||||||
|
CustomizeFlag.Gender => CustomizeIndex.Gender,
|
||||||
|
CustomizeFlag.BodyType => CustomizeIndex.BodyType,
|
||||||
|
CustomizeFlag.Height => CustomizeIndex.Height,
|
||||||
|
CustomizeFlag.Clan => CustomizeIndex.Clan,
|
||||||
|
CustomizeFlag.Face => CustomizeIndex.Face,
|
||||||
|
CustomizeFlag.Hairstyle => CustomizeIndex.Hairstyle,
|
||||||
|
CustomizeFlag.Highlights => CustomizeIndex.Highlights,
|
||||||
|
CustomizeFlag.SkinColor => CustomizeIndex.SkinColor,
|
||||||
|
CustomizeFlag.EyeColorRight => CustomizeIndex.EyeColorRight,
|
||||||
|
CustomizeFlag.HairColor => CustomizeIndex.HairColor,
|
||||||
|
CustomizeFlag.HighlightsColor => CustomizeIndex.HighlightsColor,
|
||||||
|
CustomizeFlag.FacialFeature1 => CustomizeIndex.FacialFeature1,
|
||||||
|
CustomizeFlag.FacialFeature2 => CustomizeIndex.FacialFeature2,
|
||||||
|
CustomizeFlag.FacialFeature3 => CustomizeIndex.FacialFeature3,
|
||||||
|
CustomizeFlag.FacialFeature4 => CustomizeIndex.FacialFeature4,
|
||||||
|
CustomizeFlag.FacialFeature5 => CustomizeIndex.FacialFeature5,
|
||||||
|
CustomizeFlag.FacialFeature6 => CustomizeIndex.FacialFeature6,
|
||||||
|
CustomizeFlag.FacialFeature7 => CustomizeIndex.FacialFeature7,
|
||||||
|
CustomizeFlag.LegacyTattoo => CustomizeIndex.LegacyTattoo,
|
||||||
|
CustomizeFlag.TattooColor => CustomizeIndex.TattooColor,
|
||||||
|
CustomizeFlag.Eyebrows => CustomizeIndex.Eyebrows,
|
||||||
|
CustomizeFlag.EyeColorLeft => CustomizeIndex.EyeColorLeft,
|
||||||
|
CustomizeFlag.EyeShape => CustomizeIndex.EyeShape,
|
||||||
|
CustomizeFlag.SmallIris => CustomizeIndex.SmallIris,
|
||||||
|
CustomizeFlag.Nose => CustomizeIndex.Nose,
|
||||||
|
CustomizeFlag.Jaw => CustomizeIndex.Jaw,
|
||||||
|
CustomizeFlag.Mouth => CustomizeIndex.Mouth,
|
||||||
|
CustomizeFlag.Lipstick => CustomizeIndex.Lipstick,
|
||||||
|
CustomizeFlag.LipColor => CustomizeIndex.LipColor,
|
||||||
|
CustomizeFlag.MuscleMass => CustomizeIndex.MuscleMass,
|
||||||
|
CustomizeFlag.TailShape => CustomizeIndex.TailShape,
|
||||||
|
CustomizeFlag.BustSize => CustomizeIndex.BustSize,
|
||||||
|
CustomizeFlag.FacePaint => CustomizeIndex.FacePaint,
|
||||||
|
CustomizeFlag.FacePaintReversed => CustomizeIndex.FacePaintReversed,
|
||||||
|
CustomizeFlag.FacePaintColor => CustomizeIndex.FacePaintColor,
|
||||||
|
_ => (CustomizeIndex) byte.MaxValue,
|
||||||
|
};
|
||||||
|
}
|
||||||
173
Glamourer.GameData/Customization/CustomizeIndex.cs
Normal file
173
Glamourer.GameData/Customization/CustomizeIndex.cs
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Glamourer.Customization;
|
||||||
|
|
||||||
|
public enum CustomizeIndex : byte
|
||||||
|
{
|
||||||
|
Race,
|
||||||
|
Gender,
|
||||||
|
BodyType,
|
||||||
|
Height,
|
||||||
|
Clan,
|
||||||
|
Face,
|
||||||
|
Hairstyle,
|
||||||
|
Highlights,
|
||||||
|
SkinColor,
|
||||||
|
EyeColorRight,
|
||||||
|
HairColor,
|
||||||
|
HighlightsColor,
|
||||||
|
FacialFeature1,
|
||||||
|
FacialFeature2,
|
||||||
|
FacialFeature3,
|
||||||
|
FacialFeature4,
|
||||||
|
FacialFeature5,
|
||||||
|
FacialFeature6,
|
||||||
|
FacialFeature7,
|
||||||
|
LegacyTattoo,
|
||||||
|
TattooColor,
|
||||||
|
Eyebrows,
|
||||||
|
EyeColorLeft,
|
||||||
|
EyeShape,
|
||||||
|
SmallIris,
|
||||||
|
Nose,
|
||||||
|
Jaw,
|
||||||
|
Mouth,
|
||||||
|
Lipstick,
|
||||||
|
LipColor,
|
||||||
|
MuscleMass,
|
||||||
|
TailShape,
|
||||||
|
BustSize,
|
||||||
|
FacePaint,
|
||||||
|
FacePaintReversed,
|
||||||
|
FacePaintColor,
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class CustomizationExtensions
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
public static (int ByteIdx, byte Mask) ToByteAndMask(this CustomizeIndex index)
|
||||||
|
=> index switch
|
||||||
|
{
|
||||||
|
CustomizeIndex.Race => (0, 0xFF),
|
||||||
|
CustomizeIndex.Gender => (1, 0xFF),
|
||||||
|
CustomizeIndex.BodyType => (2, 0xFF),
|
||||||
|
CustomizeIndex.Height => (3, 0xFF),
|
||||||
|
CustomizeIndex.Clan => (4, 0xFF),
|
||||||
|
CustomizeIndex.Face => (5, 0xFF),
|
||||||
|
CustomizeIndex.Hairstyle => (6, 0xFF),
|
||||||
|
CustomizeIndex.Highlights => (7, 0xFF),
|
||||||
|
CustomizeIndex.SkinColor => (8, 0xFF),
|
||||||
|
CustomizeIndex.EyeColorRight => (9, 0xFF),
|
||||||
|
CustomizeIndex.HairColor => (10, 0xFF),
|
||||||
|
CustomizeIndex.HighlightsColor => (11, 0xFF),
|
||||||
|
CustomizeIndex.FacialFeature1 => (12, 0x01),
|
||||||
|
CustomizeIndex.FacialFeature2 => (12, 0x02),
|
||||||
|
CustomizeIndex.FacialFeature3 => (12, 0x04),
|
||||||
|
CustomizeIndex.FacialFeature4 => (12, 0x08),
|
||||||
|
CustomizeIndex.FacialFeature5 => (12, 0x10),
|
||||||
|
CustomizeIndex.FacialFeature6 => (12, 0x20),
|
||||||
|
CustomizeIndex.FacialFeature7 => (12, 0x40),
|
||||||
|
CustomizeIndex.LegacyTattoo => (12, 0x80),
|
||||||
|
CustomizeIndex.TattooColor => (13, 0xFF),
|
||||||
|
CustomizeIndex.Eyebrows => (14, 0xFF),
|
||||||
|
CustomizeIndex.EyeColorLeft => (15, 0xFF),
|
||||||
|
CustomizeIndex.EyeShape => (16, 0x7F),
|
||||||
|
CustomizeIndex.SmallIris => (16, 0x80),
|
||||||
|
CustomizeIndex.Nose => (17, 0xFF),
|
||||||
|
CustomizeIndex.Jaw => (18, 0xFF),
|
||||||
|
CustomizeIndex.Mouth => (19, 0x7F),
|
||||||
|
CustomizeIndex.Lipstick => (19, 0x80),
|
||||||
|
CustomizeIndex.LipColor => (20, 0xFF),
|
||||||
|
CustomizeIndex.MuscleMass => (21, 0xFF),
|
||||||
|
CustomizeIndex.TailShape => (22, 0xFF),
|
||||||
|
CustomizeIndex.BustSize => (23, 0xFF),
|
||||||
|
CustomizeIndex.FacePaint => (24, 0x7F),
|
||||||
|
CustomizeIndex.FacePaintReversed => (24, 0x80),
|
||||||
|
CustomizeIndex.FacePaintColor => (25, 0xFF),
|
||||||
|
_ => (0, 0x00),
|
||||||
|
};
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
public static CustomizeFlag ToFlag(this CustomizeIndex index)
|
||||||
|
=> (CustomizeFlag)(1ul << (int)index);
|
||||||
|
|
||||||
|
public static string ToDefaultName(this CustomizeIndex customizeIndex)
|
||||||
|
=> customizeIndex switch
|
||||||
|
{
|
||||||
|
CustomizeIndex.Race => "Race",
|
||||||
|
CustomizeIndex.Gender => "Gender",
|
||||||
|
CustomizeIndex.BodyType => "Body Type",
|
||||||
|
CustomizeIndex.Height => "Height",
|
||||||
|
CustomizeIndex.Clan => "Clan",
|
||||||
|
CustomizeIndex.Face => "Head Style",
|
||||||
|
CustomizeIndex.Hairstyle => "Hair Style",
|
||||||
|
CustomizeIndex.Highlights => "Highlights",
|
||||||
|
CustomizeIndex.SkinColor => "Skin Color",
|
||||||
|
CustomizeIndex.EyeColorRight => "Right Eye Color",
|
||||||
|
CustomizeIndex.HairColor => "Hair Color",
|
||||||
|
CustomizeIndex.HighlightsColor => "Highlights Color",
|
||||||
|
CustomizeIndex.TattooColor => "Tattoo Color",
|
||||||
|
CustomizeIndex.Eyebrows => "Eyebrow Style",
|
||||||
|
CustomizeIndex.EyeColorLeft => "Left Eye Color",
|
||||||
|
CustomizeIndex.EyeShape => "Eye Shape",
|
||||||
|
CustomizeIndex.Nose => "Nose Style",
|
||||||
|
CustomizeIndex.Jaw => "Jaw Style",
|
||||||
|
CustomizeIndex.Mouth => "Mouth Style",
|
||||||
|
CustomizeIndex.MuscleMass => "Muscle Tone",
|
||||||
|
CustomizeIndex.TailShape => "Tail Shape",
|
||||||
|
CustomizeIndex.BustSize => "Bust Size",
|
||||||
|
CustomizeIndex.FacePaint => "Face Paint",
|
||||||
|
CustomizeIndex.FacePaintColor => "Face Paint Color",
|
||||||
|
CustomizeIndex.LipColor => "Lip Color",
|
||||||
|
CustomizeIndex.FacialFeature1 => "Facial Feature 1",
|
||||||
|
CustomizeIndex.FacialFeature2 => "Facial Feature 2",
|
||||||
|
CustomizeIndex.FacialFeature3 => "Facial Feature 3",
|
||||||
|
CustomizeIndex.FacialFeature4 => "Facial Feature 4",
|
||||||
|
CustomizeIndex.FacialFeature5 => "Facial Feature 5",
|
||||||
|
CustomizeIndex.FacialFeature6 => "Facial Feature 6",
|
||||||
|
CustomizeIndex.FacialFeature7 => "Facial Feature 7",
|
||||||
|
CustomizeIndex.LegacyTattoo => "Legacy Tattoo",
|
||||||
|
CustomizeIndex.SmallIris => "Small Iris",
|
||||||
|
CustomizeIndex.Lipstick => "Enable Lipstick",
|
||||||
|
CustomizeIndex.FacePaintReversed => "Reverse Face Paint",
|
||||||
|
_ => string.Empty,
|
||||||
|
};
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
public static unsafe CustomizeValue Get(this in Penumbra.GameData.Structs.CustomizeData data, CustomizeIndex index)
|
||||||
|
{
|
||||||
|
var (offset, mask) = index.ToByteAndMask();
|
||||||
|
return (CustomizeValue)(data.Data[offset] & mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
public static unsafe bool Set(this ref Penumbra.GameData.Structs.CustomizeData data, CustomizeIndex index, CustomizeValue value)
|
||||||
|
{
|
||||||
|
var (offset, mask) = index.ToByteAndMask();
|
||||||
|
return mask != 0xFF
|
||||||
|
? SetIfDifferentMasked(ref data.Data[offset], value, mask)
|
||||||
|
: SetIfDifferent(ref data.Data[offset], value);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
private static bool SetIfDifferentMasked(ref byte oldValue, CustomizeValue newValue, byte mask)
|
||||||
|
{
|
||||||
|
var tmp = (byte)(newValue.Value & mask);
|
||||||
|
tmp = (byte)(tmp | (oldValue & ~mask));
|
||||||
|
if (oldValue == tmp)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
oldValue = tmp;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||||
|
private static bool SetIfDifferent(ref byte oldValue, CustomizeValue newValue)
|
||||||
|
{
|
||||||
|
if (oldValue == newValue.Value)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
oldValue = newValue.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
34
Glamourer.GameData/Customization/CustomizeValue.cs
Normal file
34
Glamourer.GameData/Customization/CustomizeValue.cs
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
namespace Glamourer.Customization;
|
||||||
|
|
||||||
|
public record struct CustomizeValue(byte Value)
|
||||||
|
{
|
||||||
|
public static readonly CustomizeValue Zero = new(0);
|
||||||
|
public static readonly CustomizeValue Max = new(0xFF);
|
||||||
|
|
||||||
|
public static CustomizeValue Bool(bool b)
|
||||||
|
=> b ? Max : Zero;
|
||||||
|
|
||||||
|
public static explicit operator CustomizeValue(byte value)
|
||||||
|
=> new(value);
|
||||||
|
|
||||||
|
public static CustomizeValue operator ++(CustomizeValue v)
|
||||||
|
=> new(++v.Value);
|
||||||
|
|
||||||
|
public static CustomizeValue operator --(CustomizeValue v)
|
||||||
|
=> new(--v.Value);
|
||||||
|
|
||||||
|
public static bool operator <(CustomizeValue v, int count)
|
||||||
|
=> v.Value < count;
|
||||||
|
|
||||||
|
public static bool operator >(CustomizeValue v, int count)
|
||||||
|
=> v.Value > count;
|
||||||
|
|
||||||
|
public static CustomizeValue operator +(CustomizeValue v, int rhs)
|
||||||
|
=> new((byte)(v.Value + rhs));
|
||||||
|
|
||||||
|
public static CustomizeValue operator -(CustomizeValue v, int rhs)
|
||||||
|
=> new((byte)(v.Value - rhs));
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
=> Value.ToString();
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
using Dalamud.Data;
|
using Dalamud.Data;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
|
@ -187,6 +188,7 @@ public class RestrictedGear
|
||||||
_femaleToMale.TryAdd(fModelIdSlot, mModelIdSlot);
|
_femaleToMale.TryAdd(fModelIdSlot, mModelIdSlot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
// Add all currently existing and known gender restricted items.
|
// Add all currently existing and known gender restricted items.
|
||||||
private void AddKnown()
|
private void AddKnown()
|
||||||
{
|
{
|
||||||
|
|
@ -431,4 +433,5 @@ public class RestrictedGear
|
||||||
0x0102E8,
|
0x0102E8,
|
||||||
0x010245,
|
0x010245,
|
||||||
};
|
};
|
||||||
|
// @Formatter:on
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,17 +27,17 @@ public class GlamourerIpc : IDisposable
|
||||||
private readonly ObjectTable _objectTable;
|
private readonly ObjectTable _objectTable;
|
||||||
private readonly DalamudPluginInterface _pluginInterface;
|
private readonly DalamudPluginInterface _pluginInterface;
|
||||||
|
|
||||||
//internal ICallGateProvider<string, string?>? ProviderGetAllCustomization;
|
internal ICallGateProvider<string, string?>? ProviderGetAllCustomization;
|
||||||
//internal ICallGateProvider<Character?, string?>? ProviderGetAllCustomizationFromCharacter;
|
internal ICallGateProvider<Character?, string?>? ProviderGetAllCustomizationFromCharacter;
|
||||||
//internal ICallGateProvider<string, string, object>? ProviderApplyAll;
|
internal ICallGateProvider<string, string, object>? ProviderApplyAll;
|
||||||
//internal ICallGateProvider<string, Character?, object>? ProviderApplyAllToCharacter;
|
internal ICallGateProvider<string, Character?, object>? ProviderApplyAllToCharacter;
|
||||||
//internal ICallGateProvider<string, string, object>? ProviderApplyOnlyCustomization;
|
internal ICallGateProvider<string, string, object>? ProviderApplyOnlyCustomization;
|
||||||
//internal ICallGateProvider<string, Character?, object>? ProviderApplyOnlyCustomizationToCharacter;
|
internal ICallGateProvider<string, Character?, object>? ProviderApplyOnlyCustomizationToCharacter;
|
||||||
//internal ICallGateProvider<string, string, object>? ProviderApplyOnlyEquipment;
|
internal ICallGateProvider<string, string, object>? ProviderApplyOnlyEquipment;
|
||||||
//internal ICallGateProvider<string, Character?, object>? ProviderApplyOnlyEquipmentToCharacter;
|
internal ICallGateProvider<string, Character?, object>? ProviderApplyOnlyEquipmentToCharacter;
|
||||||
//internal ICallGateProvider<string, object>? ProviderRevert;
|
internal ICallGateProvider<string, object>? ProviderRevert;
|
||||||
//internal ICallGateProvider<Character?, object>? ProviderRevertCharacter;
|
internal ICallGateProvider<Character?, object>? ProviderRevertCharacter;
|
||||||
//internal ICallGateProvider<int>? ProviderGetApiVersion;
|
internal ICallGateProvider<int>? ProviderGetApiVersion;
|
||||||
|
|
||||||
public GlamourerIpc(ClientState clientState, ObjectTable objectTable, DalamudPluginInterface pluginInterface)
|
public GlamourerIpc(ClientState clientState, ObjectTable objectTable, DalamudPluginInterface pluginInterface)
|
||||||
{
|
{
|
||||||
|
|
@ -53,31 +53,31 @@ public class GlamourerIpc : IDisposable
|
||||||
|
|
||||||
private void DisposeProviders()
|
private void DisposeProviders()
|
||||||
{
|
{
|
||||||
// ProviderGetAllCustomization?.UnregisterFunc();
|
ProviderGetAllCustomization?.UnregisterFunc();
|
||||||
// ProviderGetAllCustomizationFromCharacter?.UnregisterFunc();
|
ProviderGetAllCustomizationFromCharacter?.UnregisterFunc();
|
||||||
// ProviderApplyAll?.UnregisterAction();
|
ProviderApplyAll?.UnregisterAction();
|
||||||
// ProviderApplyAllToCharacter?.UnregisterAction();
|
ProviderApplyAllToCharacter?.UnregisterAction();
|
||||||
// ProviderApplyOnlyCustomization?.UnregisterAction();
|
ProviderApplyOnlyCustomization?.UnregisterAction();
|
||||||
// ProviderApplyOnlyCustomizationToCharacter?.UnregisterAction();
|
ProviderApplyOnlyCustomizationToCharacter?.UnregisterAction();
|
||||||
// ProviderApplyOnlyEquipment?.UnregisterAction();
|
ProviderApplyOnlyEquipment?.UnregisterAction();
|
||||||
// ProviderApplyOnlyEquipmentToCharacter?.UnregisterAction();
|
ProviderApplyOnlyEquipmentToCharacter?.UnregisterAction();
|
||||||
// ProviderRevert?.UnregisterAction();
|
ProviderRevert?.UnregisterAction();
|
||||||
// ProviderRevertCharacter?.UnregisterAction();
|
ProviderRevertCharacter?.UnregisterAction();
|
||||||
// ProviderGetApiVersion?.UnregisterFunc();
|
ProviderGetApiVersion?.UnregisterFunc();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitializeProviders()
|
private void InitializeProviders()
|
||||||
{
|
{
|
||||||
//try
|
try
|
||||||
//{
|
{
|
||||||
// ProviderGetApiVersion = _pluginInterface.GetIpcProvider<int>(LabelProviderApiVersion);
|
ProviderGetApiVersion = _pluginInterface.GetIpcProvider<int>(LabelProviderApiVersion);
|
||||||
// ProviderGetApiVersion.RegisterFunc(GetApiVersion);
|
ProviderGetApiVersion.RegisterFunc(GetApiVersion);
|
||||||
//}
|
}
|
||||||
//catch (Exception ex)
|
catch (Exception ex)
|
||||||
//{
|
{
|
||||||
// PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApiVersion}.");
|
PluginLog.Error(ex, $"Error registering IPC provider for {LabelProviderApiVersion}.");
|
||||||
//}
|
}
|
||||||
//
|
|
||||||
//try
|
//try
|
||||||
//{
|
//{
|
||||||
// ProviderGetAllCustomization = _pluginInterface.GetIpcProvider<string, string?>(LabelProviderGetAllCustomization);
|
// ProviderGetAllCustomization = _pluginInterface.GetIpcProvider<string, string?>(LabelProviderGetAllCustomization);
|
||||||
|
|
@ -187,8 +187,8 @@ public class GlamourerIpc : IDisposable
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
//private static int GetApiVersion()
|
private static int GetApiVersion()
|
||||||
// => CurrentApiVersion;
|
=> CurrentApiVersion;
|
||||||
//
|
//
|
||||||
//private void ApplyAll(string customization, string characterName)
|
//private void ApplyAll(string customization, string characterName)
|
||||||
//{
|
//{
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,42 @@
|
||||||
using System;
|
using System;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using Dalamud.Plugin.Ipc;
|
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
using Glamourer.Structs;
|
using Glamourer.Structs;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
|
using Penumbra.Api;
|
||||||
|
using Penumbra.Api.Enums;
|
||||||
|
using Penumbra.Api.Helpers;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
namespace Glamourer.Api;
|
namespace Glamourer.Api;
|
||||||
|
|
||||||
public class PenumbraAttach : IDisposable
|
public unsafe class PenumbraAttach : IDisposable
|
||||||
{
|
{
|
||||||
public const int RequiredPenumbraBreakingVersion = 4;
|
public const int RequiredPenumbraBreakingVersion = 4;
|
||||||
public const int RequiredPenumbraFeatureVersion = 12;
|
public const int RequiredPenumbraFeatureVersion = 15;
|
||||||
|
|
||||||
private ICallGateSubscriber<ChangedItemType, uint, object>? _tooltipSubscriber;
|
private EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
|
||||||
private ICallGateSubscriber<MouseButton, ChangedItemType, uint, object>? _clickSubscriber;
|
private EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
|
||||||
private ICallGateSubscriber<string, int, object>? _redrawSubscriberName;
|
private ActionSubscriber<GameObject, RedrawType> _redrawSubscriber;
|
||||||
private ICallGateSubscriber<GameObject, int, object>? _redrawSubscriberObject;
|
private FuncSubscriber<nint, (nint, string)> _drawObjectInfo;
|
||||||
private ICallGateSubscriber<IntPtr, (IntPtr, string)>? _drawObjectInfo;
|
public EventSubscriber<nint, string, nint, nint, nint> CreatingCharacterBase;
|
||||||
private ICallGateSubscriber<IntPtr, string, IntPtr, IntPtr, IntPtr, object?>? _creatingCharacterBase;
|
public EventSubscriber<nint, string, nint> CreatedCharacterBase;
|
||||||
private ICallGateSubscriber<IntPtr, string, IntPtr, object?>? _createdCharacterBase;
|
private FuncSubscriber<int, int> _cutsceneParent;
|
||||||
private ICallGateSubscriber<int, int>? _cutsceneParent;
|
|
||||||
|
|
||||||
private readonly ICallGateSubscriber<object?> _initializedEvent;
|
private readonly EventSubscriber _initializedEvent;
|
||||||
private readonly ICallGateSubscriber<object?> _disposedEvent;
|
private readonly EventSubscriber _disposedEvent;
|
||||||
|
public bool Available { get; private set; }
|
||||||
public event Action<IntPtr, IntPtr, IntPtr, IntPtr>? CreatingCharacterBase;
|
|
||||||
public event Action<IntPtr, IntPtr>? CreatedCharacterBase;
|
|
||||||
|
|
||||||
public PenumbraAttach(bool attach)
|
public PenumbraAttach(bool attach)
|
||||||
{
|
{
|
||||||
_initializedEvent = Dalamud.PluginInterface.GetIpcSubscriber<object?>("Penumbra.Initialized");
|
_initializedEvent = Ipc.Initialized.Subscriber(Dalamud.PluginInterface, Reattach);
|
||||||
_disposedEvent = Dalamud.PluginInterface.GetIpcSubscriber<object?>("Penumbra.Disposed");
|
_disposedEvent = Ipc.Disposed.Subscriber(Dalamud.PluginInterface, Unattach);
|
||||||
_initializedEvent.Subscribe(Reattach);
|
_tooltipSubscriber = Ipc.ChangedItemTooltip.Subscriber(Dalamud.PluginInterface, PenumbraTooltip);
|
||||||
_disposedEvent.Subscribe(Unattach);
|
_clickSubscriber = Ipc.ChangedItemClick.Subscriber(Dalamud.PluginInterface, PenumbraRightClick);
|
||||||
|
CreatedCharacterBase = Ipc.CreatedCharacterBase.Subscriber(Dalamud.PluginInterface);
|
||||||
|
CreatingCharacterBase = Ipc.CreatingCharacterBase.Subscriber(Dalamud.PluginInterface);
|
||||||
Reattach(attach);
|
Reattach(attach);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,31 +49,22 @@ public class PenumbraAttach : IDisposable
|
||||||
{
|
{
|
||||||
Unattach();
|
Unattach();
|
||||||
|
|
||||||
var versionSubscriber = Dalamud.PluginInterface.GetIpcSubscriber<(int, int)>("Penumbra.ApiVersions");
|
var (breaking, feature) = Ipc.ApiVersions.Subscriber(Dalamud.PluginInterface).Invoke();
|
||||||
var (breaking, feature) = versionSubscriber.InvokeFunc();
|
|
||||||
if (breaking != RequiredPenumbraBreakingVersion || feature < RequiredPenumbraFeatureVersion)
|
if (breaking != RequiredPenumbraBreakingVersion || feature < RequiredPenumbraFeatureVersion)
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
$"Invalid Version {breaking}.{feature:D4}, required major Version {RequiredPenumbraBreakingVersion} with feature greater or equal to {RequiredPenumbraFeatureVersion}.");
|
$"Invalid Version {breaking}.{feature:D4}, required major Version {RequiredPenumbraBreakingVersion} with feature greater or equal to {RequiredPenumbraFeatureVersion}.");
|
||||||
|
|
||||||
_redrawSubscriberName = Dalamud.PluginInterface.GetIpcSubscriber<string, int, object>("Penumbra.RedrawObjectByName");
|
|
||||||
_redrawSubscriberObject = Dalamud.PluginInterface.GetIpcSubscriber<GameObject, int, object>("Penumbra.RedrawObject");
|
|
||||||
_drawObjectInfo = Dalamud.PluginInterface.GetIpcSubscriber<IntPtr, (IntPtr, string)>("Penumbra.GetDrawObjectInfo");
|
|
||||||
_cutsceneParent = Dalamud.PluginInterface.GetIpcSubscriber<int, int>("Penumbra.GetCutsceneParentIndex");
|
|
||||||
|
|
||||||
if (!attach)
|
if (!attach)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_tooltipSubscriber = Dalamud.PluginInterface.GetIpcSubscriber<ChangedItemType, uint, object>("Penumbra.ChangedItemTooltip");
|
_tooltipSubscriber.Enable();
|
||||||
_clickSubscriber =
|
_clickSubscriber.Enable();
|
||||||
Dalamud.PluginInterface.GetIpcSubscriber<MouseButton, ChangedItemType, uint, object>("Penumbra.ChangedItemClick");
|
CreatingCharacterBase.Enable();
|
||||||
_creatingCharacterBase =
|
CreatedCharacterBase.Enable();
|
||||||
Dalamud.PluginInterface.GetIpcSubscriber<IntPtr, string, IntPtr, IntPtr, IntPtr, object?>("Penumbra.CreatingCharacterBase");
|
_drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(Dalamud.PluginInterface);
|
||||||
_createdCharacterBase =
|
_cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(Dalamud.PluginInterface);
|
||||||
Dalamud.PluginInterface.GetIpcSubscriber<IntPtr, string, IntPtr, object?>("Penumbra.CreatedCharacterBase");
|
_redrawSubscriber = Ipc.RedrawObject.Subscriber(Dalamud.PluginInterface);
|
||||||
_tooltipSubscriber.Subscribe(PenumbraTooltip);
|
Available = true;
|
||||||
_clickSubscriber.Subscribe(PenumbraRightClick);
|
|
||||||
_creatingCharacterBase.Subscribe(SubscribeCreatingCharacterBase);
|
|
||||||
_createdCharacterBase.Subscribe(SubscribeCreatedCharacterBase);
|
|
||||||
PluginLog.Debug("Glamourer attached to Penumbra.");
|
PluginLog.Debug("Glamourer attached to Penumbra.");
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
|
|
@ -81,35 +73,28 @@ public class PenumbraAttach : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SubscribeCreatingCharacterBase(IntPtr gameObject, string _, IntPtr modelId, IntPtr customize, IntPtr equipment)
|
|
||||||
=> CreatingCharacterBase?.Invoke(gameObject, modelId, customize, equipment);
|
|
||||||
|
|
||||||
private void SubscribeCreatedCharacterBase(IntPtr gameObject, string _, IntPtr drawObject)
|
|
||||||
=> CreatedCharacterBase?.Invoke(gameObject, drawObject);
|
|
||||||
|
|
||||||
public void Unattach()
|
public void Unattach()
|
||||||
{
|
{
|
||||||
_tooltipSubscriber?.Unsubscribe(PenumbraTooltip);
|
_tooltipSubscriber.Disable();
|
||||||
_clickSubscriber?.Unsubscribe(PenumbraRightClick);
|
_clickSubscriber.Disable();
|
||||||
_creatingCharacterBase?.Unsubscribe(SubscribeCreatingCharacterBase);
|
CreatingCharacterBase.Disable();
|
||||||
_createdCharacterBase?.Unsubscribe(SubscribeCreatedCharacterBase);
|
CreatedCharacterBase.Disable();
|
||||||
_tooltipSubscriber = null;
|
if (Available)
|
||||||
_clickSubscriber = null;
|
|
||||||
_creatingCharacterBase = null;
|
|
||||||
_redrawSubscriberName = null;
|
|
||||||
_drawObjectInfo = null;
|
|
||||||
if (_redrawSubscriberObject != null)
|
|
||||||
{
|
{
|
||||||
PluginLog.Debug("Glamourer detached from Penumbra.");
|
Available = false;
|
||||||
_redrawSubscriberObject = null;
|
Glamourer.Log.Debug("Glamourer detached from Penumbra.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_initializedEvent.Unsubscribe(Reattach);
|
|
||||||
_disposedEvent.Unsubscribe(Unattach);
|
|
||||||
Unattach();
|
Unattach();
|
||||||
|
_tooltipSubscriber.Dispose();
|
||||||
|
_clickSubscriber.Dispose();
|
||||||
|
CreatingCharacterBase.Dispose();
|
||||||
|
CreatedCharacterBase.Dispose();
|
||||||
|
_initializedEvent.Dispose();
|
||||||
|
_disposedEvent.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void PenumbraTooltip(ChangedItemType type, uint _)
|
private static void PenumbraTooltip(ChangedItemType type, uint _)
|
||||||
|
|
@ -118,7 +103,7 @@ public class PenumbraAttach : IDisposable
|
||||||
ImGui.Text("Right click to apply to current Glamourer Set. [Glamourer]");
|
ImGui.Text("Right click to apply to current Glamourer Set. [Glamourer]");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PenumbraRightClick(MouseButton button, ChangedItemType type, uint id)
|
private static void PenumbraRightClick(MouseButton button, ChangedItemType type, uint id)
|
||||||
{
|
{
|
||||||
if (button != MouseButton.Right || type != ChangedItemType.Item)
|
if (button != MouseButton.Right || type != ChangedItemType.Item)
|
||||||
return;
|
return;
|
||||||
|
|
@ -166,43 +151,23 @@ public class PenumbraAttach : IDisposable
|
||||||
}
|
}
|
||||||
|
|
||||||
public Actor GameObjectFromDrawObject(IntPtr drawObject)
|
public Actor GameObjectFromDrawObject(IntPtr drawObject)
|
||||||
=> _drawObjectInfo?.InvokeFunc(drawObject).Item1 ?? IntPtr.Zero;
|
=> Available ? _drawObjectInfo.Invoke(drawObject).Item1 : IntPtr.Zero;
|
||||||
|
|
||||||
public int CutsceneParent(int idx)
|
public int CutsceneParent(int idx)
|
||||||
=> _cutsceneParent?.InvokeFunc(idx) ?? -1;
|
=> Available ? _cutsceneParent.Invoke(idx) : -1;
|
||||||
|
|
||||||
public void RedrawObject(GameObject? actor, RedrawType settings, bool repeat)
|
public void RedrawObject(GameObject? actor, RedrawType settings)
|
||||||
{
|
{
|
||||||
if (actor == null)
|
if (actor == null || !Available)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (_redrawSubscriberObject != null)
|
try
|
||||||
{
|
{
|
||||||
try
|
_redrawSubscriber.Invoke(actor, settings);
|
||||||
{
|
|
||||||
_redrawSubscriberObject.InvokeAction(actor, (int)settings);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
if (repeat)
|
|
||||||
{
|
|
||||||
Reattach(Glamourer.Config.AttachToPenumbra);
|
|
||||||
RedrawObject(actor, settings, false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
PluginLog.Debug($"Failure redrawing object:\n{e}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (repeat)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Reattach(Glamourer.Config.AttachToPenumbra);
|
PluginLog.Debug($"Failure redrawing object:\n{e}");
|
||||||
RedrawObject(actor, settings, false);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
PluginLog.Debug("Trying to redraw object, but not attached to Penumbra.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Glamourer.State;
|
|
||||||
using Glamourer.Structs;
|
|
||||||
using Penumbra.GameData.Structs;
|
|
||||||
|
|
||||||
namespace Glamourer.Designs;
|
|
||||||
|
|
||||||
public class Design
|
|
||||||
{
|
|
||||||
public string Name { get; }
|
|
||||||
public bool ReadOnly;
|
|
||||||
|
|
||||||
public DateTimeOffset CreationDate { get; }
|
|
||||||
public DateTimeOffset LastUpdateDate { get; }
|
|
||||||
public CharacterSave Data { get; }
|
|
||||||
|
|
||||||
public override string ToString()
|
|
||||||
=> Name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct ArmorData
|
|
||||||
{
|
|
||||||
public CharacterArmor Model;
|
|
||||||
public bool Ignore;
|
|
||||||
}
|
|
||||||
|
|
@ -12,6 +12,7 @@ using Glamourer.Structs;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
using Glamourer.Saves;
|
||||||
|
|
||||||
namespace Glamourer.Designs;
|
namespace Glamourer.Designs;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,14 +47,14 @@ public class Glamourer : IDalamudPlugin
|
||||||
//public static RevertableDesigns RevertableDesigns = new();
|
//public static RevertableDesigns RevertableDesigns = new();
|
||||||
//public readonly GlamourerIpc GlamourerIpc;
|
//public readonly GlamourerIpc GlamourerIpc;
|
||||||
|
|
||||||
public unsafe Glamourer(DalamudPluginInterface pluginInterface)
|
public Glamourer(DalamudPluginInterface pluginInterface)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Dalamud.Initialize(pluginInterface);
|
Dalamud.Initialize(pluginInterface);
|
||||||
Log = new Logger();
|
Log = new Logger();
|
||||||
|
|
||||||
Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData, Dalamud.ClientState.ClientLanguage);
|
Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData);
|
||||||
RestrictedGear = GameData.RestrictedGear(Dalamud.GameData);
|
RestrictedGear = GameData.RestrictedGear(Dalamud.GameData);
|
||||||
Models = GameData.Models(Dalamud.GameData);
|
Models = GameData.Models(Dalamud.GameData);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -84,6 +84,7 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Glamourer.GameData\Glamourer.GameData.csproj" />
|
<ProjectReference Include="..\Glamourer.GameData\Glamourer.GameData.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Penumbra\Penumbra.Api\Penumbra.Api.csproj" />
|
||||||
<ProjectReference Include="..\..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj" />
|
<ProjectReference Include="..\..\Penumbra\Penumbra.GameData\Penumbra.GameData.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
@ -118,7 +119,7 @@
|
||||||
</None>
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||||
<Exec Command="if $(Configuration) == Release powershell Compress-Archive -Force $(TargetPath), $(TargetDir)$(SolutionName).json, $(TargetDir)$(SolutionName).GameData.dll, $(TargetDir)Penumbra.GameData.dll $(SolutionDir)$(SolutionName).zip" />
|
<Exec Command="if $(Configuration) == Release powershell Compress-Archive -Force $(TargetPath), $(TargetDir)$(SolutionName).json, $(TargetDir)$(SolutionName).GameData.dll, $(TargetDir)Penumbra.GameData.dll, $(TargetDir)Penumbra.Api.dll $(SolutionDir)$(SolutionName).zip" />
|
||||||
<Exec Command="if $(Configuration) == Release powershell Copy-Item -Force $(TargetDir)$(SolutionName).json -Destination $(SolutionDir)" />
|
<Exec Command="if $(Configuration) == Release powershell Copy-Item -Force $(TargetDir)$(SolutionName).json -Destination $(SolutionDir)" />
|
||||||
</Target>
|
</Target>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
@ -9,10 +9,10 @@ internal partial class CustomizationDrawer
|
||||||
{
|
{
|
||||||
private const string ColorPickerPopupName = "ColorPicker";
|
private const string ColorPickerPopupName = "ColorPicker";
|
||||||
|
|
||||||
private void DrawColorPicker(CustomizationId id)
|
private void DrawColorPicker(CustomizeIndex index)
|
||||||
{
|
{
|
||||||
using var _ = SetId(id);
|
using var _ = SetId(index);
|
||||||
var (current, custom) = GetCurrentCustomization(id);
|
var (current, custom) = GetCurrentCustomization(index);
|
||||||
var color = ImGui.ColorConvertU32ToFloat4(custom.Color);
|
var color = ImGui.ColorConvertU32ToFloat4(custom.Color);
|
||||||
|
|
||||||
// Print 1-based index instead of 0.
|
// Print 1-based index instead of 0.
|
||||||
|
|
@ -40,7 +40,7 @@ internal partial class CustomizationDrawer
|
||||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||||
for (var i = 0; i < _currentCount; ++i)
|
for (var i = 0; i < _currentCount; ++i)
|
||||||
{
|
{
|
||||||
var custom = _set.Data(_currentId, i, _customize[CustomizationId.Face]);
|
var custom = _set.Data(_currentIndex, i, _customize[CustomizeIndex.Face]);
|
||||||
if (ImGui.ColorButton((i + 1).ToString(), ImGui.ColorConvertU32ToFloat4(custom.Color)))
|
if (ImGui.ColorButton((i + 1).ToString(), ImGui.ColorConvertU32ToFloat4(custom.Color)))
|
||||||
{
|
{
|
||||||
UpdateValue(custom.Value);
|
UpdateValue(custom.Value);
|
||||||
|
|
@ -53,13 +53,13 @@ internal partial class CustomizationDrawer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtain the current customization and print a warning if it is not known.
|
// Obtain the current customization and print a warning if it is not known.
|
||||||
private (int, CustomizationData) GetCurrentCustomization(CustomizationId id)
|
private (int, CustomizeData) GetCurrentCustomization(CustomizeIndex index)
|
||||||
{
|
{
|
||||||
var current = _set.DataByValue(id, _customize[id], out var custom);
|
var current = _set.DataByValue(index, _customize[index], out var custom, _customize.Face);
|
||||||
if (!_set.IsAvailable(id) || current >= 0)
|
if (!_set.IsAvailable(index) || current >= 0)
|
||||||
return (current, custom!.Value);
|
return (current, custom!.Value);
|
||||||
|
|
||||||
Glamourer.Log.Warning($"Read invalid customization value {_customize[id]} for {id}.");
|
Glamourer.Log.Warning($"Read invalid customization value {_customize[index]} for {index}.");
|
||||||
return (0, _set.Data(id, 0));
|
return (0, _set.Data(index, 0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using Glamourer.Util;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
namespace Glamourer.Gui.Customization;
|
namespace Glamourer.Gui.Customization;
|
||||||
|
|
@ -39,7 +40,7 @@ internal partial class CustomizationDrawer
|
||||||
return;
|
return;
|
||||||
|
|
||||||
foreach (var actor in _actors.Where(a => a))
|
foreach (var actor in _actors.Where(a => a))
|
||||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw, false);
|
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawRaceCombo()
|
private void DrawRaceCombo()
|
||||||
|
|
@ -56,7 +57,7 @@ internal partial class CustomizationDrawer
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
foreach (var actor in _actors.Where(a => a && a.DrawObject))
|
foreach (var actor in _actors.Where(a => a && a.DrawObject))
|
||||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw, false);
|
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ using Glamourer.Customization;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
|
using Penumbra.Api.Enums;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
namespace Glamourer.Gui.Customization;
|
namespace Glamourer.Gui.Customization;
|
||||||
|
|
@ -13,18 +14,18 @@ internal partial class CustomizationDrawer
|
||||||
{
|
{
|
||||||
private const string IconSelectorPopup = "Style Picker";
|
private const string IconSelectorPopup = "Style Picker";
|
||||||
|
|
||||||
private void DrawIconSelector(CustomizationId id)
|
private void DrawIconSelector(CustomizeIndex index)
|
||||||
{
|
{
|
||||||
using var _ = SetId(id);
|
using var _ = SetId(index);
|
||||||
using var bigGroup = ImRaii.Group();
|
using var bigGroup = ImRaii.Group();
|
||||||
var label = _currentOption;
|
var label = _currentOption;
|
||||||
|
|
||||||
var current = _set.DataByValue(id, _currentByte, out var custom);
|
var current = _set.DataByValue(index, _currentByte, out var custom, _customize.Face);
|
||||||
if (current < 0)
|
if (current < 0)
|
||||||
{
|
{
|
||||||
label = $"{_currentOption} (Custom #{_customize[id]})";
|
label = $"{_currentOption} (Custom #{_customize[index]})";
|
||||||
current = 0;
|
current = 0;
|
||||||
custom = _set.Data(id, 0);
|
custom = _set.Data(index, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
var icon = Glamourer.Customization.GetIcon(custom!.Value.IconId);
|
var icon = Glamourer.Customization.GetIcon(custom!.Value.IconId);
|
||||||
|
|
@ -35,7 +36,7 @@ internal partial class CustomizationDrawer
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
using (var group = ImRaii.Group())
|
using (var group = ImRaii.Group())
|
||||||
{
|
{
|
||||||
if (_currentId == CustomizationId.Face)
|
if (_currentIndex == CustomizeIndex.Face)
|
||||||
FaceInputInt(current);
|
FaceInputInt(current);
|
||||||
else
|
else
|
||||||
DataInputInt(current);
|
DataInputInt(current);
|
||||||
|
|
@ -45,7 +46,7 @@ internal partial class CustomizationDrawer
|
||||||
DrawIconPickerPopup();
|
DrawIconPickerPopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateFace(CustomizationData data)
|
private void UpdateFace(CustomizeData data)
|
||||||
{
|
{
|
||||||
// Hrothgar Hack
|
// Hrothgar Hack
|
||||||
var value = _set.Race == Race.Hrothgar ? data.Value + 4 : data.Value;
|
var value = _set.Race == Race.Hrothgar ? data.Value + 4 : data.Value;
|
||||||
|
|
@ -54,7 +55,7 @@ internal partial class CustomizationDrawer
|
||||||
|
|
||||||
_customize.Face = value;
|
_customize.Face = value;
|
||||||
foreach (var actor in _actors)
|
foreach (var actor in _actors)
|
||||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw, false);
|
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void FaceInputInt(int currentIndex)
|
private void FaceInputInt(int currentIndex)
|
||||||
|
|
@ -64,7 +65,7 @@ internal partial class CustomizationDrawer
|
||||||
if (ImGui.InputInt("##text", ref currentIndex, 1, 1))
|
if (ImGui.InputInt("##text", ref currentIndex, 1, 1))
|
||||||
{
|
{
|
||||||
currentIndex = Math.Clamp(currentIndex - 1, 0, _currentCount - 1);
|
currentIndex = Math.Clamp(currentIndex - 1, 0, _currentCount - 1);
|
||||||
var data = _set.Data(_currentId, currentIndex, _customize.Face);
|
var data = _set.Data(_currentIndex, currentIndex, _customize.Face);
|
||||||
UpdateFace(data);
|
UpdateFace(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,13 +82,13 @@ internal partial class CustomizationDrawer
|
||||||
.Push(ImGuiStyleVar.FrameRounding, 0);
|
.Push(ImGuiStyleVar.FrameRounding, 0);
|
||||||
for (var i = 0; i < _currentCount; ++i)
|
for (var i = 0; i < _currentCount; ++i)
|
||||||
{
|
{
|
||||||
var custom = _set.Data(_currentId, i, _customize.Face);
|
var custom = _set.Data(_currentIndex, i, _customize.Face);
|
||||||
var icon = Glamourer.Customization.GetIcon(custom.IconId);
|
var icon = Glamourer.Customization.GetIcon(custom.IconId);
|
||||||
using (var _ = ImRaii.Group())
|
using (var _ = ImRaii.Group())
|
||||||
{
|
{
|
||||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
|
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
|
||||||
{
|
{
|
||||||
if (_currentId == CustomizationId.Face)
|
if (_currentIndex == CustomizeIndex.Face)
|
||||||
UpdateFace(custom);
|
UpdateFace(custom);
|
||||||
else
|
else
|
||||||
UpdateValue(custom.Value);
|
UpdateValue(custom.Value);
|
||||||
|
|
|
||||||
|
|
@ -88,19 +88,8 @@ internal partial class CustomizationDrawer
|
||||||
|
|
||||||
Functions.IteratePairwise(d._set.Order[CharaMakeParams.MenuType.ColorPicker], d.DrawColorPicker, ImGui.SameLine);
|
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);
|
Functions.IteratePairwise(d._set.Order[CharaMakeParams.MenuType.Checkmark], d.DrawCheckbox,
|
||||||
var xPos = d._inputIntSize + d._framedIconSize.X + 3 * ImGui.GetStyle().ItemSpacing.X;
|
() => ImGui.SameLine(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)
|
public static void Draw(Customize customize, IReadOnlyCollection<Actor> actors, bool locked = false)
|
||||||
|
|
@ -113,29 +102,29 @@ internal partial class CustomizationDrawer
|
||||||
=> Draw(customize, CharacterEquip.Null, Array.Empty<Actor>(), locked);
|
=> Draw(customize, CharacterEquip.Null, Array.Empty<Actor>(), locked);
|
||||||
|
|
||||||
// Set state for drawing of current customization.
|
// Set state for drawing of current customization.
|
||||||
private CustomizationId _currentId;
|
private CustomizeIndex _currentIndex;
|
||||||
private CustomizationByteValue _currentByte = CustomizationByteValue.Zero;
|
private CustomizeValue _currentByte = CustomizeValue.Zero;
|
||||||
private int _currentCount;
|
private int _currentCount;
|
||||||
private string _currentOption = string.Empty;
|
private string _currentOption = string.Empty;
|
||||||
|
|
||||||
// Prepare a new customization option.
|
// Prepare a new customization option.
|
||||||
private ImRaii.Id SetId(CustomizationId id)
|
private ImRaii.Id SetId(CustomizeIndex index)
|
||||||
{
|
{
|
||||||
_currentId = id;
|
_currentIndex = index;
|
||||||
_currentByte = _customize[id];
|
_currentByte = _customize[index];
|
||||||
_currentCount = _set.Count(id, _customize.Face);
|
_currentCount = _set.Count(index, _customize.Face);
|
||||||
_currentOption = _set.Option(id);
|
_currentOption = _set.Option(index);
|
||||||
return ImRaii.PushId((int)id);
|
return ImRaii.PushId((int)index);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the current id with a value,
|
// Update the current id with a value,
|
||||||
// also update actors if any.
|
// also update actors if any.
|
||||||
private void UpdateValue(CustomizationByteValue value)
|
private void UpdateValue(CustomizeValue value)
|
||||||
{
|
{
|
||||||
if (_customize[_currentId] == value)
|
if (_customize[_currentIndex] == value)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_customize[_currentId] = value;
|
_customize[_currentIndex] = value;
|
||||||
UpdateActors();
|
UpdateActors();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Linq;
|
using System.Numerics;
|
||||||
using System.Numerics;
|
|
||||||
using Glamourer.Customization;
|
using Glamourer.Customization;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
|
|
@ -12,9 +11,7 @@ internal partial class CustomizationDrawer
|
||||||
// Only used for facial features, so fixed ID.
|
// Only used for facial features, so fixed ID.
|
||||||
private void DrawMultiIconSelector()
|
private void DrawMultiIconSelector()
|
||||||
{
|
{
|
||||||
using var _ = SetId(CustomizationId.FacialFeaturesTattoos);
|
|
||||||
using var bigGroup = ImRaii.Group();
|
using var bigGroup = ImRaii.Group();
|
||||||
|
|
||||||
DrawMultiIcons();
|
DrawMultiIcons();
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
using var group = ImRaii.Group();
|
using var group = ImRaii.Group();
|
||||||
|
|
@ -23,28 +20,30 @@ internal partial class CustomizationDrawer
|
||||||
_currentCount = 256;
|
_currentCount = 256;
|
||||||
PercentageInputInt();
|
PercentageInputInt();
|
||||||
|
|
||||||
ImGui.TextUnformatted(_set.Option(CustomizationId.FacialFeaturesTattoos));
|
ImGui.TextUnformatted(_set.Option(CustomizeIndex.LegacyTattoo));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawMultiIcons()
|
private void DrawMultiIcons()
|
||||||
{
|
{
|
||||||
using var _ = ImRaii.Group();
|
var options = _set.Order[CharaMakeParams.MenuType.IconCheckmark];
|
||||||
for (var i = 0; i < _currentCount; ++i)
|
using var _ = ImRaii.Group();
|
||||||
|
foreach (var (featureIdx, idx) in options.WithIndex())
|
||||||
{
|
{
|
||||||
var enabled = _customize.FacialFeatures[i];
|
using var id = SetId(featureIdx);
|
||||||
var feature = _set.FacialFeature(_customize.Face, i);
|
var enabled = _customize.Get(featureIdx) != CustomizeValue.Zero;
|
||||||
var icon = i == _currentCount - 1
|
var feature = _set.Data(featureIdx, 0, _customize.Face);
|
||||||
|
var icon = featureIdx == CustomizeIndex.LegacyTattoo
|
||||||
? LegacyTattoo ?? Glamourer.Customization.GetIcon(feature.IconId)
|
? LegacyTattoo ?? Glamourer.Customization.GetIcon(feature.IconId)
|
||||||
: 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,
|
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X,
|
||||||
Vector4.Zero, enabled ? Vector4.One : RedTint))
|
Vector4.Zero, enabled ? Vector4.One : RedTint))
|
||||||
{
|
{
|
||||||
_customize.FacialFeatures.Set(i, !enabled);
|
_customize.Set(featureIdx, enabled ? CustomizeValue.Zero : CustomizeValue.Max);
|
||||||
UpdateActors();
|
UpdateActors();
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||||
if (i % 4 != 3)
|
if (idx % 4 != 3)
|
||||||
ImGui.SameLine();
|
ImGui.SameLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Security.AccessControl;
|
||||||
using Glamourer.Customization;
|
using Glamourer.Customization;
|
||||||
using ImGuiNET;
|
using ImGuiNET;
|
||||||
using Newtonsoft.Json.Linq;
|
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
|
|
||||||
|
|
@ -10,9 +10,9 @@ namespace Glamourer.Gui.Customization;
|
||||||
|
|
||||||
internal partial class CustomizationDrawer
|
internal partial class CustomizationDrawer
|
||||||
{
|
{
|
||||||
private void DrawListSelector(CustomizationId id)
|
private void DrawListSelector(CustomizeIndex index)
|
||||||
{
|
{
|
||||||
using var _ = SetId(id);
|
using var _ = SetId(index);
|
||||||
using var bigGroup = ImRaii.Group();
|
using var bigGroup = ImRaii.Group();
|
||||||
|
|
||||||
ListCombo();
|
ListCombo();
|
||||||
|
|
@ -33,22 +33,22 @@ internal partial class CustomizationDrawer
|
||||||
for (var i = 0; i < _currentCount; ++i)
|
for (var i = 0; i < _currentCount; ++i)
|
||||||
{
|
{
|
||||||
if (ImGui.Selectable($"{_currentOption} #{i + 1}##combo", i == _currentByte.Value))
|
if (ImGui.Selectable($"{_currentOption} #{i + 1}##combo", i == _currentByte.Value))
|
||||||
UpdateValue((CustomizationByteValue)i);
|
UpdateValue((CustomizeValue)i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ListInputInt()
|
private void ListInputInt()
|
||||||
{
|
{
|
||||||
var tmp = _currentByte.Value + 1;
|
var tmp = _currentByte.Value + 1;
|
||||||
ImGui.SetNextItemWidth(_inputIntSize);
|
ImGui.SetNextItemWidth(_inputIntSize);
|
||||||
if (ImGui.InputInt("##text", ref tmp, 1, 1) && tmp > 0 && tmp <= _currentCount)
|
if (ImGui.InputInt("##text", ref tmp, 1, 1) && tmp > 0 && tmp <= _currentCount)
|
||||||
UpdateValue((CustomizationByteValue)Math.Clamp(tmp - 1, 0, _currentCount - 1));
|
UpdateValue((CustomizeValue)Math.Clamp(tmp - 1, 0, _currentCount - 1));
|
||||||
ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]");
|
ImGuiUtil.HoverTooltip($"Input Range: [1, {_currentCount}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PercentageSelector(CustomizationId id)
|
private void PercentageSelector(CustomizeIndex index)
|
||||||
{
|
{
|
||||||
using var _ = SetId(id);
|
using var _ = SetId(index);
|
||||||
using var bigGroup = ImRaii.Group();
|
using var bigGroup = ImRaii.Group();
|
||||||
|
|
||||||
DrawPercentageSlider();
|
DrawPercentageSlider();
|
||||||
|
|
@ -63,7 +63,7 @@ internal partial class CustomizationDrawer
|
||||||
var tmp = (int)_currentByte.Value;
|
var tmp = (int)_currentByte.Value;
|
||||||
ImGui.SetNextItemWidth(_comboSelectorSize);
|
ImGui.SetNextItemWidth(_comboSelectorSize);
|
||||||
if (ImGui.SliderInt("##slider", ref tmp, 0, _currentCount - 1, "%i", ImGuiSliderFlags.AlwaysClamp))
|
if (ImGui.SliderInt("##slider", ref tmp, 0, _currentCount - 1, "%i", ImGuiSliderFlags.AlwaysClamp))
|
||||||
UpdateValue((CustomizationByteValue)tmp);
|
UpdateValue((CustomizeValue)tmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PercentageInputInt()
|
private void PercentageInputInt()
|
||||||
|
|
@ -71,7 +71,7 @@ internal partial class CustomizationDrawer
|
||||||
var tmp = (int)_currentByte.Value;
|
var tmp = (int)_currentByte.Value;
|
||||||
ImGui.SetNextItemWidth(_inputIntSize);
|
ImGui.SetNextItemWidth(_inputIntSize);
|
||||||
if (ImGui.InputInt("##text", ref tmp, 1, 1))
|
if (ImGui.InputInt("##text", ref tmp, 1, 1))
|
||||||
UpdateValue((CustomizationByteValue)Math.Clamp(tmp, 0, _currentCount - 1));
|
UpdateValue((CustomizeValue)Math.Clamp(tmp, 0, _currentCount - 1));
|
||||||
ImGuiUtil.HoverTooltip($"Input Range: [0, {_currentCount - 1}]");
|
ImGuiUtil.HoverTooltip($"Input Range: [0, {_currentCount - 1}]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -87,6 +87,14 @@ internal partial class CustomizationDrawer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Draw a customize checkbox.
|
||||||
|
private void DrawCheckbox(CustomizeIndex idx)
|
||||||
|
{
|
||||||
|
using var id = SetId(idx);
|
||||||
|
Checkbox(_set.Option(idx), _customize.Get(idx) != CustomizeValue.Zero,
|
||||||
|
b => _customize.Set(idx, b ? CustomizeValue.Max : CustomizeValue.Zero));
|
||||||
|
}
|
||||||
|
|
||||||
// Integral input for an icon- or color based item.
|
// Integral input for an icon- or color based item.
|
||||||
private void DataInputInt(int currentIndex)
|
private void DataInputInt(int currentIndex)
|
||||||
{
|
{
|
||||||
|
|
@ -95,7 +103,7 @@ internal partial class CustomizationDrawer
|
||||||
if (ImGui.InputInt("##text", ref currentIndex, 1, 1))
|
if (ImGui.InputInt("##text", ref currentIndex, 1, 1))
|
||||||
{
|
{
|
||||||
currentIndex = Math.Clamp(currentIndex - 1, 0, _currentCount - 1);
|
currentIndex = Math.Clamp(currentIndex - 1, 0, _currentCount - 1);
|
||||||
var data = _set.Data(_currentId, currentIndex, _customize.Face);
|
var data = _set.Data(_currentIndex, currentIndex, _customize.Face);
|
||||||
UpdateValue(data.Value);
|
UpdateValue(data.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using Lumina.Excel.GeneratedSheets;
|
||||||
using Lumina.Text;
|
using Lumina.Text;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
|
using OtterGui.Raii;
|
||||||
using OtterGui.Widgets;
|
using OtterGui.Widgets;
|
||||||
using Penumbra.GameData;
|
using Penumbra.GameData;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
@ -141,6 +142,12 @@ public partial class EquipmentDrawer
|
||||||
return Draw(Label, _lastPreview, ref newIdx, ItemComboWidth * ImGuiHelpers.GlobalScale, ImGui.GetTextLineHeight());
|
return Draw(Label, _lastPreview, ref newIdx, ItemComboWidth * ImGuiHelpers.GlobalScale, ImGui.GetTextLineHeight());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//protected override bool DrawSelectable(int globalIdx, bool selected)
|
||||||
|
//{
|
||||||
|
// using var _ = ImRaii.Group();
|
||||||
|
//
|
||||||
|
//}
|
||||||
|
|
||||||
protected override bool IsVisible(int globalIndex, LowerString filter)
|
protected override bool IsVisible(int globalIndex, LowerString filter)
|
||||||
{
|
{
|
||||||
var item = Items[globalIndex];
|
var item = Items[globalIndex];
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using Dalamud.Interface;
|
using Dalamud.Interface;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
|
|
@ -10,6 +12,8 @@ using ImGuiNET;
|
||||||
using OtterGui;
|
using OtterGui;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
using OtterGui.Raii;
|
using OtterGui.Raii;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using ImGui = ImGuiNET.ImGui;
|
||||||
|
|
||||||
namespace Glamourer.Gui;
|
namespace Glamourer.Gui;
|
||||||
|
|
||||||
|
|
@ -59,6 +63,7 @@ internal partial class Interface
|
||||||
if (_currentData.Valid)
|
if (_currentData.Valid)
|
||||||
_currentSave.Update(_currentData.Objects[0]);
|
_currentSave.Update(_currentData.Objects[0]);
|
||||||
|
|
||||||
|
RevertButton();
|
||||||
CustomizationDrawer.Draw(_currentSave.Data.Customize, _currentSave.Data.Equipment, _currentData.Objects,
|
CustomizationDrawer.Draw(_currentSave.Data.Customize, _currentSave.Data.Equipment, _currentData.Objects,
|
||||||
_identifier is Actor.SpecialIdentifier);
|
_identifier is Actor.SpecialIdentifier);
|
||||||
|
|
||||||
|
|
@ -68,6 +73,52 @@ internal partial class Interface
|
||||||
private const uint RedHeaderColor = 0xFF1818C0;
|
private const uint RedHeaderColor = 0xFF1818C0;
|
||||||
private const uint GreenHeaderColor = 0xFF18C018;
|
private const uint GreenHeaderColor = 0xFF18C018;
|
||||||
|
|
||||||
|
private void RevertButton()
|
||||||
|
{
|
||||||
|
if (ImGui.Button("Revert"))
|
||||||
|
{
|
||||||
|
_manipulations.DeleteSave(_identifier);
|
||||||
|
|
||||||
|
foreach (var actor in _currentData.Objects)
|
||||||
|
_currentSave!.ApplyToActor(actor);
|
||||||
|
|
||||||
|
if (_currentData.Objects.Count > 0)
|
||||||
|
_currentSave = _manipulations.GetOrCreateSave(_currentData.Objects[0]);
|
||||||
|
|
||||||
|
_currentSave!.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
VisorBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
private unsafe void VisorBox()
|
||||||
|
{
|
||||||
|
var (flags, mask) = (_currentSave!.Data.Flags & (ApplicationFlags.SetVisor | ApplicationFlags.Visor)) switch
|
||||||
|
{
|
||||||
|
ApplicationFlags.SetVisor => (0u, 3u),
|
||||||
|
ApplicationFlags.Visor => (1u, 3u),
|
||||||
|
ApplicationFlags.SetVisor | ApplicationFlags.Visor => (3u, 3u),
|
||||||
|
_ => (2u, 3u),
|
||||||
|
};
|
||||||
|
var tmp = flags;
|
||||||
|
if (ImGui.CheckboxFlags("Visor Toggled", ref tmp, mask))
|
||||||
|
{
|
||||||
|
_currentSave.Data.Flags = flags switch
|
||||||
|
{
|
||||||
|
0 => (_currentSave.Data.Flags | ApplicationFlags.Visor) & ~ApplicationFlags.SetVisor,
|
||||||
|
1 => _currentSave.Data.Flags | ApplicationFlags.SetVisor,
|
||||||
|
2 => _currentSave.Data.Flags | ApplicationFlags.SetVisor,
|
||||||
|
_ => _currentSave.Data.Flags & ~(ApplicationFlags.SetVisor | ApplicationFlags.Visor),
|
||||||
|
};
|
||||||
|
if (_currentSave.Data.Flags.HasFlag(ApplicationFlags.SetVisor))
|
||||||
|
{
|
||||||
|
var on = _currentSave.Data.Flags.HasFlag(ApplicationFlags.Visor);
|
||||||
|
foreach (var actor in _currentData.Objects.Where(a => a.IsHuman && a.DrawObject))
|
||||||
|
RedrawManager.SetVisor(actor.DrawObject.Pointer, on);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawPanelHeader()
|
private void DrawPanelHeader()
|
||||||
{
|
{
|
||||||
var color = _currentData.Valid ? GreenHeaderColor : RedHeaderColor;
|
var color = _currentData.Valid ? GreenHeaderColor : RedHeaderColor;
|
||||||
|
|
|
||||||
51
Glamourer/Gui/Interface.DebugDataTab.cs
Normal file
51
Glamourer/Gui/Interface.DebugDataTab.cs
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
using System;
|
||||||
|
using Glamourer.Customization;
|
||||||
|
using Glamourer.Util;
|
||||||
|
using ImGuiNET;
|
||||||
|
using OtterGui;
|
||||||
|
using OtterGui.Raii;
|
||||||
|
|
||||||
|
namespace Glamourer.Gui;
|
||||||
|
|
||||||
|
internal partial class Interface
|
||||||
|
{
|
||||||
|
private class DebugDataTab
|
||||||
|
{
|
||||||
|
private readonly ICustomizationManager _mg;
|
||||||
|
|
||||||
|
public DebugDataTab(ICustomizationManager manager)
|
||||||
|
=> _mg = manager;
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
using var tab = ImRaii.TabItem("Debug");
|
||||||
|
if (!tab)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var clan in _mg.Clans)
|
||||||
|
{
|
||||||
|
foreach (var gender in _mg.Genders)
|
||||||
|
DrawCustomizationInfo(_mg.GetList(clan, gender));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawCustomizationInfo(CustomizationSet set)
|
||||||
|
{
|
||||||
|
if (!ImGui.CollapsingHeader($"{CustomizeExtensions.ClanName(set.Clan, set.Gender)} {set.Gender}"))
|
||||||
|
return;
|
||||||
|
|
||||||
|
using var table = ImRaii.Table("data", 5);
|
||||||
|
if (!table)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var index in Enum.GetValues<CustomizeIndex>())
|
||||||
|
{
|
||||||
|
ImGuiUtil.DrawTableColumn(index.ToString());
|
||||||
|
ImGuiUtil.DrawTableColumn(set.Option(index));
|
||||||
|
ImGuiUtil.DrawTableColumn(set.IsAvailable(index) ? "Available" : "Unavailable");
|
||||||
|
ImGuiUtil.DrawTableColumn(set.Type(index).ToString());
|
||||||
|
ImGuiUtil.DrawTableColumn(set.Count(index).ToString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,7 @@ internal partial class Interface : Window, IDisposable
|
||||||
|
|
||||||
private readonly ActorTab _actorTab;
|
private readonly ActorTab _actorTab;
|
||||||
private readonly DebugStateTab _debugStateTab;
|
private readonly DebugStateTab _debugStateTab;
|
||||||
|
private readonly DebugDataTab _debugDataTab;
|
||||||
|
|
||||||
public Interface(Glamourer plugin)
|
public Interface(Glamourer plugin)
|
||||||
: base(GetLabel())
|
: base(GetLabel())
|
||||||
|
|
@ -29,6 +30,7 @@ internal partial class Interface : Window, IDisposable
|
||||||
};
|
};
|
||||||
_actorTab = new ActorTab(_plugin.CurrentManipulations);
|
_actorTab = new ActorTab(_plugin.CurrentManipulations);
|
||||||
_debugStateTab = new DebugStateTab(_plugin.CurrentManipulations);
|
_debugStateTab = new DebugStateTab(_plugin.CurrentManipulations);
|
||||||
|
_debugDataTab = new DebugDataTab(Glamourer.Customization);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Draw()
|
public override void Draw()
|
||||||
|
|
@ -44,6 +46,7 @@ internal partial class Interface : Window, IDisposable
|
||||||
_actorTab.Draw();
|
_actorTab.Draw();
|
||||||
DrawSettingsTab();
|
DrawSettingsTab();
|
||||||
_debugStateTab.Draw();
|
_debugStateTab.Draw();
|
||||||
|
_debugDataTab.Draw();
|
||||||
// DrawSaves();
|
// DrawSaves();
|
||||||
// DrawFixedDesignsTab();
|
// DrawFixedDesignsTab();
|
||||||
// DrawRevertablesTab();
|
// DrawRevertablesTab();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
using Dalamud.Utility;
|
using Dalamud.Utility;
|
||||||
|
using Lumina.Excel.GeneratedSheets;
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.GameData.ByteString;
|
||||||
|
|
@ -187,13 +188,26 @@ public unsafe partial struct Actor
|
||||||
public bool IsValid
|
public bool IsValid
|
||||||
=> true;
|
=> true;
|
||||||
|
|
||||||
public OwnedIdentifier(Utf8String name, Utf8String ownerName, ushort ownerHomeWorld, uint dataId, ObjectKind kind)
|
public OwnedIdentifier(Utf8String actorName, Utf8String ownerName, ushort ownerHomeWorld, uint dataId, ObjectKind kind)
|
||||||
{
|
{
|
||||||
Name = name;
|
|
||||||
OwnerName = ownerName;
|
OwnerName = ownerName;
|
||||||
OwnerHomeWorld = ownerHomeWorld;
|
OwnerHomeWorld = ownerHomeWorld;
|
||||||
DataId = dataId;
|
DataId = dataId;
|
||||||
Kind = kind;
|
Kind = kind;
|
||||||
|
Name = actorName;
|
||||||
|
switch (Kind)
|
||||||
|
{
|
||||||
|
case ObjectKind.MountType:
|
||||||
|
var mount = Dalamud.GameData.GetExcelSheet<Mount>()!.GetRow(dataId);
|
||||||
|
if (mount != null)
|
||||||
|
Name = Utf8String.FromSpanUnsafe(mount.Singular.RawData, false).AsciiToMixed();
|
||||||
|
break;
|
||||||
|
case ObjectKind.Companion:
|
||||||
|
var companion = Dalamud.GameData.GetExcelSheet<Companion>()!.GetRow(dataId);
|
||||||
|
if (companion != null)
|
||||||
|
Name = Utf8String.FromSpanUnsafe(companion.Singular.RawData, false).AsciiToMixed();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Equals(IIdentifier? other)
|
public bool Equals(IIdentifier? other)
|
||||||
|
|
@ -341,8 +355,19 @@ public unsafe partial struct Actor
|
||||||
if (!owner)
|
if (!owner)
|
||||||
return new InvalidIdentifier();
|
return new InvalidIdentifier();
|
||||||
|
|
||||||
return new OwnedIdentifier(actor.Utf8Name, owner.Utf8Name, owner.Pointer->HomeWorld,
|
var dataId = actor.ObjectKind switch
|
||||||
actor.Pointer->GameObject.DataID, actor.ObjectKind);
|
{
|
||||||
|
ObjectKind.MountType => owner.UsedMountId,
|
||||||
|
ObjectKind.Companion => actor.CompanionId,
|
||||||
|
_ => actor.Pointer->GameObject.DataID,
|
||||||
|
};
|
||||||
|
|
||||||
|
var name = actor.Utf8Name;
|
||||||
|
if (name.IsEmpty && dataId == 0)
|
||||||
|
return new InvalidIdentifier();
|
||||||
|
|
||||||
|
return new OwnedIdentifier(name, owner.Utf8Name, owner.Pointer->HomeWorld,
|
||||||
|
dataId, actor.ObjectKind);
|
||||||
}
|
}
|
||||||
default: return new InvalidIdentifier();
|
default: return new InvalidIdentifier();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,115 +1,13 @@
|
||||||
using System;
|
using System;
|
||||||
using Dalamud.Game.ClientState.Objects.Enums;
|
using Dalamud.Game.ClientState.Objects.Enums;
|
||||||
using Dalamud.Game.ClientState.Objects.Types;
|
using Dalamud.Game.ClientState.Objects.Types;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
|
||||||
using Glamourer.Customization;
|
using Glamourer.Customization;
|
||||||
using Penumbra.GameData.ByteString;
|
using Penumbra.GameData.ByteString;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||||
|
|
||||||
namespace Glamourer.Interop;
|
namespace Glamourer.Interop;
|
||||||
|
|
||||||
public interface IDesignable
|
|
||||||
{
|
|
||||||
public bool Valid { get; }
|
|
||||||
public uint ModelId { get; }
|
|
||||||
public Customize Customize { get; }
|
|
||||||
public CharacterEquip Equip { get; }
|
|
||||||
public CharacterWeapon MainHand { get; }
|
|
||||||
public CharacterWeapon OffHand { get; }
|
|
||||||
public bool VisorEnabled { get; }
|
|
||||||
public bool WeaponEnabled { get; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe partial struct DrawObject : IEquatable<DrawObject>, IDesignable
|
|
||||||
{
|
|
||||||
public Human* Pointer;
|
|
||||||
|
|
||||||
public IntPtr Address
|
|
||||||
=> (IntPtr)Pointer;
|
|
||||||
|
|
||||||
public static implicit operator DrawObject(IntPtr? pointer)
|
|
||||||
=> new() { Pointer = (Human*)(pointer ?? IntPtr.Zero) };
|
|
||||||
|
|
||||||
public static implicit operator IntPtr(DrawObject drawObject)
|
|
||||||
=> drawObject.Pointer == null ? IntPtr.Zero : (IntPtr)drawObject.Pointer;
|
|
||||||
|
|
||||||
public bool Valid
|
|
||||||
=> Pointer != null;
|
|
||||||
|
|
||||||
public uint ModelId
|
|
||||||
=> 0;
|
|
||||||
|
|
||||||
public uint Type
|
|
||||||
=> (*(delegate* unmanaged<Human*, uint>**)Pointer)[50](Pointer);
|
|
||||||
|
|
||||||
public Customize Customize
|
|
||||||
=> new((CustomizeData*)Pointer->CustomizeData);
|
|
||||||
|
|
||||||
public CharacterEquip Equip
|
|
||||||
=> new((CharacterArmor*)Pointer->EquipSlotData);
|
|
||||||
|
|
||||||
public CharacterWeapon MainHand
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var child = (byte*)Pointer->CharacterBase.DrawObject.Object.ChildObject;
|
|
||||||
if (child == null)
|
|
||||||
return CharacterWeapon.Empty;
|
|
||||||
|
|
||||||
return *(CharacterWeapon*)(child + 0x8F0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe CharacterWeapon OffHand
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
var child = Pointer->CharacterBase.DrawObject.Object.ChildObject;
|
|
||||||
if (child == null)
|
|
||||||
return CharacterWeapon.Empty;
|
|
||||||
|
|
||||||
var sibling = (byte*) child->NextSiblingObject;
|
|
||||||
if (sibling == null)
|
|
||||||
return CharacterWeapon.Empty;
|
|
||||||
|
|
||||||
return *(CharacterWeapon*)(child + 0x8F0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe bool VisorEnabled
|
|
||||||
=> (*(byte*)(Address + 0x90) & 0x40) != 0;
|
|
||||||
|
|
||||||
public unsafe bool WeaponEnabled
|
|
||||||
=> false;
|
|
||||||
|
|
||||||
public static implicit operator bool(DrawObject actor)
|
|
||||||
=> actor.Pointer != null;
|
|
||||||
|
|
||||||
public static bool operator true(DrawObject actor)
|
|
||||||
=> actor.Pointer != null;
|
|
||||||
|
|
||||||
public static bool operator false(DrawObject actor)
|
|
||||||
=> actor.Pointer == null;
|
|
||||||
|
|
||||||
public static bool operator !(DrawObject actor)
|
|
||||||
=> actor.Pointer == null;
|
|
||||||
|
|
||||||
public bool Equals(DrawObject other)
|
|
||||||
=> Pointer == other.Pointer;
|
|
||||||
|
|
||||||
public override bool Equals(object? obj)
|
|
||||||
=> obj is DrawObject other && Equals(other);
|
|
||||||
|
|
||||||
public override int GetHashCode()
|
|
||||||
=> unchecked((int)(long)Pointer);
|
|
||||||
|
|
||||||
public static bool operator ==(DrawObject lhs, DrawObject rhs)
|
|
||||||
=> lhs.Pointer == rhs.Pointer;
|
|
||||||
|
|
||||||
public static bool operator !=(DrawObject lhs, DrawObject rhs)
|
|
||||||
=> lhs.Pointer != rhs.Pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
|
public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
|
||||||
{
|
{
|
||||||
public static readonly Actor Null = new() { Pointer = null };
|
public static readonly Actor Null = new() { Pointer = null };
|
||||||
|
|
@ -173,6 +71,12 @@ public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
|
||||||
set => Pointer->ModelCharaId = (int)value;
|
set => Pointer->ModelCharaId = (int)value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ushort UsedMountId
|
||||||
|
=> !IsHuman ? (ushort)0 : *(ushort*)((byte*)Pointer + 0x668);
|
||||||
|
|
||||||
|
public ushort CompanionId
|
||||||
|
=> ObjectKind == ObjectKind.Companion ? *(ushort*)((byte*)Pointer + 0x1AAC) : (ushort)0;
|
||||||
|
|
||||||
public Customize Customize
|
public Customize Customize
|
||||||
=> new((CustomizeData*)Pointer->CustomizeData);
|
=> new((CustomizeData*)Pointer->CustomizeData);
|
||||||
|
|
||||||
|
|
|
||||||
97
Glamourer/Interop/DrawObject.cs
Normal file
97
Glamourer/Interop/DrawObject.cs
Normal file
|
|
@ -0,0 +1,97 @@
|
||||||
|
using System;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
|
using Glamourer.Customization;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||||
|
|
||||||
|
namespace Glamourer.Interop;
|
||||||
|
|
||||||
|
public unsafe partial struct DrawObject : IEquatable<DrawObject>, IDesignable
|
||||||
|
{
|
||||||
|
public Human* Pointer;
|
||||||
|
|
||||||
|
public IntPtr Address
|
||||||
|
=> (IntPtr)Pointer;
|
||||||
|
|
||||||
|
public static implicit operator DrawObject(IntPtr? pointer)
|
||||||
|
=> new() { Pointer = (Human*)(pointer ?? IntPtr.Zero) };
|
||||||
|
|
||||||
|
public static implicit operator IntPtr(DrawObject drawObject)
|
||||||
|
=> drawObject.Pointer == null ? IntPtr.Zero : (IntPtr)drawObject.Pointer;
|
||||||
|
|
||||||
|
public bool Valid
|
||||||
|
=> Pointer != null;
|
||||||
|
|
||||||
|
public uint ModelId
|
||||||
|
=> 0;
|
||||||
|
|
||||||
|
public uint Type
|
||||||
|
=> (*(delegate* unmanaged<Human*, uint>**)Pointer)[50](Pointer);
|
||||||
|
|
||||||
|
public Customize Customize
|
||||||
|
=> new((CustomizeData*)Pointer->CustomizeData);
|
||||||
|
|
||||||
|
public CharacterEquip Equip
|
||||||
|
=> new((CharacterArmor*)Pointer->EquipSlotData);
|
||||||
|
|
||||||
|
public CharacterWeapon MainHand
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var child = (byte*)Pointer->CharacterBase.DrawObject.Object.ChildObject;
|
||||||
|
if (child == null)
|
||||||
|
return CharacterWeapon.Empty;
|
||||||
|
|
||||||
|
return *(CharacterWeapon*)(child + 0x8F0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe CharacterWeapon OffHand
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var child = Pointer->CharacterBase.DrawObject.Object.ChildObject;
|
||||||
|
if (child == null)
|
||||||
|
return CharacterWeapon.Empty;
|
||||||
|
|
||||||
|
var sibling = (byte*)child->NextSiblingObject;
|
||||||
|
if (sibling == null)
|
||||||
|
return CharacterWeapon.Empty;
|
||||||
|
|
||||||
|
return *(CharacterWeapon*)(sibling + 0x8F0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe bool VisorEnabled
|
||||||
|
=> (*(byte*)(Address + 0x90) & 0x40) != 0;
|
||||||
|
|
||||||
|
public unsafe bool WeaponEnabled
|
||||||
|
=> false;
|
||||||
|
|
||||||
|
public static implicit operator bool(DrawObject actor)
|
||||||
|
=> actor.Pointer != null;
|
||||||
|
|
||||||
|
public static bool operator true(DrawObject actor)
|
||||||
|
=> actor.Pointer != null;
|
||||||
|
|
||||||
|
public static bool operator false(DrawObject actor)
|
||||||
|
=> actor.Pointer == null;
|
||||||
|
|
||||||
|
public static bool operator !(DrawObject actor)
|
||||||
|
=> actor.Pointer == null;
|
||||||
|
|
||||||
|
public bool Equals(DrawObject other)
|
||||||
|
=> Pointer == other.Pointer;
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
=> obj is DrawObject other && Equals(other);
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
=> unchecked((int)(long)Pointer);
|
||||||
|
|
||||||
|
public static bool operator ==(DrawObject lhs, DrawObject rhs)
|
||||||
|
=> lhs.Pointer == rhs.Pointer;
|
||||||
|
|
||||||
|
public static bool operator !=(DrawObject lhs, DrawObject rhs)
|
||||||
|
=> lhs.Pointer != rhs.Pointer;
|
||||||
|
}
|
||||||
16
Glamourer/Interop/IDesignable.cs
Normal file
16
Glamourer/Interop/IDesignable.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
using Glamourer.Customization;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.Interop;
|
||||||
|
|
||||||
|
public interface IDesignable
|
||||||
|
{
|
||||||
|
public bool Valid { get; }
|
||||||
|
public uint ModelId { get; }
|
||||||
|
public Customize Customize { get; }
|
||||||
|
public CharacterEquip Equip { get; }
|
||||||
|
public CharacterWeapon MainHand { get; }
|
||||||
|
public CharacterWeapon OffHand { get; }
|
||||||
|
public bool VisorEnabled { get; }
|
||||||
|
public bool WeaponEnabled { get; }
|
||||||
|
}
|
||||||
52
Glamourer/Interop/RedrawManager.Customize.cs
Normal file
52
Glamourer/Interop/RedrawManager.Customize.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
using Dalamud.Utility.Signatures;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
|
using Glamourer.Customization;
|
||||||
|
using Penumbra.Api.Enums;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
|
namespace Glamourer.Interop;
|
||||||
|
|
||||||
|
public unsafe partial class RedrawManager
|
||||||
|
{
|
||||||
|
// Update
|
||||||
|
public delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment);
|
||||||
|
|
||||||
|
[Signature("E8 ?? ?? ?? ?? 41 0F B6 C5 66 41 89 86")]
|
||||||
|
private readonly ChangeCustomizeDelegate _changeCustomize = null!;
|
||||||
|
|
||||||
|
public bool UpdateCustomize(Actor actor, Customize customize)
|
||||||
|
{
|
||||||
|
if (!actor.Valid || !actor.DrawObject.Valid)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
var d = actor.DrawObject;
|
||||||
|
if (NeedsRedraw(d.Customize, customize))
|
||||||
|
{
|
||||||
|
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _changeCustomize(d.Pointer, (byte*)customize.Data, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool NeedsRedraw(Customize lhs, Customize rhs)
|
||||||
|
=> lhs.Race != rhs.Race
|
||||||
|
|| lhs.Gender != rhs.Gender
|
||||||
|
|| lhs.BodyType != rhs.BodyType
|
||||||
|
|| lhs.Face != rhs.Face
|
||||||
|
|| lhs.Race == Race.Hyur && lhs.Clan != rhs.Clan;
|
||||||
|
|
||||||
|
|
||||||
|
public static void SetVisor(Human* data, bool on)
|
||||||
|
{
|
||||||
|
if (data == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var flags = &data->CharacterBase.UnkFlags_01;
|
||||||
|
var state = (*flags & 0x40) != 0;
|
||||||
|
if (state == on)
|
||||||
|
return;
|
||||||
|
|
||||||
|
*flags = (byte)((on ? *flags | 0x40 : *flags & 0xBF) | 0x80);
|
||||||
|
}
|
||||||
|
}
|
||||||
68
Glamourer/Interop/RedrawManager.Equipment.cs
Normal file
68
Glamourer/Interop/RedrawManager.Equipment.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
using System;
|
||||||
|
using Dalamud.Hooking;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using Dalamud.Utility.Signatures;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.Interop;
|
||||||
|
|
||||||
|
public unsafe partial class RedrawManager
|
||||||
|
{
|
||||||
|
public delegate ulong FlagSlotForUpdateDelegate(Human* drawObject, uint slot, CharacterArmor* data);
|
||||||
|
|
||||||
|
// This gets called when one of the ten equip items of an existing draw object gets changed.
|
||||||
|
[Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A", DetourName = nameof(FlagSlotForUpdateDetour))]
|
||||||
|
private readonly Hook<FlagSlotForUpdateDelegate> _flagSlotForUpdateHook = null!;
|
||||||
|
|
||||||
|
private ulong FlagSlotForUpdateDetour(Human* drawObject, uint slotIdx, CharacterArmor* data)
|
||||||
|
{
|
||||||
|
var slot = slotIdx.ToEquipSlot();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var actor = Glamourer.Penumbra.GameObjectFromDrawObject((IntPtr)drawObject);
|
||||||
|
var identifier = actor.GetIdentifier();
|
||||||
|
|
||||||
|
if (_fixedDesigns.TryGetDesign(identifier, out var save))
|
||||||
|
{
|
||||||
|
PluginLog.Information($"Loaded {slot} from fixed design for {identifier}.");
|
||||||
|
(var replaced, *data) =
|
||||||
|
Glamourer.RestrictedGear.ResolveRestricted(save.Equipment[slot], slot, (Race)drawObject->Race, (Gender)drawObject->Sex);
|
||||||
|
}
|
||||||
|
else if (_currentManipulations.TryGetDesign(identifier, out var save2))
|
||||||
|
{
|
||||||
|
PluginLog.Information($"Updated {slot} from current designs for {identifier}.");
|
||||||
|
(var replaced, *data) =
|
||||||
|
Glamourer.RestrictedGear.ResolveRestricted(*data, slot, (Race)drawObject->Race, (Gender)(drawObject->Sex + 1));
|
||||||
|
save2.Data.Equipment[slot] = *data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
PluginLog.Error($"Error on loading new gear:\n{e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ChangeEquip(DrawObject drawObject, uint slotIdx, CharacterArmor data)
|
||||||
|
{
|
||||||
|
if (!drawObject)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (slotIdx > 9)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return FlagSlotForUpdateDetour(drawObject.Pointer, slotIdx, &data) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data)
|
||||||
|
=> actor && ChangeEquip(actor.DrawObject, slot.ToIndex(), data);
|
||||||
|
|
||||||
|
public bool ChangeEquip(DrawObject drawObject, EquipSlot slot, CharacterArmor data)
|
||||||
|
=> ChangeEquip(drawObject, slot.ToIndex(), data);
|
||||||
|
|
||||||
|
public bool ChangeEquip(Actor actor, uint slotIdx, CharacterArmor data)
|
||||||
|
=> actor && ChangeEquip(actor.DrawObject, slotIdx, data);
|
||||||
|
}
|
||||||
102
Glamourer/Interop/RedrawManager.Weapons.cs
Normal file
102
Glamourer/Interop/RedrawManager.Weapons.cs
Normal file
|
|
@ -0,0 +1,102 @@
|
||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using Dalamud.Hooking;
|
||||||
|
using Dalamud.Logging;
|
||||||
|
using Dalamud.Utility.Signatures;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.Interop;
|
||||||
|
|
||||||
|
public unsafe partial class RedrawManager
|
||||||
|
{
|
||||||
|
public static readonly int CharacterWeaponOffset = (int)Marshal.OffsetOf<Character>("DrawData");
|
||||||
|
|
||||||
|
public delegate void LoadWeaponDelegate(IntPtr offsetCharacter, uint slot, ulong weapon, byte redrawOnEquality, byte unk2,
|
||||||
|
byte skipGameObject,
|
||||||
|
byte unk4);
|
||||||
|
|
||||||
|
// Weapons for a specific character are reloaded with this function.
|
||||||
|
// The first argument is a pointer to the game object but shifted a bit inside.
|
||||||
|
// slot is 0 for main hand, 1 for offhand, 2 for unknown (always called with empty data.
|
||||||
|
// weapon argument is the new weapon data.
|
||||||
|
// redrawOnEquality controls whether the game does anything if the new weapon is identical to the old one.
|
||||||
|
// skipGameObject seems to control whether the new weapons are written to the game object or just influence the draw object. (1 = skip, 0 = change)
|
||||||
|
// unk4 seemed to be the same as unk1.
|
||||||
|
[Signature("E8 ?? ?? ?? ?? 44 8B 9F", DetourName = nameof(LoadWeaponDetour))]
|
||||||
|
private readonly Hook<LoadWeaponDelegate> _loadWeaponHook = null!;
|
||||||
|
|
||||||
|
private void LoadWeaponDetour(IntPtr characterOffset, uint slot, ulong weapon, byte redrawOnEquality, byte unk2, byte skipGameObject,
|
||||||
|
byte unk4)
|
||||||
|
{
|
||||||
|
var oldWeapon = weapon;
|
||||||
|
var character = (Actor)(characterOffset - CharacterWeaponOffset);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var identifier = character.GetIdentifier();
|
||||||
|
if (_fixedDesigns.TryGetDesign(identifier, out var save))
|
||||||
|
{
|
||||||
|
PluginLog.Information($"Loaded weapon from fixed design for {identifier}.");
|
||||||
|
weapon = slot switch
|
||||||
|
{
|
||||||
|
0 => save.MainHand.Value,
|
||||||
|
1 => save.OffHand.Value,
|
||||||
|
_ => weapon,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (redrawOnEquality == 1 && _currentManipulations.TryGetDesign(identifier, out var save2))
|
||||||
|
{
|
||||||
|
PluginLog.Information($"Loaded weapon from current design for {identifier}.");
|
||||||
|
switch (slot)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
save2.Data.MainHand = new CharacterWeapon(weapon);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
save2.Data.OffHand = new CharacterWeapon(weapon);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
PluginLog.Error($"Error on loading new weapon:\n{e}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// First call the regular function.
|
||||||
|
_loadWeaponHook.Original(characterOffset, slot, oldWeapon, redrawOnEquality, unk2, skipGameObject, unk4);
|
||||||
|
// If something changed the weapon, call it again with the actual change, not forcing redraws and skipping applying it to the game object.
|
||||||
|
if (oldWeapon != weapon)
|
||||||
|
_loadWeaponHook.Original(characterOffset, slot, weapon, 0 /* redraw */, unk2, 1 /* skip */, unk4);
|
||||||
|
// If we're not actively changing the offhand and the game object has no offhand, redraw an empty offhand to fix animation problems.
|
||||||
|
else if (slot != 1 && character.OffHand.Value == 0)
|
||||||
|
_loadWeaponHook.Original(characterOffset, 1, 0, 1 /* redraw */, unk2, 1 /* skip */, unk4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load a specific weapon for a character by its data and slot.
|
||||||
|
public void LoadWeapon(Actor character, EquipSlot slot, CharacterWeapon weapon)
|
||||||
|
{
|
||||||
|
switch (slot)
|
||||||
|
{
|
||||||
|
case EquipSlot.MainHand:
|
||||||
|
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0);
|
||||||
|
return;
|
||||||
|
case EquipSlot.OffHand:
|
||||||
|
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, weapon.Value, 0, 0, 1, 0);
|
||||||
|
return;
|
||||||
|
case EquipSlot.BothHand:
|
||||||
|
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0);
|
||||||
|
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, CharacterWeapon.Empty.Value, 0, 0, 1, 0);
|
||||||
|
return;
|
||||||
|
// function can also be called with '2', but does not seem to ever be.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load specific Main- and Offhand weapons.
|
||||||
|
public void LoadWeapon(Actor character, CharacterWeapon main, CharacterWeapon off)
|
||||||
|
{
|
||||||
|
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, main.Value, 1, 0, 1, 0);
|
||||||
|
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, off.Value, 1, 0, 1, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Reflection.Metadata;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Logging;
|
using Dalamud.Logging;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
using Glamourer.Customization;
|
using Glamourer.Customization;
|
||||||
using Glamourer.State;
|
using Glamourer.State;
|
||||||
|
|
@ -12,7 +9,6 @@ using Glamourer.Structs;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||||
using Race = Penumbra.GameData.Enums.Race;
|
|
||||||
|
|
||||||
namespace Glamourer.Interop;
|
namespace Glamourer.Interop;
|
||||||
|
|
||||||
|
|
@ -32,157 +28,6 @@ public unsafe partial class RedrawManager
|
||||||
public event Action<Actor, Job>? JobChanged;
|
public event Action<Actor, Job>? JobChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe partial class RedrawManager
|
|
||||||
{
|
|
||||||
public delegate ulong FlagSlotForUpdateDelegate(Human* drawObject, uint slot, CharacterArmor* data);
|
|
||||||
|
|
||||||
// This gets called when one of the ten equip items of an existing draw object gets changed.
|
|
||||||
[Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A", DetourName = nameof(FlagSlotForUpdateDetour))]
|
|
||||||
private readonly Hook<FlagSlotForUpdateDelegate> _flagSlotForUpdateHook = null!;
|
|
||||||
|
|
||||||
private ulong FlagSlotForUpdateDetour(Human* drawObject, uint slotIdx, CharacterArmor* data)
|
|
||||||
{
|
|
||||||
var slot = slotIdx.ToEquipSlot();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var actor = Glamourer.Penumbra.GameObjectFromDrawObject((IntPtr)drawObject);
|
|
||||||
var identifier = actor.GetIdentifier();
|
|
||||||
|
|
||||||
if (_fixedDesigns.TryGetDesign(identifier, out var save))
|
|
||||||
{
|
|
||||||
PluginLog.Information($"Loaded {slot} from fixed design for {identifier}.");
|
|
||||||
(var replaced, *data) =
|
|
||||||
Glamourer.RestrictedGear.ResolveRestricted(save.Equipment[slot], slot, (Race)drawObject->Race, (Gender)drawObject->Sex);
|
|
||||||
}
|
|
||||||
else if (_currentManipulations.TryGetDesign(identifier, out var save2))
|
|
||||||
{
|
|
||||||
PluginLog.Information($"Updated {slot} from current designs for {identifier}.");
|
|
||||||
(var replaced, *data) =
|
|
||||||
Glamourer.RestrictedGear.ResolveRestricted(*data, slot, (Race)drawObject->Race, (Gender)(drawObject->Sex + 1));
|
|
||||||
save2.Data.Equipment[slot] = *data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
PluginLog.Error($"Error on loading new gear:\n{e}");
|
|
||||||
}
|
|
||||||
|
|
||||||
return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ChangeEquip(DrawObject drawObject, uint slotIdx, CharacterArmor data)
|
|
||||||
{
|
|
||||||
if (!drawObject)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (slotIdx > 9)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return FlagSlotForUpdateDetour(drawObject.Pointer, slotIdx, &data) != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data)
|
|
||||||
=> actor && ChangeEquip(actor.DrawObject, slot.ToIndex(), data);
|
|
||||||
|
|
||||||
public bool ChangeEquip(DrawObject drawObject, EquipSlot slot, CharacterArmor data)
|
|
||||||
=> ChangeEquip(drawObject, slot.ToIndex(), data);
|
|
||||||
|
|
||||||
public bool ChangeEquip(Actor actor, uint slotIdx, CharacterArmor data)
|
|
||||||
=> actor && ChangeEquip(actor.DrawObject, slotIdx, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe partial class RedrawManager
|
|
||||||
{
|
|
||||||
public static readonly int CharacterWeaponOffset = (int) Marshal.OffsetOf<Character>("DrawData");
|
|
||||||
|
|
||||||
public delegate void LoadWeaponDelegate(IntPtr offsetCharacter, uint slot, ulong weapon, byte redrawOnEquality, byte unk2,
|
|
||||||
byte skipGameObject,
|
|
||||||
byte unk4);
|
|
||||||
|
|
||||||
// Weapons for a specific character are reloaded with this function.
|
|
||||||
// The first argument is a pointer to the game object but shifted a bit inside.
|
|
||||||
// slot is 0 for main hand, 1 for offhand, 2 for unknown (always called with empty data.
|
|
||||||
// weapon argument is the new weapon data.
|
|
||||||
// redrawOnEquality controls whether the game does anything if the new weapon is identical to the old one.
|
|
||||||
// skipGameObject seems to control whether the new weapons are written to the game object or just influence the draw object. (1 = skip, 0 = change)
|
|
||||||
// unk4 seemed to be the same as unk1.
|
|
||||||
[Signature("E8 ?? ?? ?? ?? 44 8B 9F", DetourName = nameof(LoadWeaponDetour))]
|
|
||||||
private readonly Hook<LoadWeaponDelegate> _loadWeaponHook = null!;
|
|
||||||
|
|
||||||
private void LoadWeaponDetour(IntPtr characterOffset, uint slot, ulong weapon, byte redrawOnEquality, byte unk2, byte skipGameObject,
|
|
||||||
byte unk4)
|
|
||||||
{
|
|
||||||
var oldWeapon = weapon;
|
|
||||||
var character = (Actor)(characterOffset - CharacterWeaponOffset);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
var identifier = character.GetIdentifier();
|
|
||||||
if (_fixedDesigns.TryGetDesign(identifier, out var save))
|
|
||||||
{
|
|
||||||
PluginLog.Information($"Loaded weapon from fixed design for {identifier}.");
|
|
||||||
weapon = slot switch
|
|
||||||
{
|
|
||||||
0 => save.MainHand.Value,
|
|
||||||
1 => save.OffHand.Value,
|
|
||||||
_ => weapon,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else if (redrawOnEquality == 1 && _currentManipulations.TryGetDesign(identifier, out var save2))
|
|
||||||
{
|
|
||||||
PluginLog.Information($"Loaded weapon from current design for {identifier}.");
|
|
||||||
switch (slot)
|
|
||||||
{
|
|
||||||
//case 0:
|
|
||||||
// save2.Data.MainHand = new CharacterWeapon(weapon);
|
|
||||||
// break;
|
|
||||||
//case 1:
|
|
||||||
// save.OffHand = new CharacterWeapon(weapon);
|
|
||||||
// break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
PluginLog.Error($"Error on loading new weapon:\n{e}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// First call the regular function.
|
|
||||||
_loadWeaponHook.Original(characterOffset, slot, oldWeapon, redrawOnEquality, unk2, skipGameObject, unk4);
|
|
||||||
// If something changed the weapon, call it again with the actual change, not forcing redraws and skipping applying it to the game object.
|
|
||||||
if (oldWeapon != weapon)
|
|
||||||
_loadWeaponHook.Original(characterOffset, slot, weapon, 0 /* redraw */, unk2, 1 /* skip */, unk4);
|
|
||||||
// If we're not actively changing the offhand and the game object has no offhand, redraw an empty offhand to fix animation problems.
|
|
||||||
else if (slot != 1 && character.OffHand.Value == 0)
|
|
||||||
_loadWeaponHook.Original(characterOffset, 1, 0, 1 /* redraw */, unk2, 1 /* skip */, unk4);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load a specific weapon for a character by its data and slot.
|
|
||||||
public void LoadWeapon(Actor character, EquipSlot slot, CharacterWeapon weapon)
|
|
||||||
{
|
|
||||||
switch (slot)
|
|
||||||
{
|
|
||||||
case EquipSlot.MainHand:
|
|
||||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0);
|
|
||||||
return;
|
|
||||||
case EquipSlot.OffHand:
|
|
||||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, weapon.Value, 0, 0, 1, 0);
|
|
||||||
return;
|
|
||||||
case EquipSlot.BothHand:
|
|
||||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0);
|
|
||||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, CharacterWeapon.Empty.Value, 0, 0, 1, 0);
|
|
||||||
return;
|
|
||||||
// function can also be called with '2', but does not seem to ever be.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Load specific Main- and Offhand weapons.
|
|
||||||
public void LoadWeapon(Actor character, CharacterWeapon main, CharacterWeapon off)
|
|
||||||
{
|
|
||||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, main.Value, 0, 0, 1, 0);
|
|
||||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, off.Value, 0, 0, 1, 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe partial class RedrawManager : IDisposable
|
public unsafe partial class RedrawManager : IDisposable
|
||||||
{
|
{
|
||||||
private readonly FixedDesigns _fixedDesigns;
|
private readonly FixedDesigns _fixedDesigns;
|
||||||
|
|
@ -191,8 +36,8 @@ public unsafe partial class RedrawManager : IDisposable
|
||||||
public RedrawManager(FixedDesigns fixedDesigns, CurrentManipulations currentManipulations)
|
public RedrawManager(FixedDesigns fixedDesigns, CurrentManipulations currentManipulations)
|
||||||
{
|
{
|
||||||
SignatureHelper.Initialise(this);
|
SignatureHelper.Initialise(this);
|
||||||
Glamourer.Penumbra.CreatingCharacterBase += OnCharacterRedraw;
|
Glamourer.Penumbra.CreatingCharacterBase.Event += OnCharacterRedraw;
|
||||||
Glamourer.Penumbra.CreatedCharacterBase += OnCharacterRedrawFinished;
|
Glamourer.Penumbra.CreatedCharacterBase.Event += OnCharacterRedrawFinished;
|
||||||
_fixedDesigns = fixedDesigns;
|
_fixedDesigns = fixedDesigns;
|
||||||
_currentManipulations = currentManipulations;
|
_currentManipulations = currentManipulations;
|
||||||
_flagSlotForUpdateHook.Enable();
|
_flagSlotForUpdateHook.Enable();
|
||||||
|
|
@ -205,8 +50,8 @@ public unsafe partial class RedrawManager : IDisposable
|
||||||
_flagSlotForUpdateHook.Dispose();
|
_flagSlotForUpdateHook.Dispose();
|
||||||
_loadWeaponHook.Dispose();
|
_loadWeaponHook.Dispose();
|
||||||
_changeJobHook.Dispose();
|
_changeJobHook.Dispose();
|
||||||
Glamourer.Penumbra.CreatingCharacterBase -= OnCharacterRedraw;
|
Glamourer.Penumbra.CreatingCharacterBase.Event -= OnCharacterRedraw;
|
||||||
Glamourer.Penumbra.CreatedCharacterBase -= OnCharacterRedrawFinished;
|
Glamourer.Penumbra.CreatedCharacterBase.Event -= OnCharacterRedrawFinished;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCharacterRedraw(Actor actor, uint* modelId, Customize customize, CharacterEquip equip)
|
private void OnCharacterRedraw(Actor actor, uint* modelId, Customize customize, CharacterEquip equip)
|
||||||
|
|
@ -225,23 +70,23 @@ public unsafe partial class RedrawManager : IDisposable
|
||||||
// Apply customization if they correspond and there is customization to apply.
|
// Apply customization if they correspond and there is customization to apply.
|
||||||
var gameObjectCustomize = new Customize((CustomizeData*)actor.Pointer->CustomizeData);
|
var gameObjectCustomize = new Customize((CustomizeData*)actor.Pointer->CustomizeData);
|
||||||
if (gameObjectCustomize.Equals(customize))
|
if (gameObjectCustomize.Equals(customize))
|
||||||
customize.Load(save.Data.Customize);
|
customize.Load(save!.Data.Customize);
|
||||||
|
|
||||||
// Compare game object equip data against draw object equip data for transformations.
|
// Compare game object equip data against draw object equip data for transformations.
|
||||||
// Apply each piece of equip that should be applied if they correspond.
|
// Apply each piece of equip that should be applied if they correspond.
|
||||||
var gameObjectEquip = new CharacterEquip((CharacterArmor*)actor.Pointer->EquipSlotData);
|
var gameObjectEquip = new CharacterEquip((CharacterArmor*)actor.Pointer->EquipSlotData);
|
||||||
if (gameObjectEquip.Equals(equip))
|
if (gameObjectEquip.Equals(equip))
|
||||||
{
|
{
|
||||||
var saveEquip = save.Data.Equipment;
|
var saveEquip = save!.Data.Equipment;
|
||||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
{
|
{
|
||||||
(var _, equip[slot]) =
|
(_, equip[slot]) =
|
||||||
Glamourer.RestrictedGear.ResolveRestricted(true ? equip[slot] : saveEquip[slot], slot, customize.Race, customize.Gender);
|
Glamourer.RestrictedGear.ResolveRestricted(saveEquip[slot], slot, customize.Race, customize.Gender);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCharacterRedraw(IntPtr gameObject, IntPtr modelId, IntPtr customize, IntPtr equipData)
|
private void OnCharacterRedraw(IntPtr gameObject, string collection, IntPtr modelId, IntPtr customize, IntPtr equipData)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -254,7 +99,7 @@ public unsafe partial class RedrawManager : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void OnCharacterRedrawFinished(IntPtr gameObject, IntPtr drawObject)
|
private static void OnCharacterRedrawFinished(IntPtr gameObject, string collection, IntPtr drawObject)
|
||||||
{
|
{
|
||||||
//SetVisor((Human*)drawObject, true);
|
//SetVisor((Human*)drawObject, true);
|
||||||
if (Glamourer.Models.FromCharacterBase((CharacterBase*)drawObject, out var data))
|
if (Glamourer.Models.FromCharacterBase((CharacterBase*)drawObject, out var data))
|
||||||
|
|
@ -262,42 +107,4 @@ public unsafe partial class RedrawManager : IDisposable
|
||||||
else
|
else
|
||||||
PluginLog.Information($"Key: {Glamourer.Models.KeyFromCharacterBase((CharacterBase*)drawObject):X16}");
|
PluginLog.Information($"Key: {Glamourer.Models.KeyFromCharacterBase((CharacterBase*)drawObject):X16}");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Update
|
|
||||||
public delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment);
|
|
||||||
|
|
||||||
[Signature("E8 ?? ?? ?? ?? 41 0F B6 C5 66 41 89 86")]
|
|
||||||
private readonly ChangeCustomizeDelegate _changeCustomize = null!;
|
|
||||||
|
|
||||||
public bool UpdateCustomize(Actor actor, Customize customize)
|
|
||||||
{
|
|
||||||
if (!actor.Valid || !actor.DrawObject.Valid)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
var d = actor.DrawObject;
|
|
||||||
if (NeedsRedraw(d.Customize, customize))
|
|
||||||
{
|
|
||||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw, true);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _changeCustomize(d.Pointer, (byte*)customize.Data, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool NeedsRedraw(Customize lhs, Customize rhs)
|
|
||||||
=> lhs.Race != rhs.Race || lhs.Gender != rhs.Gender || lhs.Face != rhs.Face || lhs.Race == Race.Hyur && lhs.Clan != rhs.Clan;
|
|
||||||
|
|
||||||
|
|
||||||
public static void SetVisor(Human* data, bool on)
|
|
||||||
{
|
|
||||||
if (data == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
var flags = &data->CharacterBase.UnkFlags_01;
|
|
||||||
var state = (*flags & 0x40) != 0;
|
|
||||||
if (state == on)
|
|
||||||
return;
|
|
||||||
|
|
||||||
*flags = (byte)((on ? *flags | 0x40 : *flags & 0xBF) | 0x80);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
212
Glamourer/Saves/Design.cs
Normal file
212
Glamourer/Saves/Design.cs
Normal file
|
|
@ -0,0 +1,212 @@
|
||||||
|
using System;
|
||||||
|
using Glamourer.Customization;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||||
|
|
||||||
|
namespace Glamourer.Saves;
|
||||||
|
|
||||||
|
public class EquipmentDesign
|
||||||
|
{
|
||||||
|
private Data _data = default;
|
||||||
|
|
||||||
|
// @formatter:off
|
||||||
|
//public Slot Head => new(ref _data, 0);
|
||||||
|
//public Slot Body => new(ref _data, 1);
|
||||||
|
//public Slot Hands => new(ref _data, 2);
|
||||||
|
//public Slot Legs => new(ref _data, 3);
|
||||||
|
//public Slot Feet => new(ref _data, 4);
|
||||||
|
//public Slot Ears => new(ref _data, 5);
|
||||||
|
//public Slot Neck => new(ref _data, 6);
|
||||||
|
//public Slot Wrist => new(ref _data, 7);
|
||||||
|
//public Slot RFinger => new(ref _data, 8);
|
||||||
|
//public Slot LFinger => new(ref _data, 9);
|
||||||
|
//public Slot MainHand => new(ref _data, 10);
|
||||||
|
//public Slot OffHand => new(ref _data, 11);
|
||||||
|
//// @formatter:on
|
||||||
|
//
|
||||||
|
//public Slot this[EquipSlot slot]
|
||||||
|
// => new(ref _data, (int)slot.ToIndex());
|
||||||
|
//
|
||||||
|
//public Slot this[int idx]
|
||||||
|
// => idx is >= 0 and < Data.NumEquipment ? new Slot(ref _data, idx) : throw new IndexOutOfRangeException();
|
||||||
|
|
||||||
|
public unsafe struct Data
|
||||||
|
{
|
||||||
|
public const int NumEquipment = 12;
|
||||||
|
|
||||||
|
public fixed uint Ids[NumEquipment];
|
||||||
|
public fixed byte Stains[NumEquipment];
|
||||||
|
public ushort Flags;
|
||||||
|
public ushort StainFlags;
|
||||||
|
}
|
||||||
|
|
||||||
|
//public ref struct Slot
|
||||||
|
//{
|
||||||
|
// private readonly ref Data _data;
|
||||||
|
// private readonly int _index;
|
||||||
|
// private readonly ushort _flag;
|
||||||
|
//
|
||||||
|
// public Slot(ref Data data, int idx)
|
||||||
|
// {
|
||||||
|
// _data = data;
|
||||||
|
// _index = idx;
|
||||||
|
// _flag = (ushort)(1 << idx);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public unsafe uint ItemId
|
||||||
|
// {
|
||||||
|
// get => _data.Ids[_index];
|
||||||
|
// set => _data.Ids[_index] = value;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public unsafe StainId StainId
|
||||||
|
// {
|
||||||
|
// get => _data.Stains[_index];
|
||||||
|
// set => _data.Stains[_index] = value.Value;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public bool ApplyItem
|
||||||
|
// {
|
||||||
|
// get => (_data.Flags & _flag) != 0;
|
||||||
|
// set => _data.Flags = (ushort)(value ? _data.Flags | _flag : _data.Flags & ~_flag);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public bool ApplyStain
|
||||||
|
// {
|
||||||
|
// get => (_data.StainFlags & _flag) != 0;
|
||||||
|
// set => _data.StainFlags = (ushort)(value ? _data.StainFlags | _flag : _data.StainFlags & ~_flag);
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HumanDesign
|
||||||
|
{
|
||||||
|
public unsafe struct Data
|
||||||
|
{
|
||||||
|
public CustomizeData Values;
|
||||||
|
public CustomizeFlag Flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
//public ref struct Choice<T> where T : unmanaged
|
||||||
|
//{
|
||||||
|
// private readonly ref Data _data;
|
||||||
|
// private readonly CustomizeFlag _flag;
|
||||||
|
//
|
||||||
|
// public Choice(ref Data data, CustomizeFlag flag)
|
||||||
|
// {
|
||||||
|
// _data = data;
|
||||||
|
// _flag = flag;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// public bool ApplyChoice
|
||||||
|
// {
|
||||||
|
// get => _data.Flag.HasFlag(_flag);
|
||||||
|
// set => _data.Flag = value ? _data.Flag | _flag : _data.Flag & ~_flag;
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Design
|
||||||
|
{
|
||||||
|
public string Name { get; private set; }
|
||||||
|
public string Description { get; private set; }
|
||||||
|
|
||||||
|
public DateTimeOffset CreationDate { get; }
|
||||||
|
public DateTimeOffset LastUpdateDate { get; private set; }
|
||||||
|
|
||||||
|
public bool ReadOnly { get; private set; }
|
||||||
|
|
||||||
|
public EquipmentDesign? Equipment;
|
||||||
|
public HumanDesign? Customize;
|
||||||
|
|
||||||
|
public string WriteJson()
|
||||||
|
{
|
||||||
|
return string.Empty;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct DesignSaveV1
|
||||||
|
{
|
||||||
|
public string Name;
|
||||||
|
public string Description;
|
||||||
|
|
||||||
|
public ulong CreationDate;
|
||||||
|
public ulong LastUpdateDate;
|
||||||
|
|
||||||
|
public EquipmentPiece Head;
|
||||||
|
public EquipmentPiece Body;
|
||||||
|
public EquipmentPiece Hands;
|
||||||
|
public EquipmentPiece Legs;
|
||||||
|
public EquipmentPiece Feet;
|
||||||
|
public EquipmentPiece Ears;
|
||||||
|
public EquipmentPiece Neck;
|
||||||
|
public EquipmentPiece Wrists;
|
||||||
|
public EquipmentPiece LFinger;
|
||||||
|
public EquipmentPiece RFinger;
|
||||||
|
|
||||||
|
public EquipmentPiece MainHand;
|
||||||
|
public EquipmentPiece OffHand;
|
||||||
|
|
||||||
|
public CustomizationChoice<uint> ModelId;
|
||||||
|
public CustomizationChoice<Race> Race;
|
||||||
|
public CustomizationChoice<Gender> Gender;
|
||||||
|
public CustomizationChoice BodyType;
|
||||||
|
public CustomizationChoice Height;
|
||||||
|
public CustomizationChoice<SubRace> Clan;
|
||||||
|
public CustomizationChoice Face;
|
||||||
|
public CustomizationChoice Hairstyle;
|
||||||
|
public CustomizationChoice<bool> Highlights;
|
||||||
|
public CustomizationChoice SkinColor;
|
||||||
|
public CustomizationChoice EyeColorRight;
|
||||||
|
public CustomizationChoice HairColor;
|
||||||
|
public CustomizationChoice HighlightsColor;
|
||||||
|
public CustomizationChoice<bool> FacialFeature1;
|
||||||
|
public CustomizationChoice<bool> FacialFeature2;
|
||||||
|
public CustomizationChoice<bool> FacialFeature3;
|
||||||
|
public CustomizationChoice<bool> FacialFeature4;
|
||||||
|
public CustomizationChoice<bool> FacialFeature5;
|
||||||
|
public CustomizationChoice<bool> FacialFeature6;
|
||||||
|
public CustomizationChoice<bool> FacialFeature7;
|
||||||
|
public CustomizationChoice<bool> LegacyTattoo;
|
||||||
|
public CustomizationChoice TattooColor;
|
||||||
|
public CustomizationChoice Eyebrows;
|
||||||
|
public CustomizationChoice EyeColorLeft;
|
||||||
|
public CustomizationChoice EyeShape;
|
||||||
|
public CustomizationChoice<bool> SmallIris;
|
||||||
|
public CustomizationChoice Nose;
|
||||||
|
public CustomizationChoice Jaw;
|
||||||
|
public CustomizationChoice Mouth;
|
||||||
|
public CustomizationChoice<bool> Lipstick;
|
||||||
|
public CustomizationChoice MuscleMass;
|
||||||
|
public CustomizationChoice TailShape;
|
||||||
|
public CustomizationChoice BustSize;
|
||||||
|
public CustomizationChoice FacePaint;
|
||||||
|
public CustomizationChoice<bool> FacePaintReversed;
|
||||||
|
public CustomizationChoice FacePaintColor;
|
||||||
|
|
||||||
|
public bool ReadOnly;
|
||||||
|
|
||||||
|
public override string ToString()
|
||||||
|
=> Name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct EquipmentPiece
|
||||||
|
{
|
||||||
|
public uint Item;
|
||||||
|
public bool ApplyItem;
|
||||||
|
public StainId Stain;
|
||||||
|
public bool ApplyStain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct CustomizationChoice
|
||||||
|
{
|
||||||
|
public CustomizeValue Value;
|
||||||
|
public bool Apply;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct CustomizationChoice<T> where T : struct
|
||||||
|
{
|
||||||
|
public T Value;
|
||||||
|
public bool Apply;
|
||||||
|
}
|
||||||
75
Glamourer/State/ApplicationFlags.cs
Normal file
75
Glamourer/State/ApplicationFlags.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Glamourer.State;
|
||||||
|
|
||||||
|
[Flags]
|
||||||
|
public enum ApplicationFlags : uint
|
||||||
|
{
|
||||||
|
Customizations = 0x000001,
|
||||||
|
MainHand = 0x000002,
|
||||||
|
OffHand = 0x000004,
|
||||||
|
Head = 0x000008,
|
||||||
|
Body = 0x000010,
|
||||||
|
Hands = 0x000020,
|
||||||
|
Legs = 0x000040,
|
||||||
|
Feet = 0x000080,
|
||||||
|
Ears = 0x000100,
|
||||||
|
Neck = 0x000200,
|
||||||
|
Wrist = 0x000400,
|
||||||
|
RFinger = 0x000800,
|
||||||
|
LFinger = 0x001000,
|
||||||
|
SetVisor = 0x002000,
|
||||||
|
Visor = 0x004000,
|
||||||
|
SetWeapon = 0x008000,
|
||||||
|
Weapon = 0x010000,
|
||||||
|
SetWet = 0x020000,
|
||||||
|
Wet = 0x040000,
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ApplicationFlagExtensions
|
||||||
|
{
|
||||||
|
public static ApplicationFlags ToApplicationFlag(this EquipSlot slot)
|
||||||
|
=> slot switch
|
||||||
|
{
|
||||||
|
EquipSlot.MainHand => ApplicationFlags.MainHand,
|
||||||
|
EquipSlot.OffHand => ApplicationFlags.OffHand,
|
||||||
|
EquipSlot.Head => ApplicationFlags.Head,
|
||||||
|
EquipSlot.Body => ApplicationFlags.Body,
|
||||||
|
EquipSlot.Hands => ApplicationFlags.Hands,
|
||||||
|
EquipSlot.Legs => ApplicationFlags.Legs,
|
||||||
|
EquipSlot.Feet => ApplicationFlags.Feet,
|
||||||
|
EquipSlot.Ears => ApplicationFlags.Ears,
|
||||||
|
EquipSlot.Neck => ApplicationFlags.Neck,
|
||||||
|
EquipSlot.Wrists => ApplicationFlags.Wrist,
|
||||||
|
EquipSlot.RFinger => ApplicationFlags.RFinger,
|
||||||
|
EquipSlot.BothHand => ApplicationFlags.MainHand | ApplicationFlags.OffHand,
|
||||||
|
EquipSlot.LFinger => ApplicationFlags.LFinger,
|
||||||
|
EquipSlot.HeadBody => ApplicationFlags.Body,
|
||||||
|
EquipSlot.BodyHandsLegsFeet => ApplicationFlags.Body,
|
||||||
|
EquipSlot.LegsFeet => ApplicationFlags.Legs,
|
||||||
|
EquipSlot.FullBody => ApplicationFlags.Body,
|
||||||
|
EquipSlot.BodyHands => ApplicationFlags.Body,
|
||||||
|
EquipSlot.BodyLegsFeet => ApplicationFlags.Body,
|
||||||
|
EquipSlot.ChestHands => ApplicationFlags.Body,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
public static EquipSlot ToSlot(this ApplicationFlags flags)
|
||||||
|
=> flags switch
|
||||||
|
{
|
||||||
|
ApplicationFlags.MainHand => EquipSlot.MainHand,
|
||||||
|
ApplicationFlags.OffHand => EquipSlot.OffHand,
|
||||||
|
ApplicationFlags.Head => EquipSlot.Head,
|
||||||
|
ApplicationFlags.Body => EquipSlot.Body,
|
||||||
|
ApplicationFlags.Hands => EquipSlot.Hands,
|
||||||
|
ApplicationFlags.Legs => EquipSlot.Legs,
|
||||||
|
ApplicationFlags.Feet => EquipSlot.Feet,
|
||||||
|
ApplicationFlags.Ears => EquipSlot.Ears,
|
||||||
|
ApplicationFlags.Neck => EquipSlot.Neck,
|
||||||
|
ApplicationFlags.Wrist => EquipSlot.Wrists,
|
||||||
|
ApplicationFlags.RFinger => EquipSlot.RFinger,
|
||||||
|
ApplicationFlags.LFinger => EquipSlot.LFinger,
|
||||||
|
_ => EquipSlot.Unknown,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -1,17 +1,12 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Security.AccessControl;
|
|
||||||
using Dalamud.Utility;
|
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
|
||||||
using Glamourer.Customization;
|
using Glamourer.Customization;
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
using Glamourer.Structs;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using DrawObject = Glamourer.Interop.DrawObject;
|
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||||
using Functions = Penumbra.GameData.Util.Functions;
|
using Functions = Penumbra.GameData.Util.Functions;
|
||||||
|
|
||||||
namespace Glamourer.State;
|
namespace Glamourer.State;
|
||||||
|
|
@ -33,76 +28,7 @@ public class CharacterSaveConverter : JsonConverter<CharacterSave>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum ApplicationFlags : uint
|
|
||||||
{
|
|
||||||
Customizations = 0x000001,
|
|
||||||
MainHand = 0x000002,
|
|
||||||
OffHand = 0x000004,
|
|
||||||
Head = 0x000008,
|
|
||||||
Body = 0x000010,
|
|
||||||
Hands = 0x000020,
|
|
||||||
Legs = 0x000040,
|
|
||||||
Feet = 0x000080,
|
|
||||||
Ears = 0x000100,
|
|
||||||
Neck = 0x000200,
|
|
||||||
Wrist = 0x000400,
|
|
||||||
RFinger = 0x000800,
|
|
||||||
LFinger = 0x001000,
|
|
||||||
SetVisor = 0x002000,
|
|
||||||
Visor = 0x004000,
|
|
||||||
SetWeapon = 0x008000,
|
|
||||||
Weapon = 0x010000,
|
|
||||||
SetWet = 0x020000,
|
|
||||||
Wet = 0x040000,
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class ApplicationFlagExtensions
|
|
||||||
{
|
|
||||||
public static ApplicationFlags ToApplicationFlag(this EquipSlot slot)
|
|
||||||
=> slot switch
|
|
||||||
{
|
|
||||||
EquipSlot.MainHand => ApplicationFlags.MainHand,
|
|
||||||
EquipSlot.OffHand => ApplicationFlags.OffHand,
|
|
||||||
EquipSlot.Head => ApplicationFlags.Head,
|
|
||||||
EquipSlot.Body => ApplicationFlags.Body,
|
|
||||||
EquipSlot.Hands => ApplicationFlags.Hands,
|
|
||||||
EquipSlot.Legs => ApplicationFlags.Legs,
|
|
||||||
EquipSlot.Feet => ApplicationFlags.Feet,
|
|
||||||
EquipSlot.Ears => ApplicationFlags.Ears,
|
|
||||||
EquipSlot.Neck => ApplicationFlags.Neck,
|
|
||||||
EquipSlot.Wrists => ApplicationFlags.Wrist,
|
|
||||||
EquipSlot.RFinger => ApplicationFlags.RFinger,
|
|
||||||
EquipSlot.BothHand => ApplicationFlags.MainHand | ApplicationFlags.OffHand,
|
|
||||||
EquipSlot.LFinger => ApplicationFlags.LFinger,
|
|
||||||
EquipSlot.HeadBody => ApplicationFlags.Body,
|
|
||||||
EquipSlot.BodyHandsLegsFeet => ApplicationFlags.Body,
|
|
||||||
EquipSlot.LegsFeet => ApplicationFlags.Legs,
|
|
||||||
EquipSlot.FullBody => ApplicationFlags.Body,
|
|
||||||
EquipSlot.BodyHands => ApplicationFlags.Body,
|
|
||||||
EquipSlot.BodyLegsFeet => ApplicationFlags.Body,
|
|
||||||
EquipSlot.ChestHands => ApplicationFlags.Body,
|
|
||||||
_ => 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
public static EquipSlot ToSlot(this ApplicationFlags flags)
|
|
||||||
=> flags switch
|
|
||||||
{
|
|
||||||
ApplicationFlags.MainHand => EquipSlot.MainHand,
|
|
||||||
ApplicationFlags.OffHand => EquipSlot.OffHand,
|
|
||||||
ApplicationFlags.Head => EquipSlot.Head,
|
|
||||||
ApplicationFlags.Body => EquipSlot.Body,
|
|
||||||
ApplicationFlags.Hands => EquipSlot.Hands,
|
|
||||||
ApplicationFlags.Legs => EquipSlot.Legs,
|
|
||||||
ApplicationFlags.Feet => EquipSlot.Feet,
|
|
||||||
ApplicationFlags.Ears => EquipSlot.Ears,
|
|
||||||
ApplicationFlags.Neck => EquipSlot.Neck,
|
|
||||||
ApplicationFlags.Wrist => EquipSlot.Wrists,
|
|
||||||
ApplicationFlags.RFinger => EquipSlot.RFinger,
|
|
||||||
ApplicationFlags.LFinger => EquipSlot.LFinger,
|
|
||||||
_ => EquipSlot.Unknown,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||||
public struct CharacterData
|
public struct CharacterData
|
||||||
|
|
@ -206,19 +132,6 @@ public struct CharacterData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface ICharacterData
|
|
||||||
{
|
|
||||||
public ref CharacterData Data { get; }
|
|
||||||
|
|
||||||
//public bool ApplyModel();
|
|
||||||
//public bool ApplyCustomize(Customize target);
|
|
||||||
//public bool ApplyWeapon(ref CharacterWeapon weapon, bool mainHand, bool offHand);
|
|
||||||
//public bool ApplyGear(ref CharacterArmor armor, EquipSlot slot);
|
|
||||||
//public unsafe bool ApplyWetness(CharacterBase* drawObject);
|
|
||||||
//public unsafe bool ApplyVisorState(CharacterBase* drawObject);
|
|
||||||
//public unsafe bool ApplyWeaponState(CharacterBase* drawObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
[JsonConverter(typeof(CharacterSaveConverter))]
|
[JsonConverter(typeof(CharacterSaveConverter))]
|
||||||
public class CharacterSave
|
public class CharacterSave
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
using System.Collections.Generic;
|
using Glamourer.Interop;
|
||||||
using System.Linq;
|
using Penumbra.Api.Enums;
|
||||||
using Glamourer.Customization;
|
|
||||||
using Glamourer.Interop;
|
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
|
|
||||||
namespace Glamourer.State;
|
namespace Glamourer.State;
|
||||||
|
|
||||||
public unsafe class CurrentDesign : ICharacterData
|
public unsafe class CurrentDesign : IDesign
|
||||||
{
|
{
|
||||||
public ref CharacterData Data
|
public ref CharacterData Data
|
||||||
=> ref _drawData;
|
=> ref _drawData;
|
||||||
|
|
@ -28,6 +26,39 @@ public unsafe class CurrentDesign : ICharacterData
|
||||||
_drawData = _initialData.Clone();
|
_drawData = _initialData.Clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
=> _drawData = _initialData;
|
||||||
|
|
||||||
|
public void ApplyToActor(Actor actor)
|
||||||
|
{
|
||||||
|
if (!actor)
|
||||||
|
return;
|
||||||
|
|
||||||
|
void Redraw()
|
||||||
|
=> Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
|
||||||
|
|
||||||
|
if (_drawData.ModelId != actor.ModelId)
|
||||||
|
{
|
||||||
|
Redraw();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var customize1 = _drawData.Customize;
|
||||||
|
var customize2 = actor.Customize;
|
||||||
|
if (RedrawManager.NeedsRedraw(customize1, customize2))
|
||||||
|
{
|
||||||
|
Redraw();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Glamourer.RedrawManager.UpdateCustomize(actor, customize2);
|
||||||
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
|
Glamourer.RedrawManager.ChangeEquip(actor, slot, actor.Equip[slot]);
|
||||||
|
Glamourer.RedrawManager.LoadWeapon(actor, actor.MainHand, actor.OffHand);
|
||||||
|
if (actor.IsHuman && actor.DrawObject)
|
||||||
|
RedrawManager.SetVisor(actor.DrawObject.Pointer, actor.VisorEnabled);
|
||||||
|
}
|
||||||
|
|
||||||
public void Update(Actor actor)
|
public void Update(Actor actor)
|
||||||
{
|
{
|
||||||
if (!actor)
|
if (!actor)
|
||||||
|
|
|
||||||
10
Glamourer/State/IDesign.cs
Normal file
10
Glamourer/State/IDesign.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
using Glamourer.Interop;
|
||||||
|
|
||||||
|
namespace Glamourer.State;
|
||||||
|
|
||||||
|
public interface IDesign
|
||||||
|
{
|
||||||
|
public ref CharacterData Data { get; }
|
||||||
|
|
||||||
|
public void ApplyToActor(Actor a);
|
||||||
|
}
|
||||||
|
|
@ -90,18 +90,18 @@ public static unsafe class CustomizeExtensions
|
||||||
customize.Load(newCustomize);
|
customize.Load(newCustomize);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool ChangeCustomization(this Customize customize, CharacterEquip equip, CustomizationId id, CustomizationByteValue value)
|
public static bool ChangeCustomization(this Customize customize, CharacterEquip equip, CustomizeIndex index, CustomizeValue value)
|
||||||
{
|
{
|
||||||
switch (id)
|
switch (index)
|
||||||
{
|
{
|
||||||
case CustomizationId.Race: return customize.ChangeRace(equip, (SubRace)value.Value);
|
case CustomizeIndex.Race: return customize.ChangeRace(equip, (SubRace)value.Value);
|
||||||
case CustomizationId.Gender: return customize.ChangeGender(equip, (Gender)value.Value);
|
case CustomizeIndex.Gender: return customize.ChangeGender(equip, (Gender)value.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (customize[id] == value)
|
if (customize[index] == value)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
customize[id] = value;
|
customize[index] = value;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,21 +109,20 @@ public static unsafe class CustomizeExtensions
|
||||||
private static void FixUpAttributes(Customize customize)
|
private static void FixUpAttributes(Customize customize)
|
||||||
{
|
{
|
||||||
var set = Glamourer.Customization.GetList(customize.Clan, customize.Gender);
|
var set = Glamourer.Customization.GetList(customize.Clan, customize.Gender);
|
||||||
foreach (CustomizationId id in Enum.GetValues(typeof(CustomizationId)))
|
foreach (CustomizeIndex id in Enum.GetValues(typeof(CustomizeIndex)))
|
||||||
{
|
{
|
||||||
switch (id)
|
switch (id)
|
||||||
{
|
{
|
||||||
case CustomizationId.Race: break;
|
case CustomizeIndex.Race: break;
|
||||||
case CustomizationId.Clan: break;
|
case CustomizeIndex.Clan: break;
|
||||||
case CustomizationId.BodyType: break;
|
case CustomizeIndex.BodyType: break;
|
||||||
case CustomizationId.Gender: break;
|
case CustomizeIndex.Gender: break;
|
||||||
case CustomizationId.FacialFeaturesTattoos: break;
|
case CustomizeIndex.Highlights: break;
|
||||||
case CustomizationId.HighlightsOnFlag: break;
|
case CustomizeIndex.Face: break;
|
||||||
case CustomizationId.Face: break;
|
|
||||||
default:
|
default:
|
||||||
var count = set.Count(id);
|
var count = set.Count(id);
|
||||||
if (set.DataByValue(id, customize[id], out _) < 0)
|
if (set.DataByValue(id, customize[id], out _, customize.Face) < 0)
|
||||||
customize[id] = count == 0 ? CustomizationByteValue.Zero : set.Data(id, 0).Value;
|
customize[id] = count == 0 ? CustomizeValue.Zero : set.Data(id, 0).Value;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue