mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-13 12:14:18 +01:00
Current State.
This commit is contained in:
parent
a5998b84ba
commit
2ce8076e9a
20 changed files with 226 additions and 359 deletions
|
|
@ -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),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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];
|
||||||
|
|
|
||||||
14
Glamourer/GameData/MenuType.cs
Normal file
14
Glamourer/GameData/MenuType.cs
Normal 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
|
||||||
|
}
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -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"
|
||||||
}
|
}
|
||||||
|
|
@ -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())
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
2
OtterGui
2
OtterGui
|
|
@ -1 +1 @@
|
||||||
Subproject commit d9486ae54b5a4b61cf74f79ed27daa659eb1ce5b
|
Subproject commit 8ba88eff15326bb28ed5e6157f5252c114d40b5f
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 77d52b02d21e770b30c08f89bdf06e0cb75562f7
|
Subproject commit fb81a0b55d3c68f2b26357fac3049c79fb0c22fb
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue