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

View file

@ -1,10 +1,10 @@
using Dalamud; using Dalamud.Game;
using Dalamud.Game;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Dalamud.Utility; using Dalamud.Utility;
using Lumina.Excel; using Lumina.Excel;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
using OtterGui.Classes; using OtterGui.Classes;
using Penumbra.GameData;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using Race = Penumbra.GameData.Enums.Race; using Race = Penumbra.GameData.Enums.Race;
@ -32,7 +32,7 @@ internal class CustomizeSetFactory(
var set = new CustomizeSet(race, gender) var set = new CustomizeSet(race, gender)
{ {
Name = GetName(race, gender), Name = GetName(race, gender),
Voices = row.Voices, Voices = row.VoiceStruct,
HairStyles = GetHairStyles(race, gender), HairStyles = GetHairStyles(race, gender),
HairColors = hair, HairColors = hair,
SkinColors = skin, 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> /// <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); SetAvailability(set, row);
SetFacialFeatures(set, row); SetFacialFeatures(set, row);
@ -112,10 +112,10 @@ internal class CustomizeSetFactory(
} }
private readonly ColorParameters _colorParameters = new(_gameData, _log); private readonly ColorParameters _colorParameters = new(_gameData, _log);
private readonly ExcelSheet<CharaMakeCustomize> _customizeSheet = _gameData.GetExcelSheet<CharaMakeCustomize>(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<Lobby> _lobbySheet = _gameData.GetExcelSheet<Lobby>(ClientLanguage.English);
private readonly ExcelSheet<HairMakeType> _hairSheet = _gameData.GetExcelSheet<HairMakeType>(ClientLanguage.English)!; private readonly ExcelSheet<RawRow> _hairSheet = _gameData.GetExcelSheet<RawRow>(ClientLanguage.English, "HairMakeType");
private readonly ExcelSheet<Tribe> _tribeSheet = _gameData.GetExcelSheet<Tribe>(ClientLanguage.English)!; private readonly ExcelSheet<Tribe> _tribeSheet = _gameData.GetExcelSheet<Tribe>(ClientLanguage.English);
// Those color pickers are shared between all races. // Those color pickers are shared between all races.
private readonly CustomizeData[] _highlightPicker = CreateColors(_colors, CustomizeIndex.HighlightsColor, 256, 192); 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[] _facePaintColorPickerLight = CreateColors(_colors, CustomizeIndex.FacePaintColor, 1152, 96, true);
private readonly CustomizeData[] _tattooColorPicker = CreateColors(_colors, CustomizeIndex.TattooColor, 0, 192); private readonly CustomizeData[] _tattooColorPicker = CreateColors(_colors, CustomizeIndex.TattooColor, 0, 192);
private readonly ExcelSheet<CharaMakeParams> _charaMakeSheet = _gameData.Excel private readonly ExcelSheet<CharaMakeType> _charaMakeSheet = _gameData.Excel.GetSheet<CharaMakeType>();
.GetType()
.GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)?
.MakeGenericMethod(typeof(CharaMakeParams))
.Invoke(_gameData.Excel, ["charamaketype", _gameData.Language.ToLumina(), null])! as ExcelSheet<CharaMakeParams>
?? null!;
/// <summary> Obtain available skin and hair colors for the given clan and gender. </summary> /// <summary> Obtain available skin and hair colors for the given clan and gender. </summary>
private (CustomizeData[] Skin, CustomizeData[] Hair) GetSkinHairColors(SubRace race, Gender gender) private (CustomizeData[] Skin, CustomizeData[] Hair) GetSkinHairColors(SubRace race, Gender gender)
@ -150,29 +145,28 @@ internal class CustomizeSetFactory(
private string GetName(SubRace race, Gender gender) private string GetName(SubRace race, Gender gender)
=> gender switch => gender switch
{ {
Gender.Male => _tribeSheet.GetRow((uint)race)?.Masculine.ToDalamudString().TextValue ?? race.ToName(), Gender.Male => _tribeSheet.TryGetRow((uint)race, out var row) ? row.Masculine.ExtractText() : race.ToName(),
Gender.Female => _tribeSheet.GetRow((uint)race)?.Feminine.ToDalamudString().TextValue ?? race.ToName(), Gender.Female => _tribeSheet.TryGetRow((uint)race, out var row) ? row.Feminine.ExtractText() : race.ToName(),
_ => "Unknown", _ => "Unknown",
}; };
/// <summary> Obtain available hairstyles via reflection from the Hair sheet for the given subrace and gender. </summary> /// <summary> Obtain available hairstyles via reflection from the Hair sheet for the given subrace and gender. </summary>
private CustomizeData[] 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<CustomizeData>(row.Unknown30); var numHairs = row.ReadUInt8Column(30);
var hairList = new List<CustomizeData>(numHairs);
// 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 < numHairs; ++i)
{ {
var name = $"Unknown{66 + i * 9}"; // Hairs start at Unknown66.
var customizeIdx = (uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row) var customizeIdx = row.ReadUInt32Column(66 + i * 9);
?? uint.MaxValue;
if (customizeIdx == uint.MaxValue) if (customizeIdx == uint.MaxValue)
continue; continue;
// 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); if (_customizeSheet.TryGetRow(customizeIdx, out var hairRow))
if (hairRow == null)
hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx)); hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx));
else if (_icons.IconExists(hairRow.Icon)) else if (_icons.IconExists(hairRow.Icon))
hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, 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> /// <summary> Specific icons for tails or ears. </summary>
private CustomizeData[] GetTailEarShapes(CharaMakeParams row) private CustomizeData[] GetTailEarShapes(CharaMakeType row)
=> row.Menus.Cast<CharaMakeParams.Menu?>() => ExtractValues(row, CustomizeIndex.TailShape);
.FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.TailShape.ToByteAndMask().ByteIdx)?.Values
.Select((v, i) => FromValueAndIndex(CustomizeIndex.TailShape, v, i)).ToArray()
?? [];
/// <summary> Specific icons for faces. </summary> /// <summary> Specific icons for faces. </summary>
private CustomizeData[] GetFaces(CharaMakeParams row) private CustomizeData[] GetFaces(CharaMakeType row)
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.Face.ToByteAndMask().ByteIdx) => ExtractValues(row, CustomizeIndex.Face);
?.Values
.Select((v, i) => FromValueAndIndex(CustomizeIndex.Face, v, i)).ToArray()
?? [];
/// <summary> Specific icons for Hrothgar patterns. </summary> /// <summary> Specific icons for Hrothgar patterns. </summary>
private CustomizeData[] HrothgarFurPattern(CharaMakeParams row) private CustomizeData[] HrothgarFurPattern(CharaMakeType row)
=> row.Menus.Cast<CharaMakeParams.Menu?>() => ExtractValues(row, CustomizeIndex.LipColor);
.FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.LipColor.ToByteAndMask().ByteIdx)?.Values
.Select((v, i) => FromValueAndIndex(CustomizeIndex.LipColor, v, i)).ToArray() 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> /// <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) 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<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) var numPaints = row.ReadUInt8Column(37);
var paintList = new List<CustomizeData>(numPaints);
for (var i = 0; i < numPaints; ++i)
{ {
// Face paints start at Unknown73. // Face paints start at Unknown73.
var name = $"Unknown{73 + i * 9}"; var customizeIdx = row.ReadUInt32Column(73 + i * 9);
var customizeIdx =
(uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
?? uint.MaxValue;
if (customizeIdx == uint.MaxValue) if (customizeIdx == uint.MaxValue)
continue; continue;
var paintRow = _customizeSheet.GetRow(customizeIdx);
// Face paint Row from CustomizeSheet might not be set in case of unlockable face paints. // 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, paintList.Add(new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)paintRow.FeatureID, paintRow.Icon,
(ushort)paintRow.RowId)); (ushort)paintRow.RowId));
else else
@ -232,21 +221,18 @@ internal class CustomizeSetFactory(
} }
/// <summary> Get List sizes. </summary> /// <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 gameId = index.ToByteAndMask().ByteIdx;
var menu = row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customize == gameId); var menu = row.CharaMakeStruct.FirstOrNull(m => m.Customize == gameId);
return menu?.Size ?? 0; return menu?.SubMenuNum ?? 0;
} }
/// <summary> Get generic Features. </summary> /// <summary> Get generic Features. </summary>
private CustomizeData FromValueAndIndex(CustomizeIndex id, uint value, int index) private CustomizeData FromValueAndIndex(CustomizeIndex id, uint value, int index)
{ => _customizeSheet.TryGetRow(value, out var row)
var row = _customizeSheet.GetRow(value); ? new CustomizeData(id, (CustomizeValue)row.FeatureID, row.Icon, (ushort)row.RowId)
return row == null : new CustomizeData(id, (CustomizeValue)(index + 1), value);
? new CustomizeData(id, (CustomizeValue)(index + 1), value)
: new CustomizeData(id, (CustomizeValue)row.FeatureID, row.Icon, (ushort)row.RowId);
}
/// <summary> Create generic color sets from the parameters. </summary> /// <summary> Create generic color sets from the parameters. </summary>
private static CustomizeData[] CreateColors(ColorParameters colorParameters, CustomizeIndex index, int offset, int num, 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> /// <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 => 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 byteId = c.ToByteAndMask().ByteIdx;
var menu = row.Menus var menu = row.CharaMakeStruct.FirstOrNull(m => m.Customize == byteId);
.Cast<CharaMakeParams.Menu?>()
.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 == CustomizeIndex.Highlights) 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. // Otherwise there is an error and we use the default name.
return c.ToDefaultName(); return c.ToDefaultName();
} }
// Otherwise all is normal, get the menu name or if it does not work the default name. // 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(_lobbySheet.TryGetRow(menu.Value.Menu.RowId, out var textRow)
return string.Intern(textRow?.Text.ToDalamudString().ToString() ?? c.ToDefaultName()); ? textRow.Text.ExtractText()
: c.ToDefaultName());
}).ToArray(); }).ToArray();
// Add names for both eye colors. // Add names for both eye colors.
@ -306,7 +291,7 @@ internal class CustomizeSetFactory(
} }
/// <summary> Get the manu types for all available options. </summary> /// <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. // Set up the menu types for all customizations.
return Enum.GetValues<CustomizeIndex>().Select(c => return Enum.GetValues<CustomizeIndex>().Select(c =>
@ -318,13 +303,13 @@ internal class CustomizeSetFactory(
case CustomizeIndex.EyeColorLeft: case CustomizeIndex.EyeColorLeft:
case CustomizeIndex.EyeColorRight: case CustomizeIndex.EyeColorRight:
case CustomizeIndex.FacePaintColor: case CustomizeIndex.FacePaintColor:
return CharaMakeParams.MenuType.ColorPicker; return MenuType.ColorPicker;
case CustomizeIndex.BodyType: return CharaMakeParams.MenuType.Nothing; case CustomizeIndex.BodyType: return MenuType.Nothing;
case CustomizeIndex.FacePaintReversed: case CustomizeIndex.FacePaintReversed:
case CustomizeIndex.Highlights: case CustomizeIndex.Highlights:
case CustomizeIndex.SmallIris: case CustomizeIndex.SmallIris:
case CustomizeIndex.Lipstick: case CustomizeIndex.Lipstick:
return CharaMakeParams.MenuType.Checkmark; return MenuType.Checkmark;
case CustomizeIndex.FacialFeature1: case CustomizeIndex.FacialFeature1:
case CustomizeIndex.FacialFeature2: case CustomizeIndex.FacialFeature2:
case CustomizeIndex.FacialFeature3: case CustomizeIndex.FacialFeature3:
@ -333,24 +318,22 @@ internal class CustomizeSetFactory(
case CustomizeIndex.FacialFeature6: case CustomizeIndex.FacialFeature6:
case CustomizeIndex.FacialFeature7: case CustomizeIndex.FacialFeature7:
case CustomizeIndex.LegacyTattoo: case CustomizeIndex.LegacyTattoo:
return CharaMakeParams.MenuType.IconCheckmark; return MenuType.IconCheckmark;
} }
var gameId = c.ToByteAndMask().ByteIdx; 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.CharaMakeStruct.FirstOrNull(m => m.Customize == gameId);
.Cast<CharaMakeParams.Menu?>() var ret = (MenuType)(menu?.SubMenuType ?? (byte)MenuType.ListSelector);
.FirstOrDefault(m => m!.Value.Customize == gameId); if (c is CustomizeIndex.TailShape && ret is MenuType.ListSelector)
var ret = menu?.Type ?? CharaMakeParams.MenuType.ListSelector; ret = MenuType.List1Selector;
if (c is CustomizeIndex.TailShape && ret is CharaMakeParams.MenuType.ListSelector)
ret = CharaMakeParams.MenuType.List1Selector;
return ret; return ret;
}).ToArray(); }).ToArray();
} }
/// <summary> Set the availability of options according to actual availability. </summary> /// <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(true, CustomizeIndex.Height);
Set(set.Faces.Count > 0, CustomizeIndex.Face); Set(set.Faces.Count > 0, CustomizeIndex.Face);
@ -401,7 +384,7 @@ internal class CustomizeSetFactory(
ret[(int)CustomizeIndex.EyeColorRight] = CustomizeIndex.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<MenuType>())
dict.TryAdd(type, []); dict.TryAdd(type, []);
set.Order = dict; set.Order = dict;
} }
@ -425,7 +408,7 @@ internal class CustomizeSetFactory(
bool Valid(CustomizeData c) 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; 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. /// 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. /// Facial Features are bools in a bitfield, so we supply an "off" and an "on" value for simplicity of use.
/// </summary> /// </summary>
private static void SetFacialFeatures(CustomizeSet set, CharaMakeParams row) private static void SetFacialFeatures(CustomizeSet set, in CharaMakeType row)
{ {
var count = set.Faces.Count; var count = set.Faces.Count;
set.FacialFeature1 = new List<(CustomizeData, CustomizeData)>(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(); 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 data = row.FacialFeatureByFace[i].Icons; var data = row.FacialFeatureOption[i];
tmp[0][i + 1] = Create(CustomizeIndex.FacialFeature1, data[0]); tmp[0][i + 1] = Create(CustomizeIndex.FacialFeature1, (uint)data.Option1);
tmp[1][i + 1] = Create(CustomizeIndex.FacialFeature2, data[1]); tmp[1][i + 1] = Create(CustomizeIndex.FacialFeature2, (uint)data.Option2);
tmp[2][i + 1] = Create(CustomizeIndex.FacialFeature3, data[2]); tmp[2][i + 1] = Create(CustomizeIndex.FacialFeature3, (uint)data.Option3);
tmp[3][i + 1] = Create(CustomizeIndex.FacialFeature4, data[3]); tmp[3][i + 1] = Create(CustomizeIndex.FacialFeature4, (uint)data.Option4);
tmp[4][i + 1] = Create(CustomizeIndex.FacialFeature5, data[4]); tmp[4][i + 1] = Create(CustomizeIndex.FacialFeature5, (uint)data.Option5);
tmp[5][i + 1] = Create(CustomizeIndex.FacialFeature6, data[5]); tmp[5][i + 1] = Create(CustomizeIndex.FacialFeature6, (uint)data.Option6);
tmp[6][i + 1] = Create(CustomizeIndex.FacialFeature7, data[6]); tmp[6][i + 1] = Create(CustomizeIndex.FacialFeature7, (uint)data.Option7);
} }
set.FacialFeature1 = tmp[0]; 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.Plugin.Services;
using Dalamud.Utility; using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
using OtterGui.Services; using OtterGui.Services;
using Penumbra.GameData.DataContainers; using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
@ -52,15 +52,14 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
/// <summary> Create data from event NPCs. </summary> /// <summary> Create data from event NPCs. </summary>
private static List<NpcData> CreateEnpcData(IDataManager data, DictENpc eNpcs) 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); var list = new List<NpcData>(eNpcs.Count);
// Go through all event NPCs already collected into a dictionary. // Go through all event NPCs already collected into a dictionary.
foreach (var (id, name) in eNpcs) foreach (var (id, name) in eNpcs)
{ {
var row = enpcSheet.GetRow(id.Id);
// We only accept NPCs with valid names. // We only accept NPCs with valid names.
if (row == null || name.IsNullOrWhitespace()) if (!enpcSheet.TryGetRow(id.Id, out var row) || name.IsNullOrWhitespace())
continue; continue;
// Check if the customization is a valid human. // Check if the customization is a valid human.
@ -72,14 +71,14 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
{ {
Name = name, Name = name,
Customize = customize, Customize = customize,
ModelId = row.ModelChara.Row, ModelId = row.ModelChara.RowId,
Id = id, Id = id,
Kind = ObjectKind.EventNpc, Kind = ObjectKind.EventNpc,
}; };
// Event NPCs have a reference to NpcEquip but also contain the appearance in their own row. // 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. // 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); ApplyNpcEquip(ref ret, equip);
else else
ApplyNpcEquip(ref ret, row); ApplyNpcEquip(ref ret, row);
@ -93,14 +92,14 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
/// <summary> Create data from battle NPCs. </summary> /// <summary> Create data from battle NPCs. </summary>
private static List<NpcData> CreateBnpcData(IDataManager data, DictBNpc bNpcs, DictBNpcNames bNpcNames) private static List<NpcData> CreateBnpcData(IDataManager data, DictBNpc bNpcs, DictBNpcNames bNpcNames)
{ {
var bnpcSheet = data.GetExcelSheet<BNpcBase>()!; var bnpcSheet = data.GetExcelSheet<BNpcBase>();
var list = new List<NpcData>((int)bnpcSheet.RowCount); var list = new List<NpcData>(bnpcSheet.Count);
// We go through all battle NPCs in the sheet because the dictionary refers to names. // We go through all battle NPCs in the sheet because the dictionary refers to names.
foreach (var baseRow in bnpcSheet) foreach (var baseRow in bnpcSheet)
{ {
// Only accept humans. // Only accept humans.
if (baseRow.ModelChara.Value!.Type != 1) if (baseRow.ModelChara.Value.Type != 1)
continue; continue;
var bnpcNameIds = bNpcNames[baseRow.RowId]; var bnpcNameIds = bNpcNames[baseRow.RowId];
@ -109,15 +108,15 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
continue; continue;
// Check if the customization is a valid human. // Check if the customization is a valid human.
var (valid, customize) = FromBnpcCustomize(baseRow.BNpcCustomize.Value!); var (valid, customize) = FromBnpcCustomize(baseRow.BNpcCustomize.Value);
if (!valid) if (!valid)
continue; continue;
var equip = baseRow.NpcEquip.Value!; var equip = baseRow.NpcEquip.Value;
var ret = new NpcData var ret = new NpcData
{ {
Customize = customize, Customize = customize,
ModelId = baseRow.ModelChara.Row, ModelId = baseRow.ModelChara.RowId,
Id = baseRow.RowId, Id = baseRow.RowId,
Kind = ObjectKind.BattleNpc, Kind = ObjectKind.BattleNpc,
}; };
@ -186,36 +185,36 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
/// <summary> Apply equipment from a NpcEquip row. </summary> /// <summary> Apply equipment from a NpcEquip row. </summary>
private static void ApplyNpcEquip(ref NpcData data, NpcEquip row) 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(0, row.ModelHead | (row.DyeHead.RowId << 24) | ((ulong)row.Dye2Head.RowId << 32));
data.Set(1, row.ModelBody | (row.DyeBody.Row << 24) | ((ulong)row.Dye2Body.Row << 32)); data.Set(1, row.ModelBody | (row.DyeBody.RowId << 24) | ((ulong)row.Dye2Body.RowId << 32));
data.Set(2, row.ModelHands | (row.DyeHands.Row << 24) | ((ulong)row.Dye2Hands.Row << 32)); data.Set(2, row.ModelHands | (row.DyeHands.RowId << 24) | ((ulong)row.Dye2Hands.RowId << 32));
data.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24) | ((ulong)row.Dye2Legs.Row << 32)); data.Set(3, row.ModelLegs | (row.DyeLegs.RowId << 24) | ((ulong)row.Dye2Legs.RowId << 32));
data.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24) | ((ulong)row.Dye2Feet.Row << 32)); data.Set(4, row.ModelFeet | (row.DyeFeet.RowId << 24) | ((ulong)row.Dye2Feet.RowId << 32));
data.Set(5, row.ModelEars | (row.DyeEars.Row << 24) | ((ulong)row.Dye2Ears.Row << 32)); data.Set(5, row.ModelEars | (row.DyeEars.RowId << 24) | ((ulong)row.Dye2Ears.RowId << 32));
data.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24) | ((ulong)row.Dye2Neck.Row << 32)); data.Set(6, row.ModelNeck | (row.DyeNeck.RowId << 24) | ((ulong)row.Dye2Neck.RowId << 32));
data.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24) | ((ulong)row.Dye2Wrists.Row << 32)); data.Set(7, row.ModelWrists | (row.DyeWrists.RowId << 24) | ((ulong)row.Dye2Wrists.RowId << 32));
data.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24) | ((ulong)row.Dye2RightRing.Row << 32)); data.Set(8, row.ModelRightRing | (row.DyeRightRing.RowId << 24) | ((ulong)row.Dye2RightRing.RowId << 32));
data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24) | ((ulong)row.Dye2LeftRing.Row << 32)); data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.RowId << 24) | ((ulong)row.Dye2LeftRing.RowId << 32));
data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48) | ((ulong)row.Dye2MainHand.Row << 56)); 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.Row << 48) | ((ulong)row.Dye2OffHand.Row << 56)); data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.RowId << 48) | ((ulong)row.Dye2OffHand.RowId << 56));
data.VisorToggled = row.Visor; data.VisorToggled = row.Visor;
} }
/// <summary> Apply equipment from a ENpcBase Row row. </summary> /// <summary> Apply equipment from a ENpcBase Row row. </summary>
private static void ApplyNpcEquip(ref NpcData data, ENpcBase row) 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(0, row.ModelHead | (row.DyeHead.RowId << 24) | ((ulong)row.Dye2Head.RowId << 32));
data.Set(1, row.ModelBody | (row.DyeBody.Row << 24) | ((ulong)row.Dye2Body.Row << 32)); data.Set(1, row.ModelBody | (row.DyeBody.RowId << 24) | ((ulong)row.Dye2Body.RowId << 32));
data.Set(2, row.ModelHands | (row.DyeHands.Row << 24) | ((ulong)row.Dye2Hands.Row << 32)); data.Set(2, row.ModelHands | (row.DyeHands.RowId << 24) | ((ulong)row.Dye2Hands.RowId << 32));
data.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24) | ((ulong)row.Dye2Legs.Row << 32)); data.Set(3, row.ModelLegs | (row.DyeLegs.RowId << 24) | ((ulong)row.Dye2Legs.RowId << 32));
data.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24) | ((ulong)row.Dye2Feet.Row << 32)); data.Set(4, row.ModelFeet | (row.DyeFeet.RowId << 24) | ((ulong)row.Dye2Feet.RowId << 32));
data.Set(5, row.ModelEars | (row.DyeEars.Row << 24) | ((ulong)row.Dye2Ears.Row << 32)); data.Set(5, row.ModelEars | (row.DyeEars.RowId << 24) | ((ulong)row.Dye2Ears.RowId << 32));
data.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24) | ((ulong)row.Dye2Neck.Row << 32)); data.Set(6, row.ModelNeck | (row.DyeNeck.RowId << 24) | ((ulong)row.Dye2Neck.RowId << 32));
data.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24) | ((ulong)row.Dye2Wrists.Row << 32)); data.Set(7, row.ModelWrists | (row.DyeWrists.RowId << 24) | ((ulong)row.Dye2Wrists.RowId << 32));
data.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24) | ((ulong)row.Dye2RightRing.Row << 32)); data.Set(8, row.ModelRightRing | (row.DyeRightRing.RowId << 24) | ((ulong)row.Dye2RightRing.RowId << 32));
data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24) | ((ulong)row.Dye2LeftRing.Row << 32)); data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.RowId << 24) | ((ulong)row.Dye2LeftRing.RowId << 32));
data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48) | ((ulong)row.Dye2MainHand.Row << 56)); 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.Row << 48) | ((ulong)row.Dye2OffHand.Row << 56)); data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.RowId << 48) | ((ulong)row.Dye2OffHand.RowId << 56));
data.VisorToggled = row.Visor; data.VisorToggled = row.Visor;
} }
@ -223,11 +222,11 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
private static (bool, CustomizeArray) FromBnpcCustomize(BNpcCustomize bnpcCustomize) private static (bool, CustomizeArray) FromBnpcCustomize(BNpcCustomize bnpcCustomize)
{ {
var customize = new CustomizeArray(); 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(1, (CustomizeValue)bnpcCustomize.Gender);
customize.SetByIndex(2, (CustomizeValue)bnpcCustomize.BodyType); customize.SetByIndex(2, (CustomizeValue)bnpcCustomize.BodyType);
customize.SetByIndex(3, (CustomizeValue)bnpcCustomize.Height); 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(5, (CustomizeValue)bnpcCustomize.Face);
customize.SetByIndex(6, (CustomizeValue)bnpcCustomize.HairStyle); customize.SetByIndex(6, (CustomizeValue)bnpcCustomize.HairStyle);
customize.SetByIndex(7, (CustomizeValue)bnpcCustomize.HairHighlight); 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> /// <summary> Obtain customizations from a ENpcBase row and check if the human is valid. </summary>
private static (bool, CustomizeArray) FromEnpcBase(ENpcBase enpcBase) private static (bool, CustomizeArray) FromEnpcBase(ENpcBase enpcBase)
{ {
if (enpcBase.ModelChara.Value?.Type != 1) if (enpcBase.ModelChara.ValueNullable?.Type != 1)
return (false, CustomizeArray.Default); return (false, CustomizeArray.Default);
var customize = new CustomizeArray(); 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(1, (CustomizeValue)enpcBase.Gender);
customize.SetByIndex(2, (CustomizeValue)enpcBase.BodyType); customize.SetByIndex(2, (CustomizeValue)enpcBase.BodyType);
customize.SetByIndex(3, (CustomizeValue)enpcBase.Height); 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(5, (CustomizeValue)enpcBase.Face);
customize.SetByIndex(6, (CustomizeValue)enpcBase.HairStyle); customize.SetByIndex(6, (CustomizeValue)enpcBase.HairStyle);
customize.SetByIndex(7, (CustomizeValue)enpcBase.HairHighlight); customize.SetByIndex(7, (CustomizeValue)enpcBase.HairHighlight);

View file

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

View file

@ -193,7 +193,7 @@ public partial class CustomizationDrawer
private void DrawMultiIcons() private void DrawMultiIcons()
{ {
var options = _set.Order[CharaMakeParams.MenuType.IconCheckmark]; var options = _set.Order[MenuType.IconCheckmark];
using var group = ImRaii.Group(); using var group = ImRaii.Group();
var face = _set.DataByValue(CustomizeIndex.Face, _customize.Face, out _, _customize.Face) < 0 ? _set.Faces[0].Value : _customize.Face; 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()) 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); _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); PercentageSelector(id);
Functions.IteratePairwise(_set.Order[CharaMakeParams.MenuType.IconSelector], DrawIconSelector, ImGui.SameLine); Functions.IteratePairwise(_set.Order[MenuType.IconSelector], DrawIconSelector, ImGui.SameLine);
DrawMultiIconSelector(); DrawMultiIconSelector();
foreach (var id in _set.Order[CharaMakeParams.MenuType.ListSelector]) foreach (var id in _set.Order[MenuType.ListSelector])
DrawListSelector(id, false); DrawListSelector(id, false);
foreach (var id in _set.Order[CharaMakeParams.MenuType.List1Selector]) foreach (var id in _set.Order[MenuType.List1Selector])
DrawListSelector(id, true); 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)); () => ImGui.SameLine(_comboSelectorSize - _framedIconSize.X + ImGui.GetStyle().WindowPadding.X));
return Changed != 0 || ChangeApply != _initialApply; return Changed != 0 || ChangeApply != _initialApply;
} }

View file

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

View file

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

View file

@ -40,7 +40,7 @@ public class UnlockOverview(
foreach (var type in Enum.GetValues<FullEquipType>()) 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; continue;
if (ImGui.Selectable(type.ToName(), _selected1 == type)) 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; flags &= CrestExtensions.AllRelevant;
var currentCrests = gameObject.CrestBitfield; var currentCrests = gameObject.CrestBitfield;
using var update = _inUpdate.EnterMethod(); using var update = _inUpdate.EnterMethod();
_crestChangeHook.Original(gameObject.AsCharacter, (byte) flags); _crestChangeHook.Original(&gameObject.AsCharacter->DrawData, (byte) flags);
gameObject.CrestBitfield = currentCrests; gameObject.CrestBitfield = currentCrests;
} }
@ -62,14 +62,14 @@ public sealed unsafe class CrestService : EventWrapperRef3<Actor, CrestFlag, boo
_crestChangeHook.Dispose(); _crestChangeHook.Dispose();
} }
private delegate void CrestChangeDelegate(Character* character, byte crestFlags); private delegate void CrestChangeDelegate(DrawDataContainer* container, byte crestFlags);
[Signature(Sigs.CrestChange, DetourName = nameof(CrestChangeDetour))] [Signature(Sigs.CrestChange, DetourName = nameof(CrestChangeDetour))]
private readonly Hook<CrestChangeDelegate> _crestChangeHook = null!; 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) foreach (var slot in CrestExtensions.AllRelevantSet)
{ {
var newValue = ((CrestFlag)crestFlags).HasFlag(slot); var newValue = ((CrestFlag)crestFlags).HasFlag(slot);
@ -78,9 +78,9 @@ public sealed unsafe class CrestService : EventWrapperRef3<Actor, CrestFlag, boo
} }
Glamourer.Log.Verbose( 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(); using var _ = _inUpdate.EnterMethod();
_crestChangeHook.Original(character, crestFlags); _crestChangeHook.Original(container, crestFlags);
} }
public static bool GetModelCrest(Actor gameObject, CrestFlag slot) 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 FFXIVClientStructs.FFXIV.Client.Game.Character;
using Penumbra.GameData; using Penumbra.GameData;
using Penumbra.GameData.Interop; using Penumbra.GameData.Interop;
using Penumbra.GameData.Structs;
using System.ComponentModel;
using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character; using Character = FFXIVClientStructs.FFXIV.Client.Game.Character.Character;
namespace Glamourer.Interop; namespace Glamourer.Interop;
@ -16,12 +18,11 @@ public unsafe class ScalingService : IDisposable
interop.InitializeFromAttributes(this); interop.InitializeFromAttributes(this);
_setupMountHook = _setupMountHook =
interop.HookFromAddress<SetupMount>((nint)MountContainer.MemberFunctionPointers.SetupMount, SetupMountDetour); interop.HookFromAddress<SetupMount>((nint)MountContainer.MemberFunctionPointers.SetupMount, SetupMountDetour);
_setupOrnamentHook = interop.HookFromAddress<SetupOrnament>((nint)Ornament.MemberFunctionPointers.SetupOrnament, SetupOrnamentDetour);
_calculateHeightHook = _calculateHeightHook =
interop.HookFromAddress<CalculateHeight>((nint)Character.MemberFunctionPointers.CalculateHeight, CalculateHeightDetour); interop.HookFromAddress<CalculateHeight>((nint)HeightContainer.MemberFunctionPointers.CalculateHeight, CalculateHeightDetour);
_setupMountHook.Enable(); _setupMountHook.Enable();
_setupOrnamentHook.Enable(); _updateOrnamentHook.Enable();
_placeMinionHook.Enable(); _placeMinionHook.Enable();
_calculateHeightHook.Enable(); _calculateHeightHook.Enable();
} }
@ -29,19 +30,21 @@ public unsafe class ScalingService : IDisposable
public void Dispose() public void Dispose()
{ {
_setupMountHook.Dispose(); _setupMountHook.Dispose();
_setupOrnamentHook.Dispose(); _updateOrnamentHook.Dispose();
_placeMinionHook.Dispose(); _placeMinionHook.Dispose();
_calculateHeightHook.Dispose(); _calculateHeightHook.Dispose();
} }
private delegate void SetupMount(MountContainer* container, short mountId, uint unk1, uint unk2, uint unk3, byte unk4); 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 void PlaceMinion(Companion* character);
private delegate float CalculateHeight(Character* character); private delegate float CalculateHeight(Character* character);
private readonly Hook<SetupMount> _setupMountHook; 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; private readonly Hook<CalculateHeight> _calculateHeightHook;
@ -57,19 +60,12 @@ public unsafe class ScalingService : IDisposable
SetScaleCustomize(container->OwnerObject, race, clan, gender); SetScaleCustomize(container->OwnerObject, race, clan, gender);
} }
private void SetupOrnamentDetour(Ornament* ornament, uint* unk1, float* unk2) private void UpdateOrnamentDetour(OrnamentContainer* container)
{ {
var character = ornament->GetParentCharacter(); var (race, clan, gender) = GetScaleRelevantCustomize(container->OwnerObject);
if (character == null) SetScaleCustomize(container->OwnerObject, container->OwnerObject->DrawObject);
{ _updateOrnamentHook.Original(container);
_setupOrnamentHook.Original(ornament, unk1, unk2); SetScaleCustomize(container->OwnerObject, race, clan, gender);
return;
}
var (race, clan, gender) = GetScaleRelevantCustomize(character);
SetScaleCustomize(character, character->GameObject.DrawObject);
_setupOrnamentHook.Original(ornament, unk1, unk2);
SetScaleCustomize(character, race, clan, gender);
} }
private void PlaceMinionDetour(Companion* companion) private void PlaceMinionDetour(Companion* companion)

