mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +01:00
Starting rework.
This commit is contained in:
parent
0fc8992271
commit
7af38aa2ce
58 changed files with 8857 additions and 4923 deletions
|
|
@ -4,6 +4,7 @@ using Lumina.Excel.GeneratedSheets;
|
|||
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
// A custom version of CharaMakeParams that is easier to parse.
|
||||
[Sheet("CharaMakeParams")]
|
||||
public class CharaMakeParams : ExcelRow
|
||||
{
|
||||
|
|
@ -43,10 +44,9 @@ public class CharaMakeParams : ExcelRow
|
|||
public uint[] Icons;
|
||||
}
|
||||
|
||||
public LazyRow<Race> Race { get; set; } = null!;
|
||||
public LazyRow<Tribe> Tribe { get; set; } = null!;
|
||||
|
||||
public sbyte Gender { get; set; }
|
||||
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];
|
||||
|
|
@ -60,15 +60,17 @@ public class CharaMakeParams : ExcelRow
|
|||
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);
|
||||
var currentOffset = 0;
|
||||
for (var i = 0; i < NumMenus; ++i)
|
||||
{
|
||||
Menus[i].Id = parser.ReadColumn<uint>(3 + 0 * NumMenus + i);
|
||||
Menus[i].InitVal = parser.ReadColumn<byte>(3 + 1 * NumMenus + i);
|
||||
Menus[i].Type = (MenuType)parser.ReadColumn<byte>(3 + 2 * NumMenus + i);
|
||||
Menus[i].Size = parser.ReadColumn<byte>(3 + 3 * NumMenus + i);
|
||||
Menus[i].LookAt = parser.ReadColumn<byte>(3 + 4 * NumMenus + i);
|
||||
Menus[i].Mask = parser.ReadColumn<uint>(3 + 5 * NumMenus + i);
|
||||
Menus[i].Customization = (CustomizationId)parser.ReadColumn<uint>(3 + 6 * 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].Customization = (CustomizationId)parser.ReadColumn<uint>(6 * NumMenus + currentOffset);
|
||||
Menus[i].Values = new uint[Menus[i].Size];
|
||||
|
||||
switch (Menus[i].Type)
|
||||
|
|
@ -78,47 +80,42 @@ public class CharaMakeParams : ExcelRow
|
|||
case MenuType.Percentage:
|
||||
break;
|
||||
default:
|
||||
currentOffset += 7 * NumMenus;
|
||||
for (var j = 0; j < Menus[i].Size; ++j)
|
||||
Menus[i].Values[j] = parser.ReadColumn<uint>(3 + (7 + j) * NumMenus + i);
|
||||
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>(3 + (MaxNumValues + 7 + j) * NumMenus + i);
|
||||
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>(3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + 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>(3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + j * NumFaces + i);
|
||||
}
|
||||
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.UnkData3347Obj()
|
||||
{
|
||||
Helmet = parser.ReadColumn<ulong>(
|
||||
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 0),
|
||||
Top = parser.ReadColumn<ulong>(
|
||||
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 1),
|
||||
Gloves = parser.ReadColumn<ulong>(
|
||||
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 2),
|
||||
Legs = parser.ReadColumn<ulong>(
|
||||
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 3),
|
||||
Shoes = parser.ReadColumn<ulong>(
|
||||
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 4),
|
||||
Weapon = parser.ReadColumn<ulong>(
|
||||
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 5),
|
||||
SubWeapon = parser.ReadColumn<ulong>(
|
||||
3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7 + 6),
|
||||
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,23 +1,46 @@
|
|||
using Dalamud.Data;
|
||||
using Dalamud.Plugin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Logging;
|
||||
|
||||
namespace Glamourer;
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
public class CmpFile
|
||||
// Convert the Human.Cmp file into color sets.
|
||||
// If the file can not be read due to TexTools corruption, create a 0-array of size MinSize.
|
||||
internal class CmpFile
|
||||
{
|
||||
public readonly Lumina.Data.FileResource File;
|
||||
public readonly uint[] RgbaColors;
|
||||
private readonly Lumina.Data.FileResource? _file;
|
||||
private readonly uint[] _rgbaColors;
|
||||
|
||||
// No error checking since only called internally.
|
||||
public IEnumerable<uint> GetSlice(int offset, int count)
|
||||
=> _rgbaColors.Length >= offset + count ? _rgbaColors.Skip(offset).Take(count) : Enumerable.Repeat(0u, count);
|
||||
|
||||
public bool Valid
|
||||
=> _file != null;
|
||||
|
||||
public CmpFile(DataManager gameData)
|
||||
{
|
||||
File = gameData.GetFile("chara/xls/charamake/human.cmp")!;
|
||||
RgbaColors = new uint[File.Data.Length >> 2];
|
||||
for (var i = 0; i < File.Data.Length; i += 4)
|
||||
try
|
||||
{
|
||||
RgbaColors[i >> 2] = File.Data[i]
|
||||
| (uint)(File.Data[i + 1] << 8)
|
||||
| (uint)(File.Data[i + 2] << 16)
|
||||
| (uint)(File.Data[i + 3] << 24);
|
||||
_file = gameData.GetFile("chara/xls/charamake/human.cmp")!;
|
||||
_rgbaColors = new uint[_file.Data.Length >> 2];
|
||||
for (var i = 0; i < _file.Data.Length; i += 4)
|
||||
{
|
||||
_rgbaColors[i >> 2] = _file.Data[i]
|
||||
| (uint)(_file.Data[i + 1] << 8)
|
||||
| (uint)(_file.Data[i + 2] << 16)
|
||||
| (uint)(_file.Data[i + 3] << 24);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error("READ THIS\n======== Could not obtain the human.cmp file which is necessary for color sets.\n"
|
||||
+ "======== This usually indicates an error with your index files caused by TexTools modifications.\n"
|
||||
+ "======== If you have used TexTools before, you will probably need to start over in it to use Glamourer.", e);
|
||||
_file = null;
|
||||
_rgbaColors = Array.Empty<uint>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,47 +1,45 @@
|
|||
namespace Glamourer.Customization
|
||||
{
|
||||
public enum CustomName
|
||||
{
|
||||
Clan = 0,
|
||||
Gender,
|
||||
Reverse,
|
||||
OddEyes,
|
||||
IrisSmall,
|
||||
IrisLarge,
|
||||
IrisSize,
|
||||
MidlanderM,
|
||||
HighlanderM,
|
||||
WildwoodM,
|
||||
DuskwightM,
|
||||
PlainsfolkM,
|
||||
DunesfolkM,
|
||||
SeekerOfTheSunM,
|
||||
KeeperOfTheMoonM,
|
||||
SeawolfM,
|
||||
HellsguardM,
|
||||
RaenM,
|
||||
XaelaM,
|
||||
HelionM,
|
||||
LostM,
|
||||
RavaM,
|
||||
VeenaM,
|
||||
MidlanderF,
|
||||
HighlanderF,
|
||||
WildwoodF,
|
||||
DuskwightF,
|
||||
PlainsfolkF,
|
||||
DunesfolkF,
|
||||
SeekerOfTheSunF,
|
||||
KeeperOfTheMoonF,
|
||||
SeawolfF,
|
||||
HellsguardF,
|
||||
RaenF,
|
||||
XaelaF,
|
||||
HelionF,
|
||||
LostF,
|
||||
RavaF,
|
||||
VeenaF,
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
Num,
|
||||
}
|
||||
// Localization from the game files directly.
|
||||
public enum CustomName
|
||||
{
|
||||
Clan = 0,
|
||||
Gender,
|
||||
Reverse,
|
||||
OddEyes,
|
||||
IrisSmall,
|
||||
IrisLarge,
|
||||
IrisSize,
|
||||
MidlanderM,
|
||||
HighlanderM,
|
||||
WildwoodM,
|
||||
DuskwightM,
|
||||
PlainsfolkM,
|
||||
DunesfolkM,
|
||||
SeekerOfTheSunM,
|
||||
KeeperOfTheMoonM,
|
||||
SeawolfM,
|
||||
HellsguardM,
|
||||
RaenM,
|
||||
XaelaM,
|
||||
HelionM,
|
||||
LostM,
|
||||
RavaM,
|
||||
VeenaM,
|
||||
MidlanderF,
|
||||
HighlanderF,
|
||||
WildwoodF,
|
||||
DuskwightF,
|
||||
PlainsfolkF,
|
||||
DunesfolkF,
|
||||
SeekerOfTheSunF,
|
||||
KeeperOfTheMoonF,
|
||||
SeawolfF,
|
||||
HellsguardF,
|
||||
RaenF,
|
||||
XaelaF,
|
||||
HelionF,
|
||||
LostF,
|
||||
RavaF,
|
||||
VeenaF,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,33 @@
|
|||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Glamourer.Customization
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
// Any customization value can be represented in 8 bytes by its ID,
|
||||
// a byte value, an optional value-id and an optional icon or color.
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public readonly struct Customization
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public readonly struct Customization
|
||||
[FieldOffset(0)]
|
||||
public readonly CustomizationId Id;
|
||||
|
||||
[FieldOffset(1)]
|
||||
public readonly byte Value;
|
||||
|
||||
[FieldOffset(2)]
|
||||
public readonly ushort CustomizeId;
|
||||
|
||||
[FieldOffset(4)]
|
||||
public readonly uint IconId;
|
||||
|
||||
[FieldOffset(4)]
|
||||
public readonly uint Color;
|
||||
|
||||
public Customization(CustomizationId id, byte value, uint data = 0, ushort customizeId = 0)
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public readonly CustomizationId Id;
|
||||
|
||||
[FieldOffset(1)]
|
||||
public readonly byte Value;
|
||||
|
||||
[FieldOffset(2)]
|
||||
public readonly ushort CustomizeId;
|
||||
|
||||
[FieldOffset(4)]
|
||||
public readonly uint IconId;
|
||||
|
||||
[FieldOffset(4)]
|
||||
public readonly uint Color;
|
||||
|
||||
public Customization(CustomizationId id, byte value, uint data = 0, ushort customizeId = 0)
|
||||
{
|
||||
Id = id;
|
||||
Value = value;
|
||||
IconId = data;
|
||||
Color = data;
|
||||
CustomizeId = customizeId;
|
||||
}
|
||||
Id = id;
|
||||
Value = value;
|
||||
IconId = data;
|
||||
Color = data;
|
||||
CustomizeId = customizeId;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,59 +2,17 @@
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
public unsafe struct LazyCustomization
|
||||
{
|
||||
public CharacterCustomization* Address;
|
||||
|
||||
public LazyCustomization(IntPtr characterPtr)
|
||||
=> Address = (CharacterCustomization*)(characterPtr + CharacterCustomization.CustomizationOffset);
|
||||
|
||||
public ref CharacterCustomization Value
|
||||
=> ref *Address;
|
||||
|
||||
public LazyCustomization(CharacterCustomization data)
|
||||
=> Address = &data;
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public struct CharacterCustomization
|
||||
public struct CustomizationData
|
||||
{
|
||||
public const int CustomizationOffset = 0x830;
|
||||
public const int CustomizationBytes = 26;
|
||||
|
||||
public static CharacterCustomization Default = new()
|
||||
{
|
||||
Race = Race.Hyur,
|
||||
Gender = Gender.Male,
|
||||
BodyType = 1,
|
||||
Height = 50,
|
||||
Clan = SubRace.Midlander,
|
||||
Face = 1,
|
||||
Hairstyle = 1,
|
||||
HighlightsOn = false,
|
||||
SkinColor = 1,
|
||||
EyeColorRight = 1,
|
||||
HighlightsColor = 1,
|
||||
FacialFeatures = 0,
|
||||
TattooColor = 1,
|
||||
Eyebrow = 1,
|
||||
EyeColorLeft = 1,
|
||||
EyeShape = 1,
|
||||
Nose = 1,
|
||||
Jaw = 1,
|
||||
Mouth = 1,
|
||||
LipColor = 1,
|
||||
MuscleMass = 50,
|
||||
TailShape = 1,
|
||||
BustSize = 50,
|
||||
FacePaint = 1,
|
||||
FacePaintColor = 1,
|
||||
};
|
||||
|
||||
public Race Race;
|
||||
private byte _gender;
|
||||
public byte BodyType;
|
||||
|
|
@ -82,21 +40,25 @@ public struct CharacterCustomization
|
|||
private byte _facePaint;
|
||||
public byte FacePaintColor;
|
||||
|
||||
// Skip Unknown Gender
|
||||
public Gender Gender
|
||||
{
|
||||
get => (Gender)(_gender + 1);
|
||||
set => _gender = (byte)(value - 1);
|
||||
}
|
||||
|
||||
// Single bit flag.
|
||||
public bool HighlightsOn
|
||||
{
|
||||
get => (_highlightsOn & 128) == 128;
|
||||
set => _highlightsOn = (byte)(value ? _highlightsOn | 128 : _highlightsOn & 127);
|
||||
}
|
||||
|
||||
// Get status of specific facial feature 0-7.
|
||||
public bool FacialFeature(int idx)
|
||||
=> (FacialFeatures & (1 << idx)) != 0;
|
||||
|
||||
// Set value of specific facial feature 0-7.
|
||||
public void FacialFeature(int idx, bool set)
|
||||
{
|
||||
if (set)
|
||||
|
|
@ -105,66 +67,108 @@ public struct CharacterCustomization
|
|||
FacialFeatures &= (byte)~(1 << idx);
|
||||
}
|
||||
|
||||
// Lower 7 bits
|
||||
public byte EyeShape
|
||||
{
|
||||
get => (byte)(_eyeShape & 127);
|
||||
set => _eyeShape = (byte)((value & 127) | (_eyeShape & 128));
|
||||
}
|
||||
|
||||
// Uppermost bit flag.
|
||||
public bool SmallIris
|
||||
{
|
||||
get => (_eyeShape & 128) == 128;
|
||||
set => _eyeShape = (byte)(value ? _eyeShape | 128 : _eyeShape & 127);
|
||||
}
|
||||
|
||||
|
||||
// Lower 7 bits.
|
||||
public byte Mouth
|
||||
{
|
||||
get => (byte)(_mouth & 127);
|
||||
set => _mouth = (byte)((value & 127) | (_mouth & 128));
|
||||
}
|
||||
|
||||
// Uppermost bit flag.
|
||||
public bool Lipstick
|
||||
{
|
||||
get => (_mouth & 128) == 128;
|
||||
set => _mouth = (byte)(value ? _mouth | 128 : _mouth & 127);
|
||||
}
|
||||
|
||||
// Lower 7 bits.
|
||||
public byte FacePaint
|
||||
{
|
||||
get => (byte)(_facePaint & 127);
|
||||
set => _facePaint = (byte)((value & 127) | (_facePaint & 128));
|
||||
}
|
||||
|
||||
// Uppermost bit flag.
|
||||
public bool FacePaintReversed
|
||||
{
|
||||
get => (_facePaint & 128) == 128;
|
||||
set => _facePaint = (byte)(value ? _facePaint | 128 : _facePaint & 127);
|
||||
}
|
||||
|
||||
public unsafe void Read(IntPtr customizeAddress)
|
||||
public static CustomizationData Default = new()
|
||||
{
|
||||
fixed (Race* ptr = &Race)
|
||||
Race = Race.Hyur,
|
||||
Gender = Gender.Male,
|
||||
BodyType = 1,
|
||||
Height = 50,
|
||||
Clan = SubRace.Midlander,
|
||||
Face = 1,
|
||||
Hairstyle = 1,
|
||||
HighlightsOn = false,
|
||||
SkinColor = 1,
|
||||
EyeColorRight = 1,
|
||||
HighlightsColor = 1,
|
||||
FacialFeatures = 0,
|
||||
TattooColor = 1,
|
||||
Eyebrow = 1,
|
||||
EyeColorLeft = 1,
|
||||
EyeShape = 1,
|
||||
Nose = 1,
|
||||
Jaw = 1,
|
||||
Mouth = 1,
|
||||
LipColor = 1,
|
||||
MuscleMass = 50,
|
||||
TailShape = 1,
|
||||
BustSize = 50,
|
||||
FacePaint = 1,
|
||||
FacePaintColor = 1,
|
||||
};
|
||||
|
||||
public unsafe void Read(CustomizationData* customize)
|
||||
{
|
||||
fixed (CustomizationData* ptr = &this)
|
||||
{
|
||||
Buffer.MemoryCopy(customizeAddress.ToPointer(), ptr, CustomizationBytes, CustomizationBytes);
|
||||
*ptr = *customize;
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void Read(Customization* customize)
|
||||
=> Read((IntPtr)customize);
|
||||
public unsafe void Read(IntPtr customizeAddress)
|
||||
=> Read((CustomizationData*)customizeAddress);
|
||||
|
||||
public void Read(Character character)
|
||||
=> Read(character.Address + CustomizationOffset);
|
||||
|
||||
public CharacterCustomization(Character character)
|
||||
public unsafe void Read(Human* human)
|
||||
=> Read((CustomizationData*)human->CustomizeData);
|
||||
|
||||
public CustomizationData(Character character)
|
||||
: this()
|
||||
{
|
||||
Read(character.Address + CustomizationOffset);
|
||||
}
|
||||
|
||||
public byte this[CustomizationId id]
|
||||
public unsafe CustomizationData(Human* human)
|
||||
: this()
|
||||
{
|
||||
get => id switch
|
||||
Read(human);
|
||||
}
|
||||
|
||||
public byte Get(CustomizationId id)
|
||||
=> id switch
|
||||
{
|
||||
CustomizationId.Race => (byte)Race,
|
||||
CustomizationId.Gender => (byte)Gender,
|
||||
|
|
@ -194,100 +198,59 @@ public struct CharacterCustomization
|
|||
CustomizationId.FacePaintColor => FacePaintColor,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(id), id, null),
|
||||
};
|
||||
set
|
||||
|
||||
public void Set(CustomizationId id, byte value)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
case CustomizationId.Race:
|
||||
Race = (Race)value;
|
||||
break;
|
||||
case CustomizationId.Gender:
|
||||
Gender = (Gender)value;
|
||||
break;
|
||||
case CustomizationId.BodyType:
|
||||
BodyType = value;
|
||||
break;
|
||||
case CustomizationId.Height:
|
||||
Height = value;
|
||||
break;
|
||||
case CustomizationId.Clan:
|
||||
Clan = (SubRace)value;
|
||||
break;
|
||||
case CustomizationId.Face:
|
||||
Face = value;
|
||||
break;
|
||||
case CustomizationId.Hairstyle:
|
||||
Hairstyle = value;
|
||||
break;
|
||||
case CustomizationId.HighlightsOnFlag:
|
||||
HighlightsOn = (value & 128) == 128;
|
||||
break;
|
||||
case CustomizationId.SkinColor:
|
||||
SkinColor = value;
|
||||
break;
|
||||
case CustomizationId.EyeColorR:
|
||||
EyeColorRight = value;
|
||||
break;
|
||||
case CustomizationId.HairColor:
|
||||
HairColor = value;
|
||||
break;
|
||||
case CustomizationId.HighlightColor:
|
||||
HighlightsColor = value;
|
||||
break;
|
||||
case CustomizationId.FacialFeaturesTattoos:
|
||||
FacialFeatures = value;
|
||||
break;
|
||||
case CustomizationId.TattooColor:
|
||||
TattooColor = value;
|
||||
break;
|
||||
case CustomizationId.Eyebrows:
|
||||
Eyebrow = value;
|
||||
break;
|
||||
case CustomizationId.EyeColorL:
|
||||
EyeColorLeft = value;
|
||||
break;
|
||||
case CustomizationId.EyeShape:
|
||||
EyeShape = value;
|
||||
break;
|
||||
case CustomizationId.Nose:
|
||||
Nose = value;
|
||||
break;
|
||||
case CustomizationId.Jaw:
|
||||
Jaw = value;
|
||||
break;
|
||||
case CustomizationId.Mouth:
|
||||
Mouth = value;
|
||||
break;
|
||||
case CustomizationId.LipColor:
|
||||
LipColor = value;
|
||||
break;
|
||||
case CustomizationId.MuscleToneOrTailEarLength:
|
||||
MuscleMass = value;
|
||||
break;
|
||||
case CustomizationId.TailEarShape:
|
||||
TailShape = value;
|
||||
break;
|
||||
case CustomizationId.BustSize:
|
||||
BustSize = value;
|
||||
break;
|
||||
case CustomizationId.FacePaint:
|
||||
FacePaint = value;
|
||||
break;
|
||||
case CustomizationId.FacePaintColor:
|
||||
FacePaintColor = value;
|
||||
break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(id), id, null);
|
||||
}
|
||||
// @formatter:off
|
||||
case CustomizationId.Race: Race = (Race)value; break;
|
||||
case CustomizationId.Gender: Gender = (Gender)value; break;
|
||||
case CustomizationId.BodyType: BodyType = value; break;
|
||||
case CustomizationId.Height: Height = value; break;
|
||||
case CustomizationId.Clan: Clan = (SubRace)value; break;
|
||||
case CustomizationId.Face: Face = value; break;
|
||||
case CustomizationId.Hairstyle: Hairstyle = value; break;
|
||||
case CustomizationId.HighlightsOnFlag: HighlightsOn = (value & 128) == 128; break;
|
||||
case CustomizationId.SkinColor: SkinColor = value; break;
|
||||
case CustomizationId.EyeColorR: EyeColorRight = value; break;
|
||||
case CustomizationId.HairColor: HairColor = value; break;
|
||||
case CustomizationId.HighlightColor: HighlightsColor = value; break;
|
||||
case CustomizationId.FacialFeaturesTattoos: FacialFeatures = value; break;
|
||||
case CustomizationId.TattooColor: TattooColor = value; break;
|
||||
case CustomizationId.Eyebrows: Eyebrow = value; break;
|
||||
case CustomizationId.EyeColorL: EyeColorLeft = value; break;
|
||||
case CustomizationId.EyeShape: EyeShape = value; break;
|
||||
case CustomizationId.Nose: Nose = value; break;
|
||||
case CustomizationId.Jaw: Jaw = value; break;
|
||||
case CustomizationId.Mouth: Mouth = value; break;
|
||||
case CustomizationId.LipColor: LipColor = value; break;
|
||||
case CustomizationId.MuscleToneOrTailEarLength: MuscleMass = value; break;
|
||||
case CustomizationId.TailEarShape: TailShape = value; break;
|
||||
case CustomizationId.BustSize: BustSize = value; break;
|
||||
case CustomizationId.FacePaint: FacePaint = value; break;
|
||||
case CustomizationId.FacePaintColor: FacePaintColor = value; break;
|
||||
default: throw new ArgumentOutOfRangeException(nameof(id), id, null);
|
||||
// @formatter:on
|
||||
}
|
||||
}
|
||||
|
||||
public byte this[CustomizationId id]
|
||||
{
|
||||
get => Get(id);
|
||||
set => Set(id, value);
|
||||
}
|
||||
|
||||
public unsafe void Write(FFXIVClientStructs.FFXIV.Client.Game.Character.Character* character)
|
||||
{
|
||||
fixed (CustomizationData* ptr = &this)
|
||||
{
|
||||
Buffer.MemoryCopy(ptr, character->CustomizeData, CustomizationBytes, CustomizationBytes);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe void Write(IntPtr characterAddress)
|
||||
{
|
||||
fixed (Race* ptr = &Race)
|
||||
{
|
||||
Buffer.MemoryCopy(ptr, (byte*)characterAddress + CustomizationOffset, CustomizationBytes, CustomizationBytes);
|
||||
}
|
||||
}
|
||||
=> Write((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)characterAddress);
|
||||
|
||||
public unsafe void WriteBytes(byte[] array, int offset = 0)
|
||||
{
|
||||
|
|
@ -1,108 +1,107 @@
|
|||
using System;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Customization
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
public enum CustomizationId : byte
|
||||
{
|
||||
public enum CustomizationId : byte
|
||||
{
|
||||
Race = 0,
|
||||
Gender = 1,
|
||||
BodyType = 2,
|
||||
Height = 3,
|
||||
Clan = 4,
|
||||
Face = 5,
|
||||
Hairstyle = 6,
|
||||
HighlightsOnFlag = 7,
|
||||
SkinColor = 8,
|
||||
EyeColorR = 9,
|
||||
HairColor = 10,
|
||||
HighlightColor = 11,
|
||||
FacialFeaturesTattoos = 12, // Bitmask, 1-7 per face, 8 is 1.0 tattoo
|
||||
TattooColor = 13,
|
||||
Eyebrows = 14,
|
||||
EyeColorL = 15,
|
||||
EyeShape = 16, // Flag 128 for Small
|
||||
Nose = 17,
|
||||
Jaw = 18,
|
||||
Mouth = 19, // Flag 128 for Lip Color set
|
||||
LipColor = 20, // Flag 128 for Light instead of Dark
|
||||
MuscleToneOrTailEarLength = 21,
|
||||
TailEarShape = 22,
|
||||
BustSize = 23,
|
||||
FacePaint = 24,
|
||||
FacePaintColor = 25, // Flag 128 for Light instead of Dark.
|
||||
}
|
||||
|
||||
public static class CustomizationExtensions
|
||||
{
|
||||
public static string ToDefaultName(this CustomizationId customizationId)
|
||||
=> customizationId switch
|
||||
{
|
||||
CustomizationId.Race => "Race",
|
||||
CustomizationId.Gender => "Gender",
|
||||
CustomizationId.BodyType => "Body Type",
|
||||
CustomizationId.Height => "Height",
|
||||
CustomizationId.Clan => "Clan",
|
||||
CustomizationId.Face => "Head Style",
|
||||
CustomizationId.Hairstyle => "Hair Style",
|
||||
CustomizationId.HighlightsOnFlag => "Highlights",
|
||||
CustomizationId.SkinColor => "Skin Color",
|
||||
CustomizationId.EyeColorR => "Right Eye Color",
|
||||
CustomizationId.HairColor => "Hair Color",
|
||||
CustomizationId.HighlightColor => "Highlights Color",
|
||||
CustomizationId.FacialFeaturesTattoos => "Facial Features",
|
||||
CustomizationId.TattooColor => "Tattoo Color",
|
||||
CustomizationId.Eyebrows => "Eyebrow Style",
|
||||
CustomizationId.EyeColorL => "Left Eye Color",
|
||||
CustomizationId.EyeShape => "Eye Shape",
|
||||
CustomizationId.Nose => "Nose Style",
|
||||
CustomizationId.Jaw => "Jaw Style",
|
||||
CustomizationId.Mouth => "Mouth Style",
|
||||
CustomizationId.MuscleToneOrTailEarLength => "Muscle Tone",
|
||||
CustomizationId.TailEarShape => "Tail Shape",
|
||||
CustomizationId.BustSize => "Bust Size",
|
||||
CustomizationId.FacePaint => "Face Paint",
|
||||
CustomizationId.FacePaintColor => "Face Paint Color",
|
||||
CustomizationId.LipColor => "Lip Color",
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(customizationId), customizationId, null),
|
||||
};
|
||||
|
||||
public static CharaMakeParams.MenuType ToType(this CustomizationId customizationId, Race race = Race.Hyur)
|
||||
=> customizationId switch
|
||||
{
|
||||
CustomizationId.Race => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.Gender => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.BodyType => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.Height => CharaMakeParams.MenuType.Percentage,
|
||||
CustomizationId.Clan => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.Face => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.Hairstyle => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.HighlightsOnFlag => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.SkinColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.EyeColorR => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.HairColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.HighlightColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.FacialFeaturesTattoos => CharaMakeParams.MenuType.MultiIconSelector,
|
||||
CustomizationId.TattooColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.Eyebrows => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.EyeColorL => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.EyeShape => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.Nose => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.Jaw => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.Mouth => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.MuscleToneOrTailEarLength => CharaMakeParams.MenuType.Percentage,
|
||||
CustomizationId.BustSize => CharaMakeParams.MenuType.Percentage,
|
||||
CustomizationId.FacePaint => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.FacePaintColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
|
||||
CustomizationId.TailEarShape => race == Race.Elezen || race == Race.Lalafell
|
||||
? CharaMakeParams.MenuType.ListSelector
|
||||
: CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.LipColor => race == Race.Hrothgar
|
||||
? CharaMakeParams.MenuType.IconSelector
|
||||
: CharaMakeParams.MenuType.ColorPicker,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(customizationId), customizationId, null),
|
||||
};
|
||||
}
|
||||
Race = 0,
|
||||
Gender = 1,
|
||||
BodyType = 2,
|
||||
Height = 3,
|
||||
Clan = 4,
|
||||
Face = 5,
|
||||
Hairstyle = 6,
|
||||
HighlightsOnFlag = 7,
|
||||
SkinColor = 8,
|
||||
EyeColorR = 9,
|
||||
HairColor = 10,
|
||||
HighlightColor = 11,
|
||||
FacialFeaturesTattoos = 12, // Bitmask, 1-7 per face, 8 is 1.0 tattoo
|
||||
TattooColor = 13,
|
||||
Eyebrows = 14,
|
||||
EyeColorL = 15,
|
||||
EyeShape = 16, // Flag 128 for Small
|
||||
Nose = 17,
|
||||
Jaw = 18,
|
||||
Mouth = 19, // Flag 128 for Lip Color set
|
||||
LipColor = 20, // Flag 128 for Light instead of Dark
|
||||
MuscleToneOrTailEarLength = 21,
|
||||
TailEarShape = 22,
|
||||
BustSize = 23,
|
||||
FacePaint = 24,
|
||||
FacePaintColor = 25, // Flag 128 for Light instead of Dark.
|
||||
}
|
||||
|
||||
public static class CustomizationExtensions
|
||||
{
|
||||
public static string ToDefaultName(this CustomizationId customizationId)
|
||||
=> customizationId switch
|
||||
{
|
||||
CustomizationId.Race => "Race",
|
||||
CustomizationId.Gender => "Gender",
|
||||
CustomizationId.BodyType => "Body Type",
|
||||
CustomizationId.Height => "Height",
|
||||
CustomizationId.Clan => "Clan",
|
||||
CustomizationId.Face => "Head Style",
|
||||
CustomizationId.Hairstyle => "Hair Style",
|
||||
CustomizationId.HighlightsOnFlag => "Highlights",
|
||||
CustomizationId.SkinColor => "Skin Color",
|
||||
CustomizationId.EyeColorR => "Right Eye Color",
|
||||
CustomizationId.HairColor => "Hair Color",
|
||||
CustomizationId.HighlightColor => "Highlights Color",
|
||||
CustomizationId.FacialFeaturesTattoos => "Facial Features",
|
||||
CustomizationId.TattooColor => "Tattoo Color",
|
||||
CustomizationId.Eyebrows => "Eyebrow Style",
|
||||
CustomizationId.EyeColorL => "Left Eye Color",
|
||||
CustomizationId.EyeShape => "Eye Shape",
|
||||
CustomizationId.Nose => "Nose Style",
|
||||
CustomizationId.Jaw => "Jaw Style",
|
||||
CustomizationId.Mouth => "Mouth Style",
|
||||
CustomizationId.MuscleToneOrTailEarLength => "Muscle Tone",
|
||||
CustomizationId.TailEarShape => "Tail Shape",
|
||||
CustomizationId.BustSize => "Bust Size",
|
||||
CustomizationId.FacePaint => "Face Paint",
|
||||
CustomizationId.FacePaintColor => "Face Paint Color",
|
||||
CustomizationId.LipColor => "Lip Color",
|
||||
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(customizationId), customizationId, null),
|
||||
};
|
||||
|
||||
public static CharaMakeParams.MenuType ToType(this CustomizationId customizationId, Race race = Race.Hyur)
|
||||
=> customizationId switch
|
||||
{
|
||||
CustomizationId.Race => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.Gender => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.BodyType => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.Height => CharaMakeParams.MenuType.Percentage,
|
||||
CustomizationId.Clan => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.Face => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.Hairstyle => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.HighlightsOnFlag => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.SkinColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.EyeColorR => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.HairColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.HighlightColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.FacialFeaturesTattoos => CharaMakeParams.MenuType.MultiIconSelector,
|
||||
CustomizationId.TattooColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.Eyebrows => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.EyeColorL => CharaMakeParams.MenuType.ColorPicker,
|
||||
CustomizationId.EyeShape => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.Nose => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.Jaw => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.Mouth => CharaMakeParams.MenuType.ListSelector,
|
||||
CustomizationId.MuscleToneOrTailEarLength => CharaMakeParams.MenuType.Percentage,
|
||||
CustomizationId.BustSize => CharaMakeParams.MenuType.Percentage,
|
||||
CustomizationId.FacePaint => CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.FacePaintColor => CharaMakeParams.MenuType.ColorPicker,
|
||||
|
||||
CustomizationId.TailEarShape => race is Race.Elezen or Race.Lalafell
|
||||
? CharaMakeParams.MenuType.ListSelector
|
||||
: CharaMakeParams.MenuType.IconSelector,
|
||||
CustomizationId.LipColor => race == Race.Hrothgar
|
||||
? CharaMakeParams.MenuType.IconSelector
|
||||
: CharaMakeParams.MenuType.ColorPicker,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(customizationId), customizationId, null),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ using System.Reflection;
|
|||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Plugin;
|
||||
using Lumina.Data;
|
||||
using Dalamud.Utility;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using OtterGui.Classes;
|
||||
|
|
@ -14,360 +14,407 @@ using Race = Penumbra.GameData.Enums.Race;
|
|||
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
// Generate everything about customization per tribe and gender.
|
||||
public partial class CustomizationOptions
|
||||
{
|
||||
internal static readonly Race[] Races = ((Race[])Enum.GetValues(typeof(Race))).Skip(1).ToArray();
|
||||
// All races except for Unknown
|
||||
internal static readonly Race[] Races = ((Race[])Enum.GetValues(typeof(Race))).Skip(1).ToArray();
|
||||
|
||||
// All tribes except for Unknown
|
||||
internal static readonly SubRace[] Clans = ((SubRace[])Enum.GetValues(typeof(SubRace))).Skip(1).ToArray();
|
||||
|
||||
// Two genders.
|
||||
internal static readonly Gender[] Genders =
|
||||
{
|
||||
Gender.Male,
|
||||
Gender.Female,
|
||||
};
|
||||
|
||||
// Every tribe and gender has a separate set of available customizations.
|
||||
internal CustomizationSet GetList(SubRace race, Gender gender)
|
||||
=> _list[ToIndex(race, gender)];
|
||||
=> _customizationSets[ToIndex(race, gender)];
|
||||
|
||||
// Get specific icons.
|
||||
internal ImGuiScene.TextureWrap GetIcon(uint id)
|
||||
=> _icons.LoadIcon(id);
|
||||
|
||||
private static readonly int ListSize = Clans.Length * Genders.Length;
|
||||
private readonly IconStorage _icons;
|
||||
|
||||
private readonly CustomizationSet[] _list = new CustomizationSet[ListSize];
|
||||
private readonly IconStorage _icons;
|
||||
private static readonly int ListSize = Clans.Length * Genders.Length;
|
||||
private readonly CustomizationSet[] _customizationSets = new CustomizationSet[ListSize];
|
||||
|
||||
|
||||
// Get the index for the given pair of tribe and gender.
|
||||
private static int ToIndex(SubRace race, Gender gender)
|
||||
{
|
||||
var idx = ((int)race - 1) * Genders.Length + (gender == Gender.Female ? 1 : 0);
|
||||
if (idx < 0 || idx >= ListSize)
|
||||
ThrowException(race, gender);
|
||||
return idx;
|
||||
}
|
||||
|
||||
private static void ThrowException(SubRace race, Gender gender)
|
||||
=> throw new Exception($"Invalid customization requested for {race} {gender}.");
|
||||
}
|
||||
|
||||
private static int ToIndex(SubRace race, Gender gender)
|
||||
{
|
||||
if (race == SubRace.Unknown || gender != Gender.Female && gender != Gender.Male)
|
||||
ThrowException(race, gender);
|
||||
|
||||
var ret = (int)race - 1;
|
||||
ret = ret * Genders.Length + (gender == Gender.Female ? 1 : 0);
|
||||
return ret;
|
||||
}
|
||||
|
||||
private Customization[] GetHairStyles(SubRace race, Gender gender)
|
||||
{
|
||||
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||
var hairList = new List<Customization>(row.Unknown30);
|
||||
for (var i = 0; i < row.Unknown30; ++i)
|
||||
{
|
||||
var name = $"Unknown{66 + i * 9}";
|
||||
var customizeIdx =
|
||||
(uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
|
||||
?? uint.MaxValue;
|
||||
if (customizeIdx == uint.MaxValue)
|
||||
continue;
|
||||
|
||||
var hairRow = _customizeSheet.GetRow(customizeIdx);
|
||||
hairList.Add(hairRow != null
|
||||
? new Customization(CustomizationId.Hairstyle, hairRow.FeatureID, hairRow.Icon, (ushort)hairRow.RowId)
|
||||
: new Customization(CustomizationId.Hairstyle, (byte)i, customizeIdx, 0));
|
||||
}
|
||||
|
||||
return hairList.ToArray();
|
||||
}
|
||||
|
||||
private Customization[] CreateColorPicker(CustomizationId id, int offset, int num, bool light = false)
|
||||
=> _cmpFile.RgbaColors.Skip(offset).Take(num)
|
||||
.Select((c, i) => new Customization(id, (byte)(light ? 128 + i : 0 + i), c, (ushort)(offset + i)))
|
||||
.ToArray();
|
||||
|
||||
private (Customization[], Customization[]) GetColors(SubRace race, Gender gender)
|
||||
{
|
||||
if (race > SubRace.Veena || race == SubRace.Unknown)
|
||||
throw new ArgumentOutOfRangeException(nameof(race), race, null);
|
||||
|
||||
var gv = gender == Gender.Male ? 0 : 1;
|
||||
var idx = ((int)race * 2 + gv) * 5 + 3;
|
||||
|
||||
return (CreateColorPicker(CustomizationId.SkinColor, idx << 8, 192),
|
||||
CreateColorPicker(CustomizationId.HairColor, (idx + 1) << 8, 192));
|
||||
}
|
||||
|
||||
private Customization FromValueAndIndex(CustomizationId id, uint value, int index)
|
||||
{
|
||||
var row = _customizeSheet.GetRow(value);
|
||||
return row == null
|
||||
? new Customization(id, (byte)(index + 1), value, 0)
|
||||
: new Customization(id, row.FeatureID, row.Icon, (ushort)row.RowId);
|
||||
}
|
||||
|
||||
private static int GetListSize(CharaMakeParams row, CustomizationId id)
|
||||
{
|
||||
var menu = row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == id);
|
||||
return menu?.Size ?? 0;
|
||||
}
|
||||
|
||||
private Customization[] GetFacePaints(SubRace race, Gender gender)
|
||||
{
|
||||
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||
var paintList = new List<Customization>(row.Unknown37);
|
||||
for (var i = 0; i < row.Unknown37; ++i)
|
||||
{
|
||||
var name = $"Unknown{73 + i * 9}";
|
||||
var customizeIdx =
|
||||
(uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
|
||||
?? uint.MaxValue;
|
||||
if (customizeIdx == uint.MaxValue)
|
||||
continue;
|
||||
|
||||
var paintRow = _customizeSheet.GetRow(customizeIdx);
|
||||
paintList.Add(paintRow != null
|
||||
? new Customization(CustomizationId.FacePaint, paintRow.FeatureID, paintRow.Icon, (ushort)paintRow.RowId)
|
||||
: new Customization(CustomizationId.FacePaint, (byte)i, customizeIdx, 0));
|
||||
}
|
||||
|
||||
return paintList.ToArray();
|
||||
}
|
||||
|
||||
private Customization[] GetTailEarShapes(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.TailEarShape)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.TailEarShape, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
|
||||
private Customization[] GetFaces(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.Face)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.Face, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
|
||||
private Customization[] HrothgarFurPattern(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.LipColor)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.LipColor, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
|
||||
private Customization[] HrothgarFaces(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.Hairstyle)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.Hairstyle, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
|
||||
private CustomizationSet GetSet(SubRace race, Gender gender)
|
||||
{
|
||||
var (skin, hair) = GetColors(race, gender);
|
||||
var row = _listSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||
var set = new CustomizationSet(race, gender)
|
||||
{
|
||||
HairStyles = GetHairStyles(race, gender),
|
||||
HairColors = hair,
|
||||
SkinColors = skin,
|
||||
EyeColors = _eyeColorPicker,
|
||||
HighlightColors = _highlightPicker,
|
||||
TattooColors = _tattooColorPicker,
|
||||
LipColorsDark = race.ToRace() == Race.Hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark,
|
||||
LipColorsLight = race.ToRace() == Race.Hrothgar ? Array.Empty<Customization>() : _lipColorPickerLight,
|
||||
FacePaintColorsDark = _facePaintColorPickerDark,
|
||||
FacePaintColorsLight = _facePaintColorPickerLight,
|
||||
Faces = GetFaces(row),
|
||||
NumEyebrows = GetListSize(row, CustomizationId.Eyebrows),
|
||||
NumEyeShapes = GetListSize(row, CustomizationId.EyeShape),
|
||||
NumNoseShapes = GetListSize(row, CustomizationId.Nose),
|
||||
NumJawShapes = GetListSize(row, CustomizationId.Jaw),
|
||||
NumMouthShapes = GetListSize(row, CustomizationId.Mouth),
|
||||
FacePaints = GetFacePaints(race, gender),
|
||||
TailEarShapes = GetTailEarShapes(row),
|
||||
};
|
||||
|
||||
if (GetListSize(row, CustomizationId.BustSize) > 0)
|
||||
set.SetAvailable(CustomizationId.BustSize);
|
||||
if (GetListSize(row, CustomizationId.MuscleToneOrTailEarLength) > 0)
|
||||
set.SetAvailable(CustomizationId.MuscleToneOrTailEarLength);
|
||||
|
||||
if (set.NumEyebrows > 0)
|
||||
set.SetAvailable(CustomizationId.Eyebrows);
|
||||
if (set.NumEyeShapes > 0)
|
||||
set.SetAvailable(CustomizationId.EyeShape);
|
||||
if (set.NumNoseShapes > 0)
|
||||
set.SetAvailable(CustomizationId.Nose);
|
||||
if (set.NumJawShapes > 0)
|
||||
set.SetAvailable(CustomizationId.Jaw);
|
||||
if (set.NumMouthShapes > 0)
|
||||
set.SetAvailable(CustomizationId.Mouth);
|
||||
if (set.FacePaints.Count > 0)
|
||||
{
|
||||
set.SetAvailable(CustomizationId.FacePaint);
|
||||
set.SetAvailable(CustomizationId.FacePaintColor);
|
||||
}
|
||||
|
||||
if (set.TailEarShapes.Count > 0)
|
||||
set.SetAvailable(CustomizationId.TailEarShape);
|
||||
if (set.Faces.Count > 0)
|
||||
set.SetAvailable(CustomizationId.Face);
|
||||
|
||||
var count = set.Faces.Count;
|
||||
var featureDict = new List<IReadOnlyList<Customization>>(count);
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
featureDict.Add(row.FacialFeatureByFace[i].Icons.Select((val, idx)
|
||||
=> new Customization(CustomizationId.FacialFeaturesTattoos, (byte)(1 << idx), val, (ushort)(i * 8 + idx)))
|
||||
.Append(new Customization(CustomizationId.FacialFeaturesTattoos, 1 << 7, 137905, (ushort)((i + 1) * 8)))
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
set.FeaturesTattoos = featureDict;
|
||||
|
||||
var nameArray = ((CustomizationId[])Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
||||
{
|
||||
var menu = row.Menus
|
||||
.Cast<CharaMakeParams.Menu?>()
|
||||
.FirstOrDefault(m => m!.Value.Customization == c);
|
||||
if (menu == null)
|
||||
{
|
||||
if (c == CustomizationId.HighlightsOnFlag)
|
||||
return _lobby.GetRow(237)?.Text.ToString() ?? "Highlights";
|
||||
|
||||
return c.ToDefaultName();
|
||||
}
|
||||
|
||||
if (c == CustomizationId.FacialFeaturesTattoos)
|
||||
return
|
||||
$"{_lobby.GetRow(1741)?.Text.ToString() ?? "Facial Features"} & {_lobby.GetRow(1742)?.Text.ToString() ?? "Tattoos"}";
|
||||
|
||||
var textRow = _lobby.GetRow(menu.Value.Id);
|
||||
return textRow?.Text.ToString() ?? c.ToDefaultName();
|
||||
}).ToArray();
|
||||
nameArray[(int)CustomizationId.EyeColorL] = nameArray[(int)CustomizationId.EyeColorR];
|
||||
nameArray[(int)CustomizationId.EyeColorR] = GetName(CustomName.OddEyes);
|
||||
set.OptionName = nameArray;
|
||||
|
||||
set.Types = ((CustomizationId[])Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
||||
{
|
||||
switch (c)
|
||||
{
|
||||
case CustomizationId.HighlightColor:
|
||||
case CustomizationId.EyeColorL:
|
||||
case CustomizationId.EyeColorR:
|
||||
return CharaMakeParams.MenuType.ColorPicker;
|
||||
}
|
||||
|
||||
var menu = row.Menus
|
||||
.Cast<CharaMakeParams.Menu?>()
|
||||
.FirstOrDefault(m => m!.Value.Customization == c);
|
||||
return menu?.Type ?? CharaMakeParams.MenuType.ListSelector;
|
||||
}).ToArray();
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
private readonly ExcelSheet<CharaMakeCustomize> _customizeSheet;
|
||||
private readonly ExcelSheet<CharaMakeParams> _listSheet;
|
||||
private readonly ExcelSheet<HairMakeType> _hairSheet;
|
||||
private readonly ExcelSheet<Lobby> _lobby;
|
||||
private readonly CmpFile _cmpFile;
|
||||
private readonly Customization[] _highlightPicker;
|
||||
private readonly Customization[] _eyeColorPicker;
|
||||
private readonly Customization[] _facePaintColorPickerDark;
|
||||
private readonly Customization[] _facePaintColorPickerLight;
|
||||
private readonly Customization[] _lipColorPickerDark;
|
||||
private readonly Customization[] _lipColorPickerLight;
|
||||
private readonly Customization[] _tattooColorPicker;
|
||||
private readonly string[] _names = new string[(int)CustomName.Num];
|
||||
public partial class CustomizationOptions
|
||||
{
|
||||
internal readonly bool Valid;
|
||||
|
||||
public string GetName(CustomName name)
|
||||
=> _names[(int)name];
|
||||
|
||||
private static Language FromClientLanguage(ClientLanguage language)
|
||||
=> language switch
|
||||
{
|
||||
ClientLanguage.English => Language.English,
|
||||
ClientLanguage.French => Language.French,
|
||||
ClientLanguage.German => Language.German,
|
||||
ClientLanguage.Japanese => Language.Japanese,
|
||||
_ => Language.English,
|
||||
};
|
||||
|
||||
internal CustomizationOptions(DalamudPluginInterface pi, DataManager gameData, ClientLanguage language)
|
||||
{
|
||||
try
|
||||
{
|
||||
_cmpFile = new CmpFile(gameData);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
throw new Exception("READ THIS\n======== Could not obtain the human.cmp file which is necessary for color sets.\n"
|
||||
+ "======== This usually indicates an error with your index files caused by TexTools modifications.\n"
|
||||
+ "======== If you have used TexTools before, you will probably need to start over in it to use Glamourer.", e);
|
||||
}
|
||||
|
||||
_customizeSheet = gameData.GetExcelSheet<CharaMakeCustomize>()!;
|
||||
_lobby = gameData.GetExcelSheet<Lobby>()!;
|
||||
var tmp = gameData.Excel.GetType()!.GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)!
|
||||
.MakeGenericMethod(typeof(CharaMakeParams))!.Invoke(gameData.Excel, new object?[]
|
||||
{
|
||||
"charamaketype",
|
||||
FromClientLanguage(language),
|
||||
null,
|
||||
}) as ExcelSheet<CharaMakeParams>;
|
||||
_listSheet = tmp!;
|
||||
_hairSheet = gameData.GetExcelSheet<HairMakeType>()!;
|
||||
SetNames(gameData);
|
||||
|
||||
_highlightPicker = CreateColorPicker(CustomizationId.HighlightColor, 256, 192);
|
||||
_lipColorPickerDark = CreateColorPicker(CustomizationId.LipColor, 512, 96);
|
||||
_lipColorPickerLight = CreateColorPicker(CustomizationId.LipColor, 1024, 96, true);
|
||||
_eyeColorPicker = CreateColorPicker(CustomizationId.EyeColorL, 0, 192);
|
||||
_facePaintColorPickerDark = CreateColorPicker(CustomizationId.FacePaintColor, 640, 96);
|
||||
_facePaintColorPickerLight = CreateColorPicker(CustomizationId.FacePaintColor, 1152, 96, true);
|
||||
_tattooColorPicker = CreateColorPicker(CustomizationId.TattooColor, 0, 192);
|
||||
|
||||
_icons = new IconStorage(pi, gameData, _list.Length * 50);
|
||||
var tmp = new TemporaryData(gameData, this, language);
|
||||
_icons = new IconStorage(pi, gameData, _customizationSets.Length * 50);
|
||||
Valid = tmp.Valid;
|
||||
SetNames(gameData, tmp);
|
||||
foreach (var race in Clans)
|
||||
{
|
||||
foreach (var gender in Genders)
|
||||
_list[ToIndex(race, gender)] = GetSet(race, gender);
|
||||
_customizationSets[ToIndex(race, gender)] = tmp.GetSet(race, gender);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetNames(DataManager gameData)
|
||||
|
||||
// Obtain localized names of customization options and race names from the game data.
|
||||
private readonly string[] _names = new string[Enum.GetValues<CustomName>().Length];
|
||||
|
||||
private void SetNames(DataManager gameData, TemporaryData tmp)
|
||||
{
|
||||
var subRace = gameData.GetExcelSheet<Tribe>()!;
|
||||
_names[(int)CustomName.Clan] = _lobby.GetRow(102)?.Text ?? "Clan";
|
||||
_names[(int)CustomName.Gender] = _lobby.GetRow(103)?.Text ?? "Gender";
|
||||
_names[(int)CustomName.Reverse] = _lobby.GetRow(2135)?.Text ?? "Reverse";
|
||||
_names[(int)CustomName.OddEyes] = _lobby.GetRow(2125)?.Text ?? "Odd Eyes";
|
||||
_names[(int)CustomName.IrisSmall] = _lobby.GetRow(1076)?.Text ?? "Small";
|
||||
_names[(int)CustomName.IrisLarge] = _lobby.GetRow(1075)?.Text ?? "Large";
|
||||
_names[(int)CustomName.IrisSize] = _lobby.GetRow(244)?.Text ?? "Iris Size";
|
||||
_names[(int)CustomName.MidlanderM] = subRace.GetRow((int)SubRace.Midlander)?.Masculine.ToString() ?? SubRace.Midlander.ToName();
|
||||
_names[(int)CustomName.MidlanderF] = subRace.GetRow((int)SubRace.Midlander)?.Feminine.ToString() ?? SubRace.Midlander.ToName();
|
||||
_names[(int)CustomName.HighlanderM] =
|
||||
subRace.GetRow((int)SubRace.Highlander)?.Masculine.ToString() ?? SubRace.Highlander.ToName();
|
||||
_names[(int)CustomName.HighlanderF] = subRace.GetRow((int)SubRace.Highlander)?.Feminine.ToString() ?? SubRace.Highlander.ToName();
|
||||
_names[(int)CustomName.WildwoodM] = subRace.GetRow((int)SubRace.Wildwood)?.Masculine.ToString() ?? SubRace.Wildwood.ToName();
|
||||
_names[(int)CustomName.WildwoodF] = subRace.GetRow((int)SubRace.Wildwood)?.Feminine.ToString() ?? SubRace.Wildwood.ToName();
|
||||
_names[(int)CustomName.DuskwightM] = subRace.GetRow((int)SubRace.Duskwight)?.Masculine.ToString() ?? SubRace.Duskwight.ToName();
|
||||
_names[(int)CustomName.DuskwightF] = subRace.GetRow((int)SubRace.Duskwight)?.Feminine.ToString() ?? SubRace.Duskwight.ToName();
|
||||
_names[(int)CustomName.PlainsfolkM] =
|
||||
subRace.GetRow((int)SubRace.Plainsfolk)?.Masculine.ToString() ?? SubRace.Plainsfolk.ToName();
|
||||
_names[(int)CustomName.PlainsfolkF] = subRace.GetRow((int)SubRace.Plainsfolk)?.Feminine.ToString() ?? SubRace.Plainsfolk.ToName();
|
||||
_names[(int)CustomName.DunesfolkM] = subRace.GetRow((int)SubRace.Dunesfolk)?.Masculine.ToString() ?? SubRace.Dunesfolk.ToName();
|
||||
_names[(int)CustomName.DunesfolkF] = subRace.GetRow((int)SubRace.Dunesfolk)?.Feminine.ToString() ?? SubRace.Dunesfolk.ToName();
|
||||
_names[(int)CustomName.SeekerOfTheSunM] =
|
||||
subRace.GetRow((int)SubRace.SeekerOfTheSun)?.Masculine.ToString() ?? SubRace.SeekerOfTheSun.ToName();
|
||||
_names[(int)CustomName.SeekerOfTheSunF] =
|
||||
subRace.GetRow((int)SubRace.SeekerOfTheSun)?.Feminine.ToString() ?? SubRace.SeekerOfTheSun.ToName();
|
||||
_names[(int)CustomName.KeeperOfTheMoonM] =
|
||||
subRace.GetRow((int)SubRace.KeeperOfTheMoon)?.Masculine.ToString() ?? SubRace.KeeperOfTheMoon.ToName();
|
||||
_names[(int)CustomName.KeeperOfTheMoonF] =
|
||||
subRace.GetRow((int)SubRace.KeeperOfTheMoon)?.Feminine.ToString() ?? SubRace.KeeperOfTheMoon.ToName();
|
||||
_names[(int)CustomName.SeawolfM] = subRace.GetRow((int)SubRace.Seawolf)?.Masculine.ToString() ?? SubRace.Seawolf.ToName();
|
||||
_names[(int)CustomName.SeawolfF] = subRace.GetRow((int)SubRace.Seawolf)?.Feminine.ToString() ?? SubRace.Seawolf.ToName();
|
||||
_names[(int)CustomName.HellsguardM] =
|
||||
subRace.GetRow((int)SubRace.Hellsguard)?.Masculine.ToString() ?? SubRace.Hellsguard.ToName();
|
||||
_names[(int)CustomName.HellsguardF] = subRace.GetRow((int)SubRace.Hellsguard)?.Feminine.ToString() ?? SubRace.Hellsguard.ToName();
|
||||
_names[(int)CustomName.RaenM] = subRace.GetRow((int)SubRace.Raen)?.Masculine.ToString() ?? SubRace.Raen.ToName();
|
||||
_names[(int)CustomName.RaenF] = subRace.GetRow((int)SubRace.Raen)?.Feminine.ToString() ?? SubRace.Raen.ToName();
|
||||
_names[(int)CustomName.XaelaM] = subRace.GetRow((int)SubRace.Xaela)?.Masculine.ToString() ?? SubRace.Xaela.ToName();
|
||||
_names[(int)CustomName.XaelaF] = subRace.GetRow((int)SubRace.Xaela)?.Feminine.ToString() ?? SubRace.Xaela.ToName();
|
||||
_names[(int)CustomName.HelionM] = subRace.GetRow((int)SubRace.Helion)?.Masculine.ToString() ?? SubRace.Helion.ToName();
|
||||
_names[(int)CustomName.HelionF] = subRace.GetRow((int)SubRace.Helion)?.Feminine.ToString() ?? SubRace.Helion.ToName();
|
||||
_names[(int)CustomName.LostM] = subRace.GetRow((int)SubRace.Lost)?.Masculine.ToString() ?? SubRace.Lost.ToName();
|
||||
_names[(int)CustomName.LostF] = subRace.GetRow((int)SubRace.Lost)?.Feminine.ToString() ?? SubRace.Lost.ToName();
|
||||
_names[(int)CustomName.RavaM] = subRace.GetRow((int)SubRace.Rava)?.Masculine.ToString() ?? SubRace.Rava.ToName();
|
||||
_names[(int)CustomName.RavaF] = subRace.GetRow((int)SubRace.Rava)?.Feminine.ToString() ?? SubRace.Rava.ToName();
|
||||
_names[(int)CustomName.VeenaM] = subRace.GetRow((int)SubRace.Veena)?.Masculine.ToString() ?? SubRace.Veena.ToName();
|
||||
_names[(int)CustomName.VeenaF] = subRace.GetRow((int)SubRace.Veena)?.Feminine.ToString() ?? SubRace.Veena.ToName();
|
||||
|
||||
void Set(CustomName id, Lumina.Text.SeString? s, string def)
|
||||
=> _names[(int)id] = s?.ToDalamudString().TextValue ?? def;
|
||||
|
||||
Set(CustomName.Clan, tmp.Lobby.GetRow(102)?.Text, "Clan");
|
||||
Set(CustomName.Gender, tmp.Lobby.GetRow(103)?.Text, "Gender");
|
||||
Set(CustomName.Reverse, tmp.Lobby.GetRow(2135)?.Text, "Reverse");
|
||||
Set(CustomName.OddEyes, tmp.Lobby.GetRow(2125)?.Text, "Odd Eyes");
|
||||
Set(CustomName.IrisSmall, tmp.Lobby.GetRow(1076)?.Text, "Small");
|
||||
Set(CustomName.IrisLarge, tmp.Lobby.GetRow(1075)?.Text, "Large");
|
||||
Set(CustomName.IrisSize, tmp.Lobby.GetRow(244)?.Text, "Iris Size");
|
||||
Set(CustomName.MidlanderM, subRace.GetRow((int)SubRace.Midlander)?.Masculine, SubRace.Midlander.ToName());
|
||||
Set(CustomName.MidlanderF, subRace.GetRow((int)SubRace.Midlander)?.Feminine, SubRace.Midlander.ToName());
|
||||
Set(CustomName.HighlanderM, subRace.GetRow((int)SubRace.Highlander)?.Masculine, SubRace.Highlander.ToName());
|
||||
Set(CustomName.HighlanderF, subRace.GetRow((int)SubRace.Highlander)?.Feminine, SubRace.Highlander.ToName());
|
||||
Set(CustomName.WildwoodM, subRace.GetRow((int)SubRace.Wildwood)?.Masculine, SubRace.Wildwood.ToName());
|
||||
Set(CustomName.WildwoodF, subRace.GetRow((int)SubRace.Wildwood)?.Feminine, SubRace.Wildwood.ToName());
|
||||
Set(CustomName.DuskwightM, subRace.GetRow((int)SubRace.Duskwight)?.Masculine, SubRace.Duskwight.ToName());
|
||||
Set(CustomName.DuskwightF, subRace.GetRow((int)SubRace.Duskwight)?.Feminine, SubRace.Duskwight.ToName());
|
||||
Set(CustomName.PlainsfolkM, subRace.GetRow((int)SubRace.Plainsfolk)?.Masculine, SubRace.Plainsfolk.ToName());
|
||||
Set(CustomName.PlainsfolkF, subRace.GetRow((int)SubRace.Plainsfolk)?.Feminine, SubRace.Plainsfolk.ToName());
|
||||
Set(CustomName.DunesfolkM, subRace.GetRow((int)SubRace.Dunesfolk)?.Masculine, SubRace.Dunesfolk.ToName());
|
||||
Set(CustomName.DunesfolkF, subRace.GetRow((int)SubRace.Dunesfolk)?.Feminine, SubRace.Dunesfolk.ToName());
|
||||
Set(CustomName.SeekerOfTheSunM, subRace.GetRow((int)SubRace.SeekerOfTheSun)?.Masculine, SubRace.SeekerOfTheSun.ToName());
|
||||
Set(CustomName.SeekerOfTheSunF, subRace.GetRow((int)SubRace.SeekerOfTheSun)?.Feminine, SubRace.SeekerOfTheSun.ToName());
|
||||
Set(CustomName.KeeperOfTheMoonM, subRace.GetRow((int)SubRace.KeeperOfTheMoon)?.Masculine, SubRace.KeeperOfTheMoon.ToName());
|
||||
Set(CustomName.KeeperOfTheMoonF, subRace.GetRow((int)SubRace.KeeperOfTheMoon)?.Feminine, SubRace.KeeperOfTheMoon.ToName());
|
||||
Set(CustomName.SeawolfM, subRace.GetRow((int)SubRace.Seawolf)?.Masculine, SubRace.Seawolf.ToName());
|
||||
Set(CustomName.SeawolfF, subRace.GetRow((int)SubRace.Seawolf)?.Feminine, SubRace.Seawolf.ToName());
|
||||
Set(CustomName.HellsguardM, subRace.GetRow((int)SubRace.Hellsguard)?.Masculine, SubRace.Hellsguard.ToName());
|
||||
Set(CustomName.HellsguardF, subRace.GetRow((int)SubRace.Hellsguard)?.Feminine, SubRace.Hellsguard.ToName());
|
||||
Set(CustomName.RaenM, subRace.GetRow((int)SubRace.Raen)?.Masculine, SubRace.Raen.ToName());
|
||||
Set(CustomName.RaenF, subRace.GetRow((int)SubRace.Raen)?.Feminine, SubRace.Raen.ToName());
|
||||
Set(CustomName.XaelaM, subRace.GetRow((int)SubRace.Xaela)?.Masculine, SubRace.Xaela.ToName());
|
||||
Set(CustomName.XaelaF, subRace.GetRow((int)SubRace.Xaela)?.Feminine, SubRace.Xaela.ToName());
|
||||
Set(CustomName.HelionM, subRace.GetRow((int)SubRace.Helion)?.Masculine, SubRace.Helion.ToName());
|
||||
Set(CustomName.HelionF, subRace.GetRow((int)SubRace.Helion)?.Feminine, SubRace.Helion.ToName());
|
||||
Set(CustomName.LostM, subRace.GetRow((int)SubRace.Lost)?.Masculine, SubRace.Lost.ToName());
|
||||
Set(CustomName.LostF, subRace.GetRow((int)SubRace.Lost)?.Feminine, SubRace.Lost.ToName());
|
||||
Set(CustomName.RavaM, subRace.GetRow((int)SubRace.Rava)?.Masculine, SubRace.Rava.ToName());
|
||||
Set(CustomName.RavaF, subRace.GetRow((int)SubRace.Rava)?.Feminine, SubRace.Rava.ToName());
|
||||
Set(CustomName.VeenaM, subRace.GetRow((int)SubRace.Veena)?.Masculine, SubRace.Veena.ToName());
|
||||
Set(CustomName.VeenaF, subRace.GetRow((int)SubRace.Veena)?.Feminine, SubRace.Veena.ToName());
|
||||
}
|
||||
|
||||
private class TemporaryData
|
||||
{
|
||||
public bool Valid
|
||||
=> _cmpFile.Valid;
|
||||
|
||||
public CustomizationSet GetSet(SubRace race, Gender gender)
|
||||
{
|
||||
var (skin, hair) = GetColors(race, gender);
|
||||
var row = _listSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||
|
||||
// Create the initial set with all the easily accessible parameters available for anyone.
|
||||
var set = new CustomizationSet(race, gender)
|
||||
{
|
||||
HairStyles = GetHairStyles(race, gender),
|
||||
HairColors = hair,
|
||||
SkinColors = skin,
|
||||
EyeColors = _eyeColorPicker,
|
||||
HighlightColors = _highlightPicker,
|
||||
TattooColors = _tattooColorPicker,
|
||||
LipColorsDark = race.ToRace() == Race.Hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark,
|
||||
LipColorsLight = race.ToRace() == Race.Hrothgar ? Array.Empty<Customization>() : _lipColorPickerLight,
|
||||
FacePaintColorsDark = _facePaintColorPickerDark,
|
||||
FacePaintColorsLight = _facePaintColorPickerLight,
|
||||
Faces = GetFaces(row),
|
||||
NumEyebrows = GetListSize(row, CustomizationId.Eyebrows),
|
||||
NumEyeShapes = GetListSize(row, CustomizationId.EyeShape),
|
||||
NumNoseShapes = GetListSize(row, CustomizationId.Nose),
|
||||
NumJawShapes = GetListSize(row, CustomizationId.Jaw),
|
||||
NumMouthShapes = GetListSize(row, CustomizationId.Mouth),
|
||||
FacePaints = GetFacePaints(race, gender),
|
||||
TailEarShapes = GetTailEarShapes(row),
|
||||
};
|
||||
|
||||
SetAvailability(set, row);
|
||||
SetFacialFeatures(set, row);
|
||||
SetMenuTypes(set, row);
|
||||
SetNames(set, row);
|
||||
|
||||
|
||||
return set;
|
||||
}
|
||||
|
||||
public TemporaryData(DataManager gameData, CustomizationOptions options, ClientLanguage language)
|
||||
{
|
||||
_options = options;
|
||||
_cmpFile = new CmpFile(gameData);
|
||||
_customizeSheet = gameData.GetExcelSheet<CharaMakeCustomize>()!;
|
||||
Lobby = gameData.GetExcelSheet<Lobby>()!;
|
||||
var tmp = gameData.Excel.GetType().GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)?
|
||||
.MakeGenericMethod(typeof(CharaMakeParams)).Invoke(gameData.Excel, new object?[]
|
||||
{
|
||||
"charamaketype",
|
||||
language.ToLumina(),
|
||||
null,
|
||||
}) as ExcelSheet<CharaMakeParams>;
|
||||
_listSheet = tmp!;
|
||||
_hairSheet = gameData.GetExcelSheet<HairMakeType>()!;
|
||||
_highlightPicker = CreateColorPicker(CustomizationId.HighlightColor, 256, 192);
|
||||
_lipColorPickerDark = CreateColorPicker(CustomizationId.LipColor, 512, 96);
|
||||
_lipColorPickerLight = CreateColorPicker(CustomizationId.LipColor, 1024, 96, true);
|
||||
_eyeColorPicker = CreateColorPicker(CustomizationId.EyeColorL, 0, 192);
|
||||
_facePaintColorPickerDark = CreateColorPicker(CustomizationId.FacePaintColor, 640, 96);
|
||||
_facePaintColorPickerLight = CreateColorPicker(CustomizationId.FacePaintColor, 1152, 96, true);
|
||||
_tattooColorPicker = CreateColorPicker(CustomizationId.TattooColor, 0, 192);
|
||||
}
|
||||
|
||||
// Required sheets.
|
||||
private readonly ExcelSheet<CharaMakeCustomize> _customizeSheet;
|
||||
private readonly ExcelSheet<CharaMakeParams> _listSheet;
|
||||
private readonly ExcelSheet<HairMakeType> _hairSheet;
|
||||
public readonly ExcelSheet<Lobby> Lobby;
|
||||
private readonly CmpFile _cmpFile;
|
||||
|
||||
// Those values are shared between all races.
|
||||
private readonly Customization[] _highlightPicker;
|
||||
private readonly Customization[] _eyeColorPicker;
|
||||
private readonly Customization[] _facePaintColorPickerDark;
|
||||
private readonly Customization[] _facePaintColorPickerLight;
|
||||
private readonly Customization[] _lipColorPickerDark;
|
||||
private readonly Customization[] _lipColorPickerLight;
|
||||
private readonly Customization[] _tattooColorPicker;
|
||||
|
||||
private readonly CustomizationOptions _options;
|
||||
|
||||
private Customization[] CreateColorPicker(CustomizationId id, int offset, int num, bool light = false)
|
||||
=> _cmpFile.GetSlice(offset, num)
|
||||
.Select((c, i) => new Customization(id, (byte)(light ? 128 + i : 0 + i), c, (ushort)(offset + i)))
|
||||
.ToArray();
|
||||
|
||||
private static void SetMenuTypes(CustomizationSet set, CharaMakeParams row)
|
||||
{
|
||||
// Set up the menu types for all customizations.
|
||||
set.Types = ((CustomizationId[])Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
||||
{
|
||||
// Those types are not correctly given in the menu, so special case them to color pickers.
|
||||
switch (c)
|
||||
{
|
||||
case CustomizationId.HighlightColor:
|
||||
case CustomizationId.EyeColorL:
|
||||
case CustomizationId.EyeColorR:
|
||||
return CharaMakeParams.MenuType.ColorPicker;
|
||||
}
|
||||
|
||||
// Otherwise find the first menu corresponding to the id.
|
||||
// If there is none, assume a list.
|
||||
var menu = row.Menus
|
||||
.Cast<CharaMakeParams.Menu?>()
|
||||
.FirstOrDefault(m => m!.Value.Customization == c);
|
||||
return menu?.Type ?? CharaMakeParams.MenuType.ListSelector;
|
||||
}).ToArray();
|
||||
set.Order = CustomizationSet.ComputeOrder(set);
|
||||
}
|
||||
|
||||
// Set customizations available if they have any options.
|
||||
private static void SetAvailability(CustomizationSet set, CharaMakeParams row)
|
||||
{
|
||||
void Set(bool available, CustomizationId flag)
|
||||
{
|
||||
if (available)
|
||||
set.SetAvailable(flag);
|
||||
}
|
||||
|
||||
// Both are percentages that are either unavailable or 0-100.
|
||||
Set(GetListSize(row, CustomizationId.BustSize) > 0, CustomizationId.BustSize);
|
||||
Set(GetListSize(row, CustomizationId.MuscleToneOrTailEarLength) > 0, CustomizationId.MuscleToneOrTailEarLength);
|
||||
Set(set.NumEyebrows > 0, CustomizationId.Eyebrows);
|
||||
Set(set.NumEyeShapes > 0, CustomizationId.EyeShape);
|
||||
Set(set.NumNoseShapes > 0, CustomizationId.Nose);
|
||||
Set(set.NumJawShapes > 0, CustomizationId.Jaw);
|
||||
Set(set.NumMouthShapes > 0, CustomizationId.Mouth);
|
||||
Set(set.TailEarShapes.Count > 0, CustomizationId.TailEarShape);
|
||||
Set(set.Faces.Count > 0, CustomizationId.Face);
|
||||
Set(set.FacePaints.Count > 0, CustomizationId.FacePaint);
|
||||
Set(set.FacePaints.Count > 0, CustomizationId.FacePaintColor);
|
||||
}
|
||||
|
||||
// Create a list of lists of facial features and the legacy tattoo.
|
||||
private static void SetFacialFeatures(CustomizationSet set, CharaMakeParams row)
|
||||
{
|
||||
var count = set.Faces.Count;
|
||||
var featureDict = new List<IReadOnlyList<Customization>>(count);
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
var legacyTattoo = new Customization(CustomizationId.FacialFeaturesTattoos, 1 << 7, 137905, (ushort)((i + 1) * 8));
|
||||
featureDict.Add(row.FacialFeatureByFace[i].Icons.Select((val, idx)
|
||||
=> new Customization(CustomizationId.FacialFeaturesTattoos, (byte)(1 << idx), val, (ushort)(i * 8 + idx)))
|
||||
.Append(legacyTattoo)
|
||||
.ToArray());
|
||||
}
|
||||
|
||||
set.FeaturesTattoos = featureDict.ToArray();
|
||||
}
|
||||
|
||||
// Set the names for the given set of parameters.
|
||||
private void SetNames(CustomizationSet set, CharaMakeParams row)
|
||||
{
|
||||
var nameArray = ((CustomizationId[])Enum.GetValues(typeof(CustomizationId))).Select(c =>
|
||||
{
|
||||
// Find the first menu that corresponds to the Id.
|
||||
var menu = row.Menus
|
||||
.Cast<CharaMakeParams.Menu?>()
|
||||
.FirstOrDefault(m => m!.Value.Customization == c);
|
||||
if (menu == null)
|
||||
{
|
||||
// If none exists and the id corresponds to highlights, set the Highlights name.
|
||||
if (c == CustomizationId.HighlightsOnFlag)
|
||||
return Lobby.GetRow(237)?.Text.ToDalamudString().ToString() ?? "Highlights";
|
||||
|
||||
// Otherwise there is an error and we use the default name.
|
||||
return c.ToDefaultName();
|
||||
}
|
||||
|
||||
// Facial Features and Tattoos is created by combining two strings.
|
||||
if (c == CustomizationId.FacialFeaturesTattoos)
|
||||
return
|
||||
$"{Lobby.GetRow(1741)?.Text.ToDalamudString().ToString() ?? "Facial Features"} & {Lobby.GetRow(1742)?.Text.ToDalamudString().ToString() ?? "Tattoos"}";
|
||||
|
||||
// Otherwise all is normal, get the menu name or if it does not work the default name.
|
||||
var textRow = Lobby.GetRow(menu.Value.Id);
|
||||
return textRow?.Text.ToDalamudString().ToString() ?? c.ToDefaultName();
|
||||
}).ToArray();
|
||||
|
||||
// Add names for both eye colors.
|
||||
nameArray[(int)CustomizationId.EyeColorL] = nameArray[(int)CustomizationId.EyeColorR];
|
||||
nameArray[(int)CustomizationId.EyeColorR] = _options.GetName(CustomName.OddEyes);
|
||||
|
||||
set.OptionName = nameArray;
|
||||
}
|
||||
|
||||
// Obtain available skin and hair colors for the given subrace and gender.
|
||||
private (Customization[], Customization[]) GetColors(SubRace race, Gender gender)
|
||||
{
|
||||
if (race is > SubRace.Veena or SubRace.Unknown)
|
||||
throw new ArgumentOutOfRangeException(nameof(race), race, null);
|
||||
|
||||
var gv = gender == Gender.Male ? 0 : 1;
|
||||
var idx = ((int)race * 2 + gv) * 5 + 3;
|
||||
|
||||
return (CreateColorPicker(CustomizationId.SkinColor, idx << 8, 192),
|
||||
CreateColorPicker(CustomizationId.HairColor, (idx + 1) << 8, 192));
|
||||
}
|
||||
|
||||
// Obtain available hairstyles via reflection from the Hair sheet for the given subrace and gender.
|
||||
private Customization[] GetHairStyles(SubRace race, Gender gender)
|
||||
{
|
||||
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||
// Unknown30 is the number of available hairstyles.
|
||||
var hairList = new List<Customization>(row.Unknown30);
|
||||
// Hairstyles can be found starting at Unknown66.
|
||||
for (var i = 0; i < row.Unknown30; ++i)
|
||||
{
|
||||
var name = $"Unknown{66 + i * 9}";
|
||||
var customizeIdx = (uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
|
||||
?? uint.MaxValue;
|
||||
if (customizeIdx == uint.MaxValue)
|
||||
continue;
|
||||
|
||||
// Hair Row from CustomizeSheet might not be set in case of unlockable hair.
|
||||
var hairRow = _customizeSheet.GetRow(customizeIdx);
|
||||
hairList.Add(hairRow != null
|
||||
? new Customization(CustomizationId.Hairstyle, hairRow.FeatureID, hairRow.Icon, (ushort)hairRow.RowId)
|
||||
: new Customization(CustomizationId.Hairstyle, (byte)i, customizeIdx));
|
||||
}
|
||||
|
||||
return hairList.ToArray();
|
||||
}
|
||||
|
||||
// Get Features.
|
||||
private Customization FromValueAndIndex(CustomizationId id, uint value, int index)
|
||||
{
|
||||
var row = _customizeSheet.GetRow(value);
|
||||
return row == null
|
||||
? new Customization(id, (byte)(index + 1), value)
|
||||
: new Customization(id, row.FeatureID, row.Icon, (ushort)row.RowId);
|
||||
}
|
||||
|
||||
// Get List sizes.
|
||||
private static int GetListSize(CharaMakeParams row, CustomizationId id)
|
||||
{
|
||||
var menu = row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == id);
|
||||
return menu?.Size ?? 0;
|
||||
}
|
||||
|
||||
// Get face paints from the hair sheet via reflection.
|
||||
private Customization[] GetFacePaints(SubRace race, Gender gender)
|
||||
{
|
||||
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
|
||||
var paintList = new List<Customization>(row.Unknown37);
|
||||
|
||||
// Number of available face paints is at Unknown37.
|
||||
for (var i = 0; i < row.Unknown37; ++i)
|
||||
{
|
||||
// Face paints start at Unknown73.
|
||||
var name = $"Unknown{73 + i * 9}";
|
||||
var customizeIdx =
|
||||
(uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
|
||||
?? uint.MaxValue;
|
||||
if (customizeIdx == uint.MaxValue)
|
||||
continue;
|
||||
|
||||
var paintRow = _customizeSheet.GetRow(customizeIdx);
|
||||
// Facepaint Row from CustomizeSheet might not be set in case of unlockable facepaints.
|
||||
paintList.Add(paintRow != null
|
||||
? new Customization(CustomizationId.FacePaint, paintRow.FeatureID, paintRow.Icon, (ushort)paintRow.RowId)
|
||||
: new Customization(CustomizationId.FacePaint, (byte)i, customizeIdx));
|
||||
}
|
||||
|
||||
return paintList.ToArray();
|
||||
}
|
||||
|
||||
// Specific icons for tails or ears.
|
||||
private Customization[] GetTailEarShapes(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.TailEarShape)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.TailEarShape, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
|
||||
// Specific icons for faces.
|
||||
private Customization[] GetFaces(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.Face)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.Face, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
|
||||
// Specific icons for Hrothgar patterns.
|
||||
private Customization[] HrothgarFurPattern(CharaMakeParams row)
|
||||
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customization == CustomizationId.LipColor)?.Values
|
||||
.Select((v, i) => FromValueAndIndex(CustomizationId.LipColor, v, i)).ToArray()
|
||||
?? Array.Empty<Customization>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,31 +1,21 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
// Each Subrace and Gender combo has a customization set.
|
||||
// This describes the available customizations, their types and their names.
|
||||
public class CustomizationSet
|
||||
{
|
||||
public const int DefaultAvailable =
|
||||
(1 << (int)CustomizationId.Height)
|
||||
| (1 << (int)CustomizationId.Hairstyle)
|
||||
| (1 << (int)CustomizationId.SkinColor)
|
||||
| (1 << (int)CustomizationId.EyeColorR)
|
||||
| (1 << (int)CustomizationId.EyeColorL)
|
||||
| (1 << (int)CustomizationId.HairColor)
|
||||
| (1 << (int)CustomizationId.HighlightColor)
|
||||
| (1 << (int)CustomizationId.FacialFeaturesTattoos)
|
||||
| (1 << (int)CustomizationId.TattooColor)
|
||||
| (1 << (int)CustomizationId.LipColor)
|
||||
| (1 << (int)CustomizationId.Height);
|
||||
|
||||
internal CustomizationSet(SubRace clan, Gender gender)
|
||||
{
|
||||
Gender = gender;
|
||||
Clan = clan;
|
||||
_settingAvailable = clan.ToRace() == Race.Hrothgar && gender == Gender.Female
|
||||
? 0
|
||||
? 0u
|
||||
: DefaultAvailable;
|
||||
}
|
||||
|
||||
|
|
@ -35,39 +25,50 @@ public class CustomizationSet
|
|||
public Race Race
|
||||
=> Clan.ToRace();
|
||||
|
||||
private int _settingAvailable;
|
||||
private uint _settingAvailable;
|
||||
|
||||
internal void SetAvailable(CustomizationId id)
|
||||
=> _settingAvailable |= 1 << (int)id;
|
||||
=> _settingAvailable |= 1u << (int)id;
|
||||
|
||||
public bool IsAvailable(CustomizationId id)
|
||||
=> (_settingAvailable & (1 << (int)id)) != 0;
|
||||
=> (_settingAvailable & (1u << (int)id)) != 0;
|
||||
|
||||
public int NumEyebrows { get; internal set; }
|
||||
public int NumEyeShapes { get; internal set; }
|
||||
public int NumNoseShapes { get; internal set; }
|
||||
public int NumJawShapes { get; internal set; }
|
||||
public int NumMouthShapes { get; internal set; }
|
||||
public int NumEyebrows { get; internal init; }
|
||||
public int NumEyeShapes { get; internal init; }
|
||||
public int NumNoseShapes { get; internal init; }
|
||||
public int NumJawShapes { get; internal init; }
|
||||
public int NumMouthShapes { get; internal init; }
|
||||
|
||||
public string ToHumanReadable(CustomizationData customizationData)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
foreach (var id in Enum.GetValues<CustomizationId>().Where(IsAvailable))
|
||||
sb.AppendFormat("{0,-20}", Option(id)).Append(customizationData[id]);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
public IReadOnlyList<string> OptionName { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> Faces { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> HairStyles { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> TailEarShapes { get; internal set; } = null!;
|
||||
public IReadOnlyList<IReadOnlyList<Customization>> FeaturesTattoos { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> FacePaints { get; internal set; } = null!;
|
||||
public IReadOnlyList<string> OptionName { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> Faces { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> HairStyles { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> TailEarShapes { get; internal init; } = null!;
|
||||
public IReadOnlyList<IReadOnlyList<Customization>> FeaturesTattoos { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> FacePaints { get; internal init; } = null!;
|
||||
|
||||
public IReadOnlyList<Customization> SkinColors { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> HairColors { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> HighlightColors { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> EyeColors { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> TattooColors { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> FacePaintColorsLight { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> FacePaintColorsDark { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> LipColorsLight { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> LipColorsDark { get; internal set; } = null!;
|
||||
public IReadOnlyList<Customization> SkinColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> HairColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> HighlightColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> EyeColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> TattooColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> FacePaintColorsLight { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> FacePaintColorsDark { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> LipColorsLight { get; internal init; } = null!;
|
||||
public IReadOnlyList<Customization> LipColorsDark { get; internal init; } = null!;
|
||||
|
||||
public IReadOnlyList<CharaMakeParams.MenuType> Types { get; internal set; } = null!;
|
||||
public IReadOnlyDictionary<CharaMakeParams.MenuType, CustomizationId[]> Order { get; internal set; } = null!;
|
||||
|
||||
public IReadOnlyList<CharaMakeParams.MenuType> Types { get; internal set; } = null!;
|
||||
|
||||
public string Option(CustomizationId id)
|
||||
=> OptionName[(int)id];
|
||||
|
|
@ -154,6 +155,15 @@ public class CustomizationSet
|
|||
public CharaMakeParams.MenuType Type(CustomizationId id)
|
||||
=> Types[(int)id];
|
||||
|
||||
internal static IReadOnlyDictionary<CharaMakeParams.MenuType, CustomizationId[]> ComputeOrder(CustomizationSet set)
|
||||
{
|
||||
var ret = (CustomizationId[])Enum.GetValues(typeof(CustomizationId));
|
||||
ret[(int)CustomizationId.TattooColor] = CustomizationId.EyeColorL;
|
||||
ret[(int)CustomizationId.EyeColorL] = CustomizationId.EyeColorR;
|
||||
ret[(int)CustomizationId.EyeColorR] = CustomizationId.TattooColor;
|
||||
|
||||
return ret.Skip(2).Where(set.IsAvailable).GroupBy(set.Type).ToDictionary(k => k.Key, k => k.ToArray());
|
||||
}
|
||||
|
||||
public int Count(CustomizationId id)
|
||||
{
|
||||
|
|
@ -187,4 +197,17 @@ public class CustomizationSet
|
|||
_ => throw new ArgumentOutOfRangeException(nameof(id), id, null),
|
||||
};
|
||||
}
|
||||
|
||||
private const uint DefaultAvailable =
|
||||
(1u << (int)CustomizationId.Height)
|
||||
| (1u << (int)CustomizationId.Hairstyle)
|
||||
| (1u << (int)CustomizationId.SkinColor)
|
||||
| (1u << (int)CustomizationId.EyeColorR)
|
||||
| (1u << (int)CustomizationId.EyeColorL)
|
||||
| (1u << (int)CustomizationId.HairColor)
|
||||
| (1u << (int)CustomizationId.HighlightColor)
|
||||
| (1u << (int)CustomizationId.FacialFeaturesTattoos)
|
||||
| (1u << (int)CustomizationId.TattooColor)
|
||||
| (1u << (int)CustomizationId.LipColor)
|
||||
| (1u << (int)CustomizationId.Height);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
using System.Collections.Generic;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Customization
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
public interface ICustomizationManager
|
||||
{
|
||||
public interface ICustomizationManager
|
||||
{
|
||||
public IReadOnlyList<Race> Races { get; }
|
||||
public IReadOnlyList<SubRace> Clans { get; }
|
||||
public IReadOnlyList<Gender> Genders { get; }
|
||||
public IReadOnlyList<Race> Races { get; }
|
||||
public IReadOnlyList<SubRace> Clans { get; }
|
||||
public IReadOnlyList<Gender> Genders { get; }
|
||||
|
||||
public CustomizationSet GetList(SubRace race, Gender gender);
|
||||
public CustomizationSet GetList(SubRace race, Gender gender);
|
||||
|
||||
public ImGuiScene.TextureWrap GetIcon(uint iconId);
|
||||
public string GetName(CustomName name);
|
||||
}
|
||||
public ImGuiScene.TextureWrap GetIcon(uint iconId);
|
||||
public string GetName(CustomName name);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue