Current State.

This commit is contained in:
Ottermandias 2024-11-16 21:57:17 +01:00
parent a5998b84ba
commit 2ce8076e9a
20 changed files with 226 additions and 359 deletions

View file

@ -1,126 +0,0 @@
using Lumina.Data;
using Lumina.Excel;
using Lumina.Excel.GeneratedSheets;
namespace Glamourer.GameData;
/// <summary> A custom version of CharaMakeParams that is easier to parse. </summary>
[Sheet("CharaMakeParams")]
public class CharaMakeParams : ExcelRow
{
public const int NumMenus = 28;
public const int NumVoices = 12;
public const int NumGraphics = 10;
public const int MaxNumValues = 100;
public const int NumFaces = 8;
public const int NumFeatures = 7;
public const int NumEquip = 3;
public enum MenuType
{
ListSelector = 0,
IconSelector = 1,
ColorPicker = 2,
DoubleColorPicker = 3,
IconCheckmark = 4,
Percentage = 5,
Checkmark = 6, // custom
Nothing = 7, // custom
List1Selector = 8, // custom, 1-indexed lists
}
public struct Menu
{
public uint Id;
public byte InitVal;
public MenuType Type;
public byte Size;
public byte LookAt;
public uint Mask;
public uint Customize;
public uint[] Values;
public byte[] Graphic;
}
public struct FacialFeatures
{
public uint[] Icons;
}
public LazyRow<Race> Race { get; set; } = null!;
public LazyRow<Tribe> Tribe { get; set; } = null!;
public sbyte Gender { get; set; }
public Menu[] Menus { get; set; } = new Menu[NumMenus];
public byte[] Voices { get; set; } = new byte[NumVoices];
public FacialFeatures[] FacialFeatureByFace { get; set; } = new FacialFeatures[NumFaces];
public CharaMakeType.CharaMakeTypeUnkData3347Obj[] Equip { get; set; } = new CharaMakeType.CharaMakeTypeUnkData3347Obj[NumEquip];
public override void PopulateData(RowParser parser, Lumina.GameData gameData, Language language)
{
RowId = parser.RowId;
SubRowId = parser.SubRowId;
Race = new LazyRow<Race>(gameData, parser.ReadColumn<uint>(0), language);
Tribe = new LazyRow<Tribe>(gameData, parser.ReadColumn<uint>(1), language);
Gender = parser.ReadColumn<sbyte>(2);
int currentOffset;
for (var i = 0; i < NumMenus; ++i)
{
currentOffset = 3 + i;
Menus[i].Id = parser.ReadColumn<uint>(0 * NumMenus + currentOffset);
Menus[i].InitVal = parser.ReadColumn<byte>(1 * NumMenus + currentOffset);
Menus[i].Type = (MenuType)parser.ReadColumn<byte>(2 * NumMenus + currentOffset);
Menus[i].Size = parser.ReadColumn<byte>(3 * NumMenus + currentOffset);
Menus[i].LookAt = parser.ReadColumn<byte>(4 * NumMenus + currentOffset);
Menus[i].Mask = parser.ReadColumn<uint>(5 * NumMenus + currentOffset);
Menus[i].Customize = parser.ReadColumn<uint>(6 * NumMenus + currentOffset);
Menus[i].Values = new uint[Menus[i].Size];
switch (Menus[i].Type)
{
case MenuType.ColorPicker:
case MenuType.DoubleColorPicker:
case MenuType.Percentage:
break;
default:
currentOffset += 7 * NumMenus;
for (var j = 0; j < Menus[i].Size; ++j)
Menus[i].Values[j] = parser.ReadColumn<uint>(j * NumMenus + currentOffset);
break;
}
Menus[i].Graphic = new byte[NumGraphics];
currentOffset = 3 + (MaxNumValues + 7) * NumMenus + i;
for (var j = 0; j < NumGraphics; ++j)
Menus[i].Graphic[j] = parser.ReadColumn<byte>(j * NumMenus + currentOffset);
}
currentOffset = 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus;
for (var i = 0; i < NumVoices; ++i)
Voices[i] = parser.ReadColumn<byte>(currentOffset++);
for (var i = 0; i < NumFaces; ++i)
{
currentOffset = 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + i;
FacialFeatureByFace[i].Icons = new uint[NumFeatures];
for (var j = 0; j < NumFeatures; ++j)
FacialFeatureByFace[i].Icons[j] = (uint)parser.ReadColumn<int>(j * NumFaces + currentOffset);
}
for (var i = 0; i < NumEquip; ++i)
{
currentOffset = 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7;
Equip[i] = new CharaMakeType.CharaMakeTypeUnkData3347Obj()
{
Helmet = parser.ReadColumn<ulong>(currentOffset + 0),
Top = parser.ReadColumn<ulong>(currentOffset + 1),
Gloves = parser.ReadColumn<ulong>(currentOffset + 2),
Legs = parser.ReadColumn<ulong>(currentOffset + 3),
Shoes = parser.ReadColumn<ulong>(currentOffset + 4),
Weapon = parser.ReadColumn<ulong>(currentOffset + 5),
SubWeapon = parser.ReadColumn<ulong>(currentOffset + 6),
};
}
}
}

View file

@ -1,6 +1,7 @@
using OtterGui;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Race = Penumbra.GameData.Enums.Race;
namespace Glamourer.GameData;
@ -38,9 +39,9 @@ public class CustomizeSet
public string Option(CustomizeIndex index)
=> OptionName[(int)index];
public IReadOnlyList<byte> Voices { get; internal init; } = null!;
public IReadOnlyList<CharaMakeParams.MenuType> Types { get; internal set; } = null!;
public IReadOnlyDictionary<CharaMakeParams.MenuType, CustomizeIndex[]> Order { get; internal set; } = null!;
public IReadOnlyList<byte> Voices { get; internal init; } = null!;
public IReadOnlyList<MenuType> Types { get; internal set; } = null!;
public IReadOnlyDictionary<MenuType, CustomizeIndex[]> Order { get; internal set; } = null!;
// Always list selector.
@ -97,9 +98,9 @@ public class CustomizeSet
return type switch
{
CharaMakeParams.MenuType.ListSelector => GetInteger0(out custom),
CharaMakeParams.MenuType.List1Selector => GetInteger1(out custom),
CharaMakeParams.MenuType.IconSelector => index switch
MenuType.ListSelector => GetInteger0(out custom),
MenuType.List1Selector => GetInteger1(out custom),
MenuType.IconSelector => index switch
{
CustomizeIndex.Face => Get(Faces, HrothgarFaceHack(value), out custom),
CustomizeIndex.Hairstyle => Get((face = HrothgarFaceHack(face)).Value < HairByFace.Count ? HairByFace[face.Value] : HairStyles,
@ -109,7 +110,7 @@ public class CustomizeSet
CustomizeIndex.LipColor => Get(LipColorsDark, value, out custom),
_ => Invalid(out custom),
},
CharaMakeParams.MenuType.ColorPicker => index switch
MenuType.ColorPicker => index switch
{
CustomizeIndex.SkinColor => Get(SkinColors, value, out custom),
CustomizeIndex.EyeColorLeft => Get(EyeColors, value, out custom),
@ -121,16 +122,16 @@ public class CustomizeSet
CustomizeIndex.FacePaintColor => Get(FacePaintColorsDark.Concat(FacePaintColorsLight), value, out custom),
_ => Invalid(out custom),
},
CharaMakeParams.MenuType.DoubleColorPicker => index switch
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 => GetInteger0(out custom),
CharaMakeParams.MenuType.Checkmark => GetBool(index, value, out custom),
_ => Invalid(out custom),
MenuType.IconCheckmark => GetBool(index, value, out custom),
MenuType.Percentage => GetInteger0(out custom),
MenuType.Checkmark => GetBool(index, value, out custom),
_ => Invalid(out custom),
};
int Get(IEnumerable<CustomizeData> list, CustomizeValue v, out CustomizeData? output)
@ -208,10 +209,10 @@ public class CustomizeSet
switch (Types[(int)index])
{
case CharaMakeParams.MenuType.Percentage: return new CustomizeData(index, (CustomizeValue)idx, 0, (ushort)idx);
case CharaMakeParams.MenuType.ListSelector: return new CustomizeData(index, (CustomizeValue)idx, 0, (ushort)idx);
case CharaMakeParams.MenuType.List1Selector: return new CustomizeData(index, (CustomizeValue)(idx + 1), 0, (ushort)idx);
case CharaMakeParams.MenuType.Checkmark: return new CustomizeData(index, CustomizeValue.Bool(idx != 0), 0, (ushort)idx);
case MenuType.Percentage: return new CustomizeData(index, (CustomizeValue)idx, 0, (ushort)idx);
case MenuType.ListSelector: return new CustomizeData(index, (CustomizeValue)idx, 0, (ushort)idx);
case MenuType.List1Selector: return new CustomizeData(index, (CustomizeValue)(idx + 1), 0, (ushort)idx);
case MenuType.Checkmark: return new CustomizeData(index, CustomizeValue.Bool(idx != 0), 0, (ushort)idx);
}
return index switch
@ -241,7 +242,7 @@ public class CustomizeSet
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public CharaMakeParams.MenuType Type(CustomizeIndex index)
public MenuType Type(CustomizeIndex index)
=> Types[(int)index];
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
@ -256,9 +257,9 @@ public class CustomizeSet
return Type(index) switch
{
CharaMakeParams.MenuType.Percentage => 101,
CharaMakeParams.MenuType.IconCheckmark => 2,
CharaMakeParams.MenuType.Checkmark => 2,
MenuType.Percentage => 101,
MenuType.IconCheckmark => 2,
MenuType.Checkmark => 2,
_ => index switch
{
CustomizeIndex.Face => Faces.Count,

View file

@ -1,10 +1,10 @@
using Dalamud;
using Dalamud.Game;
using Dalamud.Game;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using Lumina.Excel;
using Lumina.Excel.GeneratedSheets;
using Lumina.Excel.Sheets;
using OtterGui.Classes;
using Penumbra.GameData;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Race = Penumbra.GameData.Enums.Race;
@ -32,7 +32,7 @@ internal class CustomizeSetFactory(
var set = new CustomizeSet(race, gender)
{
Name = GetName(race, gender),
Voices = row.Voices,
Voices = row.VoiceStruct,
HairStyles = GetHairStyles(race, gender),
HairColors = hair,
SkinColors = skin,
@ -59,7 +59,7 @@ internal class CustomizeSetFactory(
}
/// <summary> Some data can not be set independently of the rest, so we need a post-processing step to finalize. </summary>
private void SetPostProcessing(CustomizeSet set, CharaMakeParams row)
private void SetPostProcessing(CustomizeSet set, in CharaMakeType row)
{
SetAvailability(set, row);
SetFacialFeatures(set, row);
@ -112,10 +112,10 @@ internal class CustomizeSetFactory(
}
private readonly ColorParameters _colorParameters = new(_gameData, _log);
private readonly ExcelSheet<CharaMakeCustomize> _customizeSheet = _gameData.GetExcelSheet<CharaMakeCustomize>(ClientLanguage.English)!;
private readonly ExcelSheet<Lobby> _lobbySheet = _gameData.GetExcelSheet<Lobby>(ClientLanguage.English)!;
private readonly ExcelSheet<HairMakeType> _hairSheet = _gameData.GetExcelSheet<HairMakeType>(ClientLanguage.English)!;
private readonly ExcelSheet<Tribe> _tribeSheet = _gameData.GetExcelSheet<Tribe>(ClientLanguage.English)!;
private readonly ExcelSheet<CharaMakeCustomize> _customizeSheet = _gameData.GetExcelSheet<CharaMakeCustomize>(ClientLanguage.English);
private readonly ExcelSheet<Lobby> _lobbySheet = _gameData.GetExcelSheet<Lobby>(ClientLanguage.English);
private readonly ExcelSheet<RawRow> _hairSheet = _gameData.GetExcelSheet<RawRow>(ClientLanguage.English, "HairMakeType");
private readonly ExcelSheet<Tribe> _tribeSheet = _gameData.GetExcelSheet<Tribe>(ClientLanguage.English);
// Those color pickers are shared between all races.
private readonly CustomizeData[] _highlightPicker = CreateColors(_colors, CustomizeIndex.HighlightsColor, 256, 192);
@ -126,12 +126,7 @@ internal class CustomizeSetFactory(
private readonly CustomizeData[] _facePaintColorPickerLight = CreateColors(_colors, CustomizeIndex.FacePaintColor, 1152, 96, true);
private readonly CustomizeData[] _tattooColorPicker = CreateColors(_colors, CustomizeIndex.TattooColor, 0, 192);
private readonly ExcelSheet<CharaMakeParams> _charaMakeSheet = _gameData.Excel
.GetType()
.GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)?
.MakeGenericMethod(typeof(CharaMakeParams))
.Invoke(_gameData.Excel, ["charamaketype", _gameData.Language.ToLumina(), null])! as ExcelSheet<CharaMakeParams>
?? null!;
private readonly ExcelSheet<CharaMakeType> _charaMakeSheet = _gameData.Excel.GetSheet<CharaMakeType>();
/// <summary> Obtain available skin and hair colors for the given clan and gender. </summary>
private (CustomizeData[] Skin, CustomizeData[] Hair) GetSkinHairColors(SubRace race, Gender gender)
@ -150,29 +145,28 @@ internal class CustomizeSetFactory(
private string GetName(SubRace race, Gender gender)
=> gender switch
{
Gender.Male => _tribeSheet.GetRow((uint)race)?.Masculine.ToDalamudString().TextValue ?? race.ToName(),
Gender.Female => _tribeSheet.GetRow((uint)race)?.Feminine.ToDalamudString().TextValue ?? race.ToName(),
Gender.Male => _tribeSheet.TryGetRow((uint)race, out var row) ? row.Masculine.ExtractText() : race.ToName(),
Gender.Female => _tribeSheet.TryGetRow((uint)race, out var row) ? row.Feminine.ExtractText() : race.ToName(),
_ => "Unknown",
};
/// <summary> Obtain available hairstyles via reflection from the Hair sheet for the given subrace and gender. </summary>
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.
var hairList = new List<CustomizeData>(row.Unknown30);
var numHairs = row.ReadUInt8Column(30);
var hairList = new List<CustomizeData>(numHairs);
// Hairstyles can be found starting at Unknown66.
for (var i = 0; i < row.Unknown30; ++i)
for (var i = 0; i < numHairs; ++i)
{
var name = $"Unknown{66 + i * 9}";
var customizeIdx = (uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
?? uint.MaxValue;
// Hairs start at Unknown66.
var customizeIdx = row.ReadUInt32Column(66 + i * 9);
if (customizeIdx == uint.MaxValue)
continue;
// Hair Row from CustomizeSheet might not be set in case of unlockable hair.
var hairRow = _customizeSheet.GetRow(customizeIdx);
if (hairRow == null)
if (_customizeSheet.TryGetRow(customizeIdx, out var hairRow))
hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx));
else if (_icons.IconExists(hairRow.Icon))
hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon,
@ -183,45 +177,40 @@ internal class CustomizeSetFactory(
}
/// <summary> Specific icons for tails or ears. </summary>
private CustomizeData[] GetTailEarShapes(CharaMakeParams row)
=> row.Menus.Cast<CharaMakeParams.Menu?>()
.FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.TailShape.ToByteAndMask().ByteIdx)?.Values
.Select((v, i) => FromValueAndIndex(CustomizeIndex.TailShape, v, i)).ToArray()
?? [];
private CustomizeData[] GetTailEarShapes(CharaMakeType row)
=> ExtractValues(row, CustomizeIndex.TailShape);
/// <summary> Specific icons for faces. </summary>
private CustomizeData[] GetFaces(CharaMakeParams row)
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.Face.ToByteAndMask().ByteIdx)
?.Values
.Select((v, i) => FromValueAndIndex(CustomizeIndex.Face, v, i)).ToArray()
?? [];
private CustomizeData[] GetFaces(CharaMakeType row)
=> ExtractValues(row, CustomizeIndex.Face);
/// <summary> Specific icons for Hrothgar patterns. </summary>
private CustomizeData[] HrothgarFurPattern(CharaMakeParams row)
=> row.Menus.Cast<CharaMakeParams.Menu?>()
.FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.LipColor.ToByteAndMask().ByteIdx)?.Values
.Select((v, i) => FromValueAndIndex(CustomizeIndex.LipColor, v, i)).ToArray()
?? [];
private CustomizeData[] HrothgarFurPattern(CharaMakeType row)
=> ExtractValues(row, CustomizeIndex.LipColor);
private CustomizeData[] ExtractValues(CharaMakeType row, CustomizeIndex type)
{
var data = row.CharaMakeStruct.FirstOrNull(m => m.Customize == CustomizeIndex.TailShape.ToByteAndMask().ByteIdx);
return data?.SubMenuParam.Take(data.Value.SubMenuNum).Select((v, i) => FromValueAndIndex(type, v, i)).ToArray() ?? [];
}
/// <summary> Get face paints from the hair sheet via reflection since there are also unlockable face paints. </summary>
private CustomizeData[] GetFacePaints(SubRace race, Gender gender)
{
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
var paintList = new List<CustomizeData>(row.Unknown37);
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender);
// Number of available face paints is at Unknown37.
for (var i = 0; i < row.Unknown37; ++i)
var numPaints = row.ReadUInt8Column(37);
var paintList = new List<CustomizeData>(numPaints);
for (var i = 0; i < numPaints; ++i)
{
// Face paints start at Unknown73.
var name = $"Unknown{73 + i * 9}";
var customizeIdx =
(uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
?? uint.MaxValue;
var customizeIdx = row.ReadUInt32Column(73 + i * 9);
if (customizeIdx == uint.MaxValue)
continue;
var paintRow = _customizeSheet.GetRow(customizeIdx);
// Face paint Row from CustomizeSheet might not be set in case of unlockable face paints.
if (paintRow != null)
if (_customizeSheet.TryGetRow(customizeIdx, out var paintRow))
paintList.Add(new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)paintRow.FeatureID, paintRow.Icon,
(ushort)paintRow.RowId));
else
@ -232,21 +221,18 @@ internal class CustomizeSetFactory(
}
/// <summary> Get List sizes. </summary>
private static int GetListSize(CharaMakeParams row, CustomizeIndex index)
private static int GetListSize(CharaMakeType row, CustomizeIndex index)
{
var gameId = index.ToByteAndMask().ByteIdx;
var menu = row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customize == gameId);
return menu?.Size ?? 0;
var menu = row.CharaMakeStruct.FirstOrNull(m => m.Customize == gameId);
return menu?.SubMenuNum ?? 0;
}
/// <summary> Get generic Features. </summary>
private CustomizeData FromValueAndIndex(CustomizeIndex id, uint value, int index)
{
var row = _customizeSheet.GetRow(value);
return row == null
? new CustomizeData(id, (CustomizeValue)(index + 1), value)
: new CustomizeData(id, (CustomizeValue)row.FeatureID, row.Icon, (ushort)row.RowId);
}
=> _customizeSheet.TryGetRow(value, out var row)
? new CustomizeData(id, (CustomizeValue)row.FeatureID, row.Icon, (ushort)row.RowId)
: new CustomizeData(id, (CustomizeValue)(index + 1), value);
/// <summary> Create generic color sets from the parameters. </summary>
private static CustomizeData[] CreateColors(ColorParameters colorParameters, CustomizeIndex index, int offset, int num,
@ -264,28 +250,27 @@ internal class CustomizeSetFactory(
}
/// <summary> Set the specific option names for the given set of parameters. </summary>
private string[] GetOptionNames(CharaMakeParams row)
private string[] GetOptionNames(CharaMakeType row)
{
var nameArray = Enum.GetValues<CustomizeIndex>().Select(c =>
{
// Find the first menu that corresponds to the Id.
var byteId = c.ToByteAndMask().ByteIdx;
var menu = row.Menus
.Cast<CharaMakeParams.Menu?>()
.FirstOrDefault(m => m!.Value.Customize == byteId);
var menu = row.CharaMakeStruct.FirstOrNull(m => m.Customize == byteId);
if (menu == null)
{
// If none exists and the id corresponds to highlights, set the Highlights name.
if (c == CustomizeIndex.Highlights)
return string.Intern(_lobbySheet.GetRow(237)?.Text.ToDalamudString().ToString() ?? "Highlights");
return string.Intern(_lobbySheet.TryGetRow(237, out var text) ? text.Text.ExtractText() : "Highlights");
// Otherwise there is an error and we use the default name.
return c.ToDefaultName();
}
// Otherwise all is normal, get the menu name or if it does not work the default name.
var textRow = _lobbySheet.GetRow(menu.Value.Id);
return string.Intern(textRow?.Text.ToDalamudString().ToString() ?? c.ToDefaultName());
return string.Intern(_lobbySheet.TryGetRow(menu.Value.Menu.RowId, out var textRow)
? textRow.Text.ExtractText()
: c.ToDefaultName());
}).ToArray();
// Add names for both eye colors.
@ -306,7 +291,7 @@ internal class CustomizeSetFactory(
}
/// <summary> Get the manu types for all available options. </summary>
private static CharaMakeParams.MenuType[] GetMenuTypes(CharaMakeParams row)
private static MenuType[] GetMenuTypes(CharaMakeType row)
{
// Set up the menu types for all customizations.
return Enum.GetValues<CustomizeIndex>().Select(c =>
@ -318,13 +303,13 @@ internal class CustomizeSetFactory(
case CustomizeIndex.EyeColorLeft:
case CustomizeIndex.EyeColorRight:
case CustomizeIndex.FacePaintColor:
return CharaMakeParams.MenuType.ColorPicker;
case CustomizeIndex.BodyType: return CharaMakeParams.MenuType.Nothing;
return MenuType.ColorPicker;
case CustomizeIndex.BodyType: return MenuType.Nothing;
case CustomizeIndex.FacePaintReversed:
case CustomizeIndex.Highlights:
case CustomizeIndex.SmallIris:
case CustomizeIndex.Lipstick:
return CharaMakeParams.MenuType.Checkmark;
return MenuType.Checkmark;
case CustomizeIndex.FacialFeature1:
case CustomizeIndex.FacialFeature2:
case CustomizeIndex.FacialFeature3:
@ -333,24 +318,22 @@ internal class CustomizeSetFactory(
case CustomizeIndex.FacialFeature6:
case CustomizeIndex.FacialFeature7:
case CustomizeIndex.LegacyTattoo:
return CharaMakeParams.MenuType.IconCheckmark;
return MenuType.IconCheckmark;
}
var gameId = c.ToByteAndMask().ByteIdx;
// Otherwise find the first menu corresponding to the id.
// If there is none, assume a list.
var menu = row.Menus
.Cast<CharaMakeParams.Menu?>()
.FirstOrDefault(m => m!.Value.Customize == gameId);
var ret = menu?.Type ?? CharaMakeParams.MenuType.ListSelector;
if (c is CustomizeIndex.TailShape && ret is CharaMakeParams.MenuType.ListSelector)
ret = CharaMakeParams.MenuType.List1Selector;
var menu = row.CharaMakeStruct.FirstOrNull(m => m.Customize == gameId);
var ret = (MenuType)(menu?.SubMenuType ?? (byte)MenuType.ListSelector);
if (c is CustomizeIndex.TailShape && ret is MenuType.ListSelector)
ret = MenuType.List1Selector;
return ret;
}).ToArray();
}
/// <summary> Set the availability of options according to actual availability. </summary>
private static void SetAvailability(CustomizeSet set, CharaMakeParams row)
private static void SetAvailability(CustomizeSet set, CharaMakeType row)
{
Set(true, CustomizeIndex.Height);
Set(set.Faces.Count > 0, CustomizeIndex.Face);
@ -401,7 +384,7 @@ internal class CustomizeSetFactory(
ret[(int)CustomizeIndex.EyeColorRight] = CustomizeIndex.TattooColor;
var dict = ret.Skip(2).Where(set.IsAvailable).GroupBy(set.Type).ToDictionary(k => k.Key, k => k.ToArray());
foreach (var type in Enum.GetValues<CharaMakeParams.MenuType>())
foreach (var type in Enum.GetValues<MenuType>())
dict.TryAdd(type, []);
set.Order = dict;
}
@ -425,7 +408,7 @@ internal class CustomizeSetFactory(
bool Valid(CustomizeData c)
{
var data = _customizeSheet.GetRow(c.CustomizeId)?.Unknown6 ?? 0;
var data = _customizeSheet.TryGetRow(c.CustomizeId, out var customize) ? customize.Unknown0 : 0;
return data == 0 || data == i + set.Faces.Count;
}
}
@ -437,7 +420,7 @@ internal class CustomizeSetFactory(
/// Create a list of lists of facial features and the legacy tattoo.
/// Facial Features are bools in a bitfield, so we supply an "off" and an "on" value for simplicity of use.
/// </summary>
private static void SetFacialFeatures(CustomizeSet set, CharaMakeParams row)
private static void SetFacialFeatures(CustomizeSet set, in CharaMakeType row)
{
var count = set.Faces.Count;
set.FacialFeature1 = new List<(CustomizeData, CustomizeData)>(count);
@ -446,14 +429,14 @@ internal class CustomizeSetFactory(
var tmp = Enumerable.Repeat(0, 7).Select(_ => new (CustomizeData, CustomizeData)[count + 1]).ToArray();
for (var i = 0; i < count; ++i)
{
var data = row.FacialFeatureByFace[i].Icons;
tmp[0][i + 1] = Create(CustomizeIndex.FacialFeature1, data[0]);
tmp[1][i + 1] = Create(CustomizeIndex.FacialFeature2, data[1]);
tmp[2][i + 1] = Create(CustomizeIndex.FacialFeature3, data[2]);
tmp[3][i + 1] = Create(CustomizeIndex.FacialFeature4, data[3]);
tmp[4][i + 1] = Create(CustomizeIndex.FacialFeature5, data[4]);
tmp[5][i + 1] = Create(CustomizeIndex.FacialFeature6, data[5]);
tmp[6][i + 1] = Create(CustomizeIndex.FacialFeature7, data[6]);
var data = row.FacialFeatureOption[i];
tmp[0][i + 1] = Create(CustomizeIndex.FacialFeature1, (uint)data.Option1);
tmp[1][i + 1] = Create(CustomizeIndex.FacialFeature2, (uint)data.Option2);
tmp[2][i + 1] = Create(CustomizeIndex.FacialFeature3, (uint)data.Option3);
tmp[3][i + 1] = Create(CustomizeIndex.FacialFeature4, (uint)data.Option4);
tmp[4][i + 1] = Create(CustomizeIndex.FacialFeature5, (uint)data.Option5);
tmp[5][i + 1] = Create(CustomizeIndex.FacialFeature6, (uint)data.Option6);
tmp[6][i + 1] = Create(CustomizeIndex.FacialFeature7, (uint)data.Option7);
}
set.FacialFeature1 = tmp[0];

View file

@ -0,0 +1,14 @@
namespace Glamourer.GameData;
public enum MenuType
{
ListSelector = 0,
IconSelector = 1,
ColorPicker = 2,
DoubleColorPicker = 3,
IconCheckmark = 4,
Percentage = 5,
Checkmark = 6, // custom
Nothing = 7, // custom
List1Selector = 8, // custom, 1-indexed lists
}

View file

@ -1,7 +1,7 @@
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Lumina.Excel.GeneratedSheets;
using Lumina.Excel.Sheets;
using OtterGui.Services;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Structs;
@ -52,15 +52,14 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
/// <summary> Create data from event NPCs. </summary>
private static List<NpcData> CreateEnpcData(IDataManager data, DictENpc eNpcs)
{
var enpcSheet = data.GetExcelSheet<ENpcBase>()!;
var enpcSheet = data.GetExcelSheet<ENpcBase>();
var list = new List<NpcData>(eNpcs.Count);
// Go through all event NPCs already collected into a dictionary.
foreach (var (id, name) in eNpcs)
{
var row = enpcSheet.GetRow(id.Id);
// We only accept NPCs with valid names.
if (row == null || name.IsNullOrWhitespace())
if (!enpcSheet.TryGetRow(id.Id, out var row) || name.IsNullOrWhitespace())
continue;
// Check if the customization is a valid human.
@ -72,14 +71,14 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
{
Name = name,
Customize = customize,
ModelId = row.ModelChara.Row,
ModelId = row.ModelChara.RowId,
Id = id,
Kind = ObjectKind.EventNpc,
};
// Event NPCs have a reference to NpcEquip but also contain the appearance in their own row.
// Prefer the NpcEquip reference if it is set and the own does not appear to be set, otherwise use the own.
if (row.NpcEquip.Row != 0 && row.NpcEquip.Value is { } equip && row is { ModelBody: 0, ModelLegs: 0 })
if (row.NpcEquip.RowId != 0 && row.NpcEquip.Value is { } equip && row is { ModelBody: 0, ModelLegs: 0 })
ApplyNpcEquip(ref ret, equip);
else
ApplyNpcEquip(ref ret, row);
@ -93,14 +92,14 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
/// <summary> Create data from battle NPCs. </summary>
private static List<NpcData> CreateBnpcData(IDataManager data, DictBNpc bNpcs, DictBNpcNames bNpcNames)
{
var bnpcSheet = data.GetExcelSheet<BNpcBase>()!;
var list = new List<NpcData>((int)bnpcSheet.RowCount);
var bnpcSheet = data.GetExcelSheet<BNpcBase>();
var list = new List<NpcData>(bnpcSheet.Count);
// We go through all battle NPCs in the sheet because the dictionary refers to names.
foreach (var baseRow in bnpcSheet)
{
// Only accept humans.
if (baseRow.ModelChara.Value!.Type != 1)
if (baseRow.ModelChara.Value.Type != 1)
continue;
var bnpcNameIds = bNpcNames[baseRow.RowId];
@ -109,15 +108,15 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
continue;
// Check if the customization is a valid human.
var (valid, customize) = FromBnpcCustomize(baseRow.BNpcCustomize.Value!);
var (valid, customize) = FromBnpcCustomize(baseRow.BNpcCustomize.Value);
if (!valid)
continue;
var equip = baseRow.NpcEquip.Value!;
var equip = baseRow.NpcEquip.Value;
var ret = new NpcData
{
Customize = customize,
ModelId = baseRow.ModelChara.Row,
ModelId = baseRow.ModelChara.RowId,
Id = baseRow.RowId,
Kind = ObjectKind.BattleNpc,
};
@ -186,36 +185,36 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
/// <summary> Apply equipment from a NpcEquip row. </summary>
private static void ApplyNpcEquip(ref NpcData data, NpcEquip row)
{
data.Set(0, row.ModelHead | (row.DyeHead.Row << 24) | ((ulong)row.Dye2Head.Row << 32));
data.Set(1, row.ModelBody | (row.DyeBody.Row << 24) | ((ulong)row.Dye2Body.Row << 32));
data.Set(2, row.ModelHands | (row.DyeHands.Row << 24) | ((ulong)row.Dye2Hands.Row << 32));
data.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24) | ((ulong)row.Dye2Legs.Row << 32));
data.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24) | ((ulong)row.Dye2Feet.Row << 32));
data.Set(5, row.ModelEars | (row.DyeEars.Row << 24) | ((ulong)row.Dye2Ears.Row << 32));
data.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24) | ((ulong)row.Dye2Neck.Row << 32));
data.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24) | ((ulong)row.Dye2Wrists.Row << 32));
data.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24) | ((ulong)row.Dye2RightRing.Row << 32));
data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24) | ((ulong)row.Dye2LeftRing.Row << 32));
data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48) | ((ulong)row.Dye2MainHand.Row << 56));
data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48) | ((ulong)row.Dye2OffHand.Row << 56));
data.Set(0, row.ModelHead | (row.DyeHead.RowId << 24) | ((ulong)row.Dye2Head.RowId << 32));
data.Set(1, row.ModelBody | (row.DyeBody.RowId << 24) | ((ulong)row.Dye2Body.RowId << 32));
data.Set(2, row.ModelHands | (row.DyeHands.RowId << 24) | ((ulong)row.Dye2Hands.RowId << 32));
data.Set(3, row.ModelLegs | (row.DyeLegs.RowId << 24) | ((ulong)row.Dye2Legs.RowId << 32));
data.Set(4, row.ModelFeet | (row.DyeFeet.RowId << 24) | ((ulong)row.Dye2Feet.RowId << 32));
data.Set(5, row.ModelEars | (row.DyeEars.RowId << 24) | ((ulong)row.Dye2Ears.RowId << 32));
data.Set(6, row.ModelNeck | (row.DyeNeck.RowId << 24) | ((ulong)row.Dye2Neck.RowId << 32));
data.Set(7, row.ModelWrists | (row.DyeWrists.RowId << 24) | ((ulong)row.Dye2Wrists.RowId << 32));
data.Set(8, row.ModelRightRing | (row.DyeRightRing.RowId << 24) | ((ulong)row.Dye2RightRing.RowId << 32));
data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.RowId << 24) | ((ulong)row.Dye2LeftRing.RowId << 32));
data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.RowId << 48) | ((ulong)row.Dye2MainHand.RowId << 56));
data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.RowId << 48) | ((ulong)row.Dye2OffHand.RowId << 56));
data.VisorToggled = row.Visor;
}
/// <summary> Apply equipment from a ENpcBase Row row. </summary>
private static void ApplyNpcEquip(ref NpcData data, ENpcBase row)
{
data.Set(0, row.ModelHead | (row.DyeHead.Row << 24) | ((ulong)row.Dye2Head.Row << 32));
data.Set(1, row.ModelBody | (row.DyeBody.Row << 24) | ((ulong)row.Dye2Body.Row << 32));
data.Set(2, row.ModelHands | (row.DyeHands.Row << 24) | ((ulong)row.Dye2Hands.Row << 32));
data.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24) | ((ulong)row.Dye2Legs.Row << 32));
data.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24) | ((ulong)row.Dye2Feet.Row << 32));
data.Set(5, row.ModelEars | (row.DyeEars.Row << 24) | ((ulong)row.Dye2Ears.Row << 32));
data.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24) | ((ulong)row.Dye2Neck.Row << 32));
data.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24) | ((ulong)row.Dye2Wrists.Row << 32));
data.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24) | ((ulong)row.Dye2RightRing.Row << 32));
data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24) | ((ulong)row.Dye2LeftRing.Row << 32));
data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48) | ((ulong)row.Dye2MainHand.Row << 56));
data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48) | ((ulong)row.Dye2OffHand.Row << 56));
data.Set(0, row.ModelHead | (row.DyeHead.RowId << 24) | ((ulong)row.Dye2Head.RowId << 32));
data.Set(1, row.ModelBody | (row.DyeBody.RowId << 24) | ((ulong)row.Dye2Body.RowId << 32));
data.Set(2, row.ModelHands | (row.DyeHands.RowId << 24) | ((ulong)row.Dye2Hands.RowId << 32));
data.Set(3, row.ModelLegs | (row.DyeLegs.RowId << 24) | ((ulong)row.Dye2Legs.RowId << 32));
data.Set(4, row.ModelFeet | (row.DyeFeet.RowId << 24) | ((ulong)row.Dye2Feet.RowId << 32));
data.Set(5, row.ModelEars | (row.DyeEars.RowId << 24) | ((ulong)row.Dye2Ears.RowId << 32));
data.Set(6, row.ModelNeck | (row.DyeNeck.RowId << 24) | ((ulong)row.Dye2Neck.RowId << 32));
data.Set(7, row.ModelWrists | (row.DyeWrists.RowId << 24) | ((ulong)row.Dye2Wrists.RowId << 32));
data.Set(8, row.ModelRightRing | (row.DyeRightRing.RowId << 24) | ((ulong)row.Dye2RightRing.RowId << 32));
data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.RowId << 24) | ((ulong)row.Dye2LeftRing.RowId << 32));
data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.RowId << 48) | ((ulong)row.Dye2MainHand.RowId << 56));
data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.RowId << 48) | ((ulong)row.Dye2OffHand.RowId << 56));
data.VisorToggled = row.Visor;
}
@ -223,11 +222,11 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
private static (bool, CustomizeArray) FromBnpcCustomize(BNpcCustomize bnpcCustomize)
{
var customize = new CustomizeArray();
customize.SetByIndex(0, (CustomizeValue)(byte)bnpcCustomize.Race.Row);
customize.SetByIndex(0, (CustomizeValue)(byte)bnpcCustomize.Race.RowId);
customize.SetByIndex(1, (CustomizeValue)bnpcCustomize.Gender);
customize.SetByIndex(2, (CustomizeValue)bnpcCustomize.BodyType);
customize.SetByIndex(3, (CustomizeValue)bnpcCustomize.Height);
customize.SetByIndex(4, (CustomizeValue)(byte)bnpcCustomize.Tribe.Row);
customize.SetByIndex(4, (CustomizeValue)(byte)bnpcCustomize.Tribe.RowId);
customize.SetByIndex(5, (CustomizeValue)bnpcCustomize.Face);
customize.SetByIndex(6, (CustomizeValue)bnpcCustomize.HairStyle);
customize.SetByIndex(7, (CustomizeValue)bnpcCustomize.HairHighlight);
@ -261,15 +260,15 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
/// <summary> Obtain customizations from a ENpcBase row and check if the human is valid. </summary>
private static (bool, CustomizeArray) FromEnpcBase(ENpcBase enpcBase)
{
if (enpcBase.ModelChara.Value?.Type != 1)
if (enpcBase.ModelChara.ValueNullable?.Type != 1)
return (false, CustomizeArray.Default);
var customize = new CustomizeArray();
customize.SetByIndex(0, (CustomizeValue)(byte)enpcBase.Race.Row);
customize.SetByIndex(0, (CustomizeValue)(byte)enpcBase.Race.RowId);
customize.SetByIndex(1, (CustomizeValue)enpcBase.Gender);
customize.SetByIndex(2, (CustomizeValue)enpcBase.BodyType);
customize.SetByIndex(3, (CustomizeValue)enpcBase.Height);
customize.SetByIndex(4, (CustomizeValue)(byte)enpcBase.Tribe.Row);
customize.SetByIndex(4, (CustomizeValue)(byte)enpcBase.Tribe.RowId);
customize.SetByIndex(5, (CustomizeValue)enpcBase.Face);
customize.SetByIndex(6, (CustomizeValue)enpcBase.HairStyle);
customize.SetByIndex(7, (CustomizeValue)enpcBase.HairHighlight);

View file

@ -8,7 +8,7 @@
"AssemblyVersion": "9.0.0.1",
"RepoUrl": "https://github.com/Ottermandias/Glamourer",
"ApplicableVersion": "any",
"DalamudApiLevel": 10,
"DalamudApiLevel": 11,
"ImageUrls": null,
"IconUrl": "https://raw.githubusercontent.com/Ottermandias/Glamourer/master/images/icon.png"
}

View file

@ -193,7 +193,7 @@ public partial class CustomizationDrawer
private void DrawMultiIcons()
{
var options = _set.Order[CharaMakeParams.MenuType.IconCheckmark];
var options = _set.Order[MenuType.IconCheckmark];
using var group = ImRaii.Group();
var face = _set.DataByValue(CustomizeIndex.Face, _customize.Face, out _, _customize.Face) < 0 ? _set.Faces[0].Value : _customize.Face;
foreach (var (featureIdx, idx) in options.WithIndex())

View file

@ -121,22 +121,22 @@ public partial class CustomizationDrawer(
_set = _service.Manager.GetSet(_customize.Clan, _customize.Gender);
foreach (var id in _set.Order[CharaMakeParams.MenuType.Percentage])
foreach (var id in _set.Order[MenuType.Percentage])
PercentageSelector(id);
Functions.IteratePairwise(_set.Order[CharaMakeParams.MenuType.IconSelector], DrawIconSelector, ImGui.SameLine);
Functions.IteratePairwise(_set.Order[MenuType.IconSelector], DrawIconSelector, ImGui.SameLine);
DrawMultiIconSelector();
foreach (var id in _set.Order[CharaMakeParams.MenuType.ListSelector])
foreach (var id in _set.Order[MenuType.ListSelector])
DrawListSelector(id, false);
foreach (var id in _set.Order[CharaMakeParams.MenuType.List1Selector])
foreach (var id in _set.Order[MenuType.List1Selector])
DrawListSelector(id, true);
Functions.IteratePairwise(_set.Order[CharaMakeParams.MenuType.ColorPicker], DrawColorPicker, ImGui.SameLine);
Functions.IteratePairwise(_set.Order[MenuType.ColorPicker], DrawColorPicker, ImGui.SameLine);
Functions.IteratePairwise(_set.Order[CharaMakeParams.MenuType.Checkmark], DrawCheckbox,
Functions.IteratePairwise(_set.Order[MenuType.Checkmark], DrawCheckbox,
() => ImGui.SameLine(_comboSelectorSize - _framedIconSize.X + ImGui.GetStyle().WindowPadding.X));
return Changed != 0 || ChangeApply != _initialApply;
}

View file

@ -2,7 +2,7 @@
using Glamourer.Services;
using Glamourer.Unlocks;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using Lumina.Excel.Sheets;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Log;
@ -91,8 +91,8 @@ public sealed class BonusItemCombo : FilterComboCache<EquipItem>
return slot switch
{
BonusItemFlag.Glasses => sheet.GetRow(16050)?.Text.ToString() ?? "Facewear",
BonusItemFlag.UnkSlot => sheet.GetRow(16051)?.Text.ToString() ?? "Facewear",
BonusItemFlag.Glasses => sheet.TryGetRow(16050, out var text) ? text.Text.ToString() : "Facewear",
BonusItemFlag.UnkSlot => sheet.TryGetRow(16051, out var text) ? text.Text.ToString() : "Facewear",
_ => string.Empty,
};

View file

@ -2,7 +2,7 @@
using Glamourer.Services;
using Glamourer.Unlocks;
using ImGuiNET;
using Lumina.Excel.GeneratedSheets;
using Lumina.Excel.Sheets;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Log;
@ -88,20 +88,20 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
private static string GetLabel(IDataManager gameData, EquipSlot slot)
{
var sheet = gameData.GetExcelSheet<Addon>()!;
var sheet = gameData.GetExcelSheet<Addon>();
return slot switch
{
EquipSlot.Head => sheet.GetRow(740)?.Text.ToString() ?? "Head",
EquipSlot.Body => sheet.GetRow(741)?.Text.ToString() ?? "Body",
EquipSlot.Hands => sheet.GetRow(742)?.Text.ToString() ?? "Hands",
EquipSlot.Legs => sheet.GetRow(744)?.Text.ToString() ?? "Legs",
EquipSlot.Feet => sheet.GetRow(745)?.Text.ToString() ?? "Feet",
EquipSlot.Ears => sheet.GetRow(746)?.Text.ToString() ?? "Ears",
EquipSlot.Neck => sheet.GetRow(747)?.Text.ToString() ?? "Neck",
EquipSlot.Wrists => sheet.GetRow(748)?.Text.ToString() ?? "Wrists",
EquipSlot.RFinger => sheet.GetRow(749)?.Text.ToString() ?? "Right Ring",
EquipSlot.LFinger => sheet.GetRow(750)?.Text.ToString() ?? "Left Ring",
EquipSlot.Head => sheet.TryGetRow(740, out var text) ? text.Text.ToString() : "Head",
EquipSlot.Body => sheet.TryGetRow(741, out var text) ? text.Text.ToString() : "Body",
EquipSlot.Hands => sheet.TryGetRow(742, out var text) ? text.Text.ToString() : "Hands",
EquipSlot.Legs => sheet.TryGetRow(744, out var text) ? text.Text.ToString() : "Legs",
EquipSlot.Feet => sheet.TryGetRow(745, out var text) ? text.Text.ToString() : "Feet",
EquipSlot.Ears => sheet.TryGetRow(746, out var text) ? text.Text.ToString() : "Ears",
EquipSlot.Neck => sheet.TryGetRow(747, out var text) ? text.Text.ToString() : "Neck",
EquipSlot.Wrists => sheet.TryGetRow(748, out var text) ? text.Text.ToString() : "Wrists",
EquipSlot.RFinger => sheet.TryGetRow(749, out var text) ? text.Text.ToString() : "Right Ring",
EquipSlot.LFinger => sheet.TryGetRow(750, out var text) ? text.Text.ToString() : "Left Ring",
_ => string.Empty,
};
}

View file

@ -40,7 +40,7 @@ public class UnlockOverview(
foreach (var type in Enum.GetValues<FullEquipType>())
{
if (type.IsOffhandType() || !items.ItemData.ByType.TryGetValue(type, out var value) || value.Count == 0)
if (type.IsOffhandType() || type.IsBonus() || !items.ItemData.ByType.TryGetValue(type, out var value) || value.Count == 0)
continue;
if (ImGui.Selectable(type.ToName(), _selected1 == type))

View file

@ -47,7 +47,7 @@ public sealed unsafe class CrestService : EventWrapperRef3<Actor, CrestFlag, boo
flags &= CrestExtensions.AllRelevant;
var currentCrests = gameObject.CrestBitfield;
using var update = _inUpdate.EnterMethod();
_crestChangeHook.Original(gameObject.AsCharacter, (byte) flags);
_crestChangeHook.Original(&gameObject.AsCharacter->DrawData, (byte) flags);
gameObject.CrestBitfield = currentCrests;
}
@ -62,14 +62,14 @@ public sealed unsafe class CrestService : EventWrapperRef3<Actor, CrestFlag, boo
_crestChangeHook.Dispose();
}
private delegate void CrestChangeDelegate(Character* character, byte crestFlags);
private delegate void CrestChangeDelegate(DrawDataContainer* container, byte crestFlags);
[Signature(Sigs.CrestChange, DetourName = nameof(CrestChangeDetour))]
private readonly Hook<CrestChangeDelegate> _crestChangeHook = null!;
private void CrestChangeDetour(Character* character, byte crestFlags)
private void CrestChangeDetour(DrawDataContainer* container, byte crestFlags)
{
var actor = (Actor)character;
var actor = (Actor)container->OwnerObject;
foreach (var slot in CrestExtensions.AllRelevantSet)
{
var newValue = ((CrestFlag)crestFlags).HasFlag(slot);
@ -78,9 +78,9 @@ public sealed unsafe class CrestService : EventWrapperRef3<Actor, CrestFlag, boo
}
Glamourer.Log.Verbose(
$"Called CrestChange on {(ulong)character:X} with {crestFlags:X} and prior flags {((Actor)character).CrestBitfield}.");
$"Called CrestChange on {(ulong)container:X} with {crestFlags:X} and prior flags {actor.CrestBitfield}.");
using var _ = _inUpdate.EnterMethod();
_crestChangeHook.Original(character, crestFlags);
_crestChangeHook.Original(container, crestFlags);
}
public static bool GetModelCrest(Actor gameObject, CrestFlag slot)

View file

@ -5,6 +5,8 @@ using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using Penumbra.GameData;
using Penumbra.GameData.Interop;
using Penumbra.GameData.Structs;
using System.ComponentModel;
using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character;
namespace Glamourer.Interop;
@ -16,12 +18,11 @@ public unsafe class ScalingService : IDisposable
interop.InitializeFromAttributes(this);
_setupMountHook =
interop.HookFromAddress<SetupMount>((nint)MountContainer.MemberFunctionPointers.SetupMount, SetupMountDetour);
_setupOrnamentHook = interop.HookFromAddress<SetupOrnament>((nint)Ornament.MemberFunctionPointers.SetupOrnament, SetupOrnamentDetour);
_calculateHeightHook =
interop.HookFromAddress<CalculateHeight>((nint)Character.MemberFunctionPointers.CalculateHeight, CalculateHeightDetour);
interop.HookFromAddress<CalculateHeight>((nint)HeightContainer.MemberFunctionPointers.CalculateHeight, CalculateHeightDetour);
_setupMountHook.Enable();
_setupOrnamentHook.Enable();
_updateOrnamentHook.Enable();
_placeMinionHook.Enable();
_calculateHeightHook.Enable();
}
@ -29,19 +30,21 @@ public unsafe class ScalingService : IDisposable
public void Dispose()
{
_setupMountHook.Dispose();
_setupOrnamentHook.Dispose();
_updateOrnamentHook.Dispose();
_placeMinionHook.Dispose();
_calculateHeightHook.Dispose();
}
private delegate void SetupMount(MountContainer* container, short mountId, uint unk1, uint unk2, uint unk3, byte unk4);
private delegate void SetupOrnament(Ornament* ornament, uint* unk1, float* unk2);
private delegate void UpdateOrnament(OrnamentContainer* ornament);
private delegate void PlaceMinion(Companion* character);
private delegate float CalculateHeight(Character* character);
private readonly Hook<SetupMount> _setupMountHook;
private readonly Hook<SetupOrnament> _setupOrnamentHook;
// TODO: Use client structs sig.
[Signature(Sigs.UpdateOrnament, DetourName = nameof(UpdateOrnamentDetour))]
private readonly Hook<UpdateOrnament> _updateOrnamentHook = null!;
private readonly Hook<CalculateHeight> _calculateHeightHook;
@ -57,19 +60,12 @@ public unsafe class ScalingService : IDisposable
SetScaleCustomize(container->OwnerObject, race, clan, gender);
}
private void SetupOrnamentDetour(Ornament* ornament, uint* unk1, float* unk2)
private void UpdateOrnamentDetour(OrnamentContainer* container)
{
var character = ornament->GetParentCharacter();
if (character == null)
{
_setupOrnamentHook.Original(ornament, unk1, unk2);
return;
}
var (race, clan, gender) = GetScaleRelevantCustomize(character);
SetScaleCustomize(character, character->GameObject.DrawObject);
_setupOrnamentHook.Original(ornament, unk1, unk2);
SetScaleCustomize(character, race, clan, gender);
var (race, clan, gender) = GetScaleRelevantCustomize(container->OwnerObject);
SetScaleCustomize(container->OwnerObject, container->OwnerObject->DrawObject);
_updateOrnamentHook.Original(container);
SetScaleCustomize(container->OwnerObject, race, clan, gender);
}
private void PlaceMinionDetour(Companion* companion)

View file

@ -548,7 +548,7 @@ public class CommandService : IDisposable, IApiService
if (baseValue != null)
{
var v = baseValue.Value;
if (set.Type(customizeIndex) is CharaMakeParams.MenuType.ListSelector)
if (set.Type(customizeIndex) is MenuType.ListSelector)
--v;
set.DataByValue(customizeIndex, new CustomizeValue(v), out var data, customize.Face);
if (data != null)

View file

@ -1,5 +1,6 @@
using Dalamud.Plugin.Services;
using Lumina.Excel;
using Lumina.Excel.Sheets;
using Penumbra.GameData.Data;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
@ -16,12 +17,12 @@ public class ItemManager
private readonly Configuration _config;
public readonly ObjectIdentification ObjectIdentification;
public readonly ExcelSheet<Lumina.Excel.GeneratedSheets.Item> ItemSheet;
public readonly DictStain Stains;
public readonly ItemData ItemData;
public readonly DictBonusItems DictBonusItems;
public readonly RestrictedGear RestrictedGear;
public readonly ObjectIdentification ObjectIdentification;
public readonly ExcelSheet<Item> ItemSheet;
public readonly DictStain Stains;
public readonly ItemData ItemData;
public readonly DictBonusItems DictBonusItems;
public readonly RestrictedGear RestrictedGear;
public readonly EquipItem DefaultSword;
@ -29,13 +30,13 @@ public class ItemManager
ItemData itemData, DictStain stains, RestrictedGear restrictedGear, DictBonusItems dictBonusItems)
{
_config = config;
ItemSheet = gameData.GetExcelSheet<Lumina.Excel.GeneratedSheets.Item>()!;
ItemSheet = gameData.GetExcelSheet<Item>();
ObjectIdentification = objectIdentification;
ItemData = itemData;
Stains = stains;
RestrictedGear = restrictedGear;
DictBonusItems = dictBonusItems;
DefaultSword = EquipItem.FromMainhand(ItemSheet.GetRow(1601)!); // Weathered Shortsword
DefaultSword = EquipItem.FromMainhand(ItemSheet.GetRow(1601)); // Weathered Shortsword
}
public (bool, CharacterArmor) ResolveRestrictedGear(CharacterArmor armor, EquipSlot slot, Race race, Gender gender)

View file

@ -8,7 +8,7 @@ using Glamourer.GameData;
using Glamourer.Events;
using Glamourer.Interop;
using Glamourer.Services;
using Lumina.Excel.GeneratedSheets;
using Lumina.Excel.Sheets;
using Penumbra.GameData;
using Penumbra.GameData.Enums;
@ -183,25 +183,25 @@ public class CustomizeUnlockManager : IDisposable, ISavable
var list = customizations.Manager.GetSet(clan, gender);
foreach (var hair in list.HairStyles)
{
var x = sheet.FirstOrDefault(f => f.FeatureID == hair.Value.Value);
var x = sheet.FirstOrNull(f => f.FeatureID == hair.Value.Value);
if (x?.IsPurchasable == true)
{
var name = x.FeatureID == 61
var name = x.Value.FeatureID == 61
? "Eternal Bond"
: x.HintItem.Value?.Name.ToDalamudString().ToString().Replace("Modern Aesthetics - ", string.Empty)
: x.Value.HintItem.ValueNullable?.Name.ExtractText().Replace("Modern Aesthetics - ", string.Empty)
?? string.Empty;
ret.TryAdd(hair, (x.Data, name));
ret.TryAdd(hair, (x.Value.Data, name));
}
}
foreach (var paint in list.FacePaints)
{
var x = sheet.FirstOrDefault(f => f.FeatureID == paint.Value.Value);
var x = sheet.FirstOrNull(f => f.FeatureID == paint.Value.Value);
if (x?.IsPurchasable == true)
{
var name = x.HintItem.Value?.Name.ToDalamudString().ToString().Replace("Modern Cosmetics - ", string.Empty)
var name = x.Value.HintItem.ValueNullable?.Name.ExtractText().Replace("Modern Cosmetics - ", string.Empty)
?? string.Empty;
ret.TryAdd(paint, (x.Data, name));
ret.TryAdd(paint, (x.Value.Data, name));
}
}
}

View file

@ -3,11 +3,11 @@ using FFXIVClientStructs.FFXIV.Client.Game;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using Glamourer.Events;
using Glamourer.Services;
using Lumina.Excel.GeneratedSheets;
using Lumina.Excel.Sheets;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Cabinet = Lumina.Excel.GeneratedSheets.Cabinet;
using Cabinet = Lumina.Excel.Sheets.Cabinet;
namespace Glamourer.Unlocks;
@ -192,7 +192,7 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary<Item
public bool IsUnlocked(CustomItemId itemId, out DateTimeOffset time)
{
// Pseudo items are always unlocked.
if (itemId.Id >= _items.ItemSheet.RowCount)
if (itemId.Id >= (uint) _items.ItemSheet.Count)
{
time = DateTimeOffset.MinValue;
return true;
@ -273,32 +273,31 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary<Item
private static Dictionary<ItemId, UnlockRequirements> CreateUnlockData(IDataManager gameData, ItemManager items)
{
var ret = new Dictionary<ItemId, UnlockRequirements>();
var cabinet = gameData.GetExcelSheet<Cabinet>()!;
var cabinet = gameData.GetExcelSheet<Cabinet>();
foreach (var row in cabinet)
{
if (items.ItemData.TryGetValue(row.Item.Row, EquipSlot.MainHand, out var item))
if (items.ItemData.TryGetValue(row.Item.RowId, EquipSlot.MainHand, out var item))
ret.TryAdd(item.ItemId, new UnlockRequirements(row.RowId, 0, 0, 0, UnlockType.Cabinet));
}
var gilShopItem = gameData.GetExcelSheet<GilShopItem>()!;
var gilShop = gameData.GetExcelSheet<GilShop>()!;
foreach (var row in gilShopItem)
var gilShopItem = gameData.GetSubrowExcelSheet<GilShopItem>();
var gilShop = gameData.GetExcelSheet<GilShop>();
foreach (var row in gilShopItem.SelectMany(g => g))
{
if (!items.ItemData.TryGetValue(row.Item.Row, EquipSlot.MainHand, out var item))
if (!items.ItemData.TryGetValue(row.Item.RowId, EquipSlot.MainHand, out var item))
continue;
var quest1 = row.QuestRequired[0].Row;
var quest2 = row.QuestRequired[1].Row;
var achievement = row.AchievementRequired.Row;
var quest1 = row.QuestRequired[0].RowId;
var quest2 = row.QuestRequired[1].RowId;
var achievement = row.AchievementRequired.RowId;
var state = row.StateRequired;
var shop = gilShop.GetRow(row.RowId);
if (shop != null && shop.Quest.Row != 0)
if (gilShop.TryGetRow(row.RowId, out var shop) && shop.Quest.RowId != 0)
{
if (quest1 == 0)
quest1 = shop.Quest.Row;
quest1 = shop.Quest.RowId;
else if (quest2 == 0)
quest2 = shop.Quest.Row;
quest2 = shop.Quest.RowId;
}
var type = (quest1 != 0 ? UnlockType.Quest1 : 0)

@ -1 +1 @@
Subproject commit d9486ae54b5a4b61cf74f79ed27daa659eb1ce5b
Subproject commit 8ba88eff15326bb28ed5e6157f5252c114d40b5f

@ -1 +1 @@
Subproject commit 77d52b02d21e770b30c08f89bdf06e0cb75562f7
Subproject commit fb81a0b55d3c68f2b26357fac3049c79fb0c22fb

View file

@ -22,7 +22,7 @@
"RepoUrl": "https://github.com/Ottermandias/Glamourer",
"ApplicableVersion": "any",
"DalamudApiLevel": 10,
"TestingDalamudApiLevel": 10,
"TestingDalamudApiLevel": 11,
"IsHide": "False",
"IsTestingExclusive": "False",
"DownloadCount": 1,