View file

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

View file

@ -1,5 +1,6 @@
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using Lumina.Excel; using Lumina.Excel;
using Lumina.Excel.Sheets;
using Penumbra.GameData.Data; using Penumbra.GameData.Data;
using Penumbra.GameData.DataContainers; using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
@ -16,12 +17,12 @@ public class ItemManager
private readonly Configuration _config; private readonly Configuration _config;
public readonly ObjectIdentification ObjectIdentification; public readonly ObjectIdentification ObjectIdentification;
public readonly ExcelSheet<Lumina.Excel.GeneratedSheets.Item> ItemSheet; public readonly ExcelSheet<Item> ItemSheet;
public readonly DictStain Stains; public readonly DictStain Stains;
public readonly ItemData ItemData; public readonly ItemData ItemData;
public readonly DictBonusItems DictBonusItems; public readonly DictBonusItems DictBonusItems;
public readonly RestrictedGear RestrictedGear; public readonly RestrictedGear RestrictedGear;
public readonly EquipItem DefaultSword; public readonly EquipItem DefaultSword;
@ -29,13 +30,13 @@ public class ItemManager
ItemData itemData, DictStain stains, RestrictedGear restrictedGear, DictBonusItems dictBonusItems) ItemData itemData, DictStain stains, RestrictedGear restrictedGear, DictBonusItems dictBonusItems)
{ {
_config = config; _config = config;
ItemSheet = gameData.GetExcelSheet<Lumina.Excel.GeneratedSheets.Item>()!; ItemSheet = gameData.GetExcelSheet<Item>();
ObjectIdentification = objectIdentification; ObjectIdentification = objectIdentification;
ItemData = itemData; ItemData = itemData;
Stains = stains; Stains = stains;
RestrictedGear = restrictedGear; RestrictedGear = restrictedGear;
DictBonusItems = dictBonusItems; 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) 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.Events;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.Services; using Glamourer.Services;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
using Penumbra.GameData; using Penumbra.GameData;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
@ -183,25 +183,25 @@ public class CustomizeUnlockManager : IDisposable, ISavable
var list = customizations.Manager.GetSet(clan, gender); var list = customizations.Manager.GetSet(clan, gender);
foreach (var hair in list.HairStyles) 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) if (x?.IsPurchasable == true)
{ {
var name = x.FeatureID == 61 var name = x.Value.FeatureID == 61
? "Eternal Bond" ? "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; ?? string.Empty;
ret.TryAdd(hair, (x.Data, name)); ret.TryAdd(hair, (x.Value.Data, name));
} }
} }
foreach (var paint in list.FacePaints) 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) 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; ?? 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 FFXIVClientStructs.FFXIV.Client.Game.UI;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Services; using Glamourer.Services;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.Sheets;
using Penumbra.GameData.Data; using Penumbra.GameData.Data;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using Cabinet = Lumina.Excel.GeneratedSheets.Cabinet; using Cabinet = Lumina.Excel.Sheets.Cabinet;
namespace Glamourer.Unlocks; namespace Glamourer.Unlocks;
@ -192,7 +192,7 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary<Item
public bool IsUnlocked(CustomItemId itemId, out DateTimeOffset time) public bool IsUnlocked(CustomItemId itemId, out DateTimeOffset time)
{ {
// Pseudo items are always unlocked. // Pseudo items are always unlocked.
if (itemId.Id >= _items.ItemSheet.RowCount) if (itemId.Id >= (uint) _items.ItemSheet.Count)
{ {
time = DateTimeOffset.MinValue; time = DateTimeOffset.MinValue;
return true; return true;
@ -273,32 +273,31 @@ public class ItemUnlockManager : ISavable, IDisposable, IReadOnlyDictionary<Item
private static Dictionary<ItemId, UnlockRequirements> CreateUnlockData(IDataManager gameData, ItemManager items) private static Dictionary<ItemId, UnlockRequirements> CreateUnlockData(IDataManager gameData, ItemManager items)
{ {
var ret = new Dictionary<ItemId, UnlockRequirements>(); var ret = new Dictionary<ItemId, UnlockRequirements>();
var cabinet = gameData.GetExcelSheet<Cabinet>()!; var cabinet = gameData.GetExcelSheet<Cabinet>();
foreach (var row in 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)); ret.TryAdd(item.ItemId, new UnlockRequirements(row.RowId, 0, 0, 0, UnlockType.Cabinet));
} }
var gilShopItem = gameData.GetExcelSheet<GilShopItem>()!; var gilShopItem = gameData.GetSubrowExcelSheet<GilShopItem>();
var gilShop = gameData.GetExcelSheet<GilShop>()!; var gilShop = gameData.GetExcelSheet<GilShop>();
foreach (var row in gilShopItem) 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; continue;
var quest1 = row.QuestRequired[0].Row; var quest1 = row.QuestRequired[0].RowId;
var quest2 = row.QuestRequired[1].Row; var quest2 = row.QuestRequired[1].RowId;
var achievement = row.AchievementRequired.Row; var achievement = row.AchievementRequired.RowId;
var state = row.StateRequired; var state = row.StateRequired;
var shop = gilShop.GetRow(row.RowId); if (gilShop.TryGetRow(row.RowId, out var shop) && shop.Quest.RowId != 0)
if (shop != null && shop.Quest.Row != 0)
{ {
if (quest1 == 0) if (quest1 == 0)
quest1 = shop.Quest.Row; quest1 = shop.Quest.RowId;
else if (quest2 == 0) else if (quest2 == 0)
quest2 = shop.Quest.Row; quest2 = shop.Quest.RowId;
} }
var type = (quest1 != 0 ? UnlockType.Quest1 : 0) 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", "RepoUrl": "https://github.com/Ottermandias/Glamourer",
"ApplicableVersion": "any", "ApplicableVersion": "any",
"DalamudApiLevel": 10, "DalamudApiLevel": 10,
"TestingDalamudApiLevel": 10, "TestingDalamudApiLevel": 11,
"IsHide": "False", "IsHide": "False",
"IsTestingExclusive": "False", "IsTestingExclusive": "False",
"DownloadCount": 1, "DownloadCount": 1,