mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 10:17:23 +01:00
Starting rework.
This commit is contained in:
parent
0fc8992271
commit
7af38aa2ce
58 changed files with 8857 additions and 4923 deletions
159
Glamourer.GameData/CharacterCustomization.cs
Normal file
159
Glamourer.GameData/CharacterCustomization.cs
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
using System;
|
||||
using Glamourer.Customization;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer;
|
||||
|
||||
public readonly unsafe struct CharacterCustomization
|
||||
{
|
||||
public static readonly CharacterCustomization Null = new(null);
|
||||
|
||||
private readonly CustomizationData* _data;
|
||||
|
||||
public IntPtr Address
|
||||
=> (IntPtr)_data;
|
||||
|
||||
public CharacterCustomization(CustomizationData* data)
|
||||
=> _data = data;
|
||||
|
||||
public ref Race Race
|
||||
=> ref _data->Race;
|
||||
|
||||
public ref SubRace Clan
|
||||
=> ref _data->Clan;
|
||||
|
||||
public Gender Gender
|
||||
{
|
||||
get => _data->Gender;
|
||||
set => _data->Gender = value;
|
||||
}
|
||||
|
||||
public ref byte BodyType
|
||||
=> ref _data->BodyType;
|
||||
|
||||
public ref byte Height
|
||||
=> ref _data->Height;
|
||||
|
||||
public ref byte Face
|
||||
=> ref _data->Face;
|
||||
|
||||
public ref byte Hairstyle
|
||||
=> ref _data->Hairstyle;
|
||||
|
||||
public bool HighlightsOn
|
||||
{
|
||||
get => _data->HighlightsOn;
|
||||
set => _data->HighlightsOn = value;
|
||||
}
|
||||
|
||||
public ref byte SkinColor
|
||||
=> ref _data->SkinColor;
|
||||
|
||||
public ref byte EyeColorRight
|
||||
=> ref _data->EyeColorRight;
|
||||
|
||||
public ref byte HairColor
|
||||
=> ref _data->HairColor;
|
||||
|
||||
public ref byte HighlightsColor
|
||||
=> ref _data->HighlightsColor;
|
||||
|
||||
public ref byte FacialFeatures
|
||||
=> ref _data->FacialFeatures;
|
||||
|
||||
public ref byte TattooColor
|
||||
=> ref _data->TattooColor;
|
||||
|
||||
public ref byte Eyebrow
|
||||
=> ref _data->Eyebrow;
|
||||
|
||||
public ref byte EyeColorLeft
|
||||
=> ref _data->EyeColorLeft;
|
||||
|
||||
public byte EyeShape
|
||||
{
|
||||
get => _data->EyeShape;
|
||||
set => _data->EyeShape = value;
|
||||
}
|
||||
|
||||
public byte FacePaint
|
||||
{
|
||||
get => _data->FacePaint;
|
||||
set => _data->FacePaint = value;
|
||||
}
|
||||
|
||||
public bool FacePaintReversed
|
||||
{
|
||||
get => _data->FacePaintReversed;
|
||||
set => _data->FacePaintReversed = value;
|
||||
}
|
||||
|
||||
public byte Mouth
|
||||
{
|
||||
get => _data->Mouth;
|
||||
set => _data->Mouth = value;
|
||||
}
|
||||
|
||||
public bool SmallIris
|
||||
{
|
||||
get => _data->SmallIris;
|
||||
set => _data->SmallIris = value;
|
||||
}
|
||||
|
||||
public bool Lipstick
|
||||
{
|
||||
get => _data->Lipstick;
|
||||
set => _data->Lipstick = value;
|
||||
}
|
||||
|
||||
public ref byte Nose
|
||||
=> ref _data->Nose;
|
||||
|
||||
public ref byte Jaw
|
||||
=> ref _data->Jaw;
|
||||
|
||||
public ref byte LipColor
|
||||
=> ref _data->LipColor;
|
||||
|
||||
public ref byte MuscleMass
|
||||
=> ref _data->MuscleMass;
|
||||
|
||||
public ref byte TailShape
|
||||
=> ref _data->TailShape;
|
||||
|
||||
public ref byte BustSize
|
||||
=> ref _data->BustSize;
|
||||
|
||||
public ref byte FacePaintColor
|
||||
=> ref _data->FacePaintColor;
|
||||
|
||||
public bool FacialFeature(int idx)
|
||||
=> _data->FacialFeature(idx);
|
||||
|
||||
public void FacialFeature(int idx, bool set)
|
||||
=> _data->FacialFeature(idx, set);
|
||||
|
||||
public byte this[CustomizationId id]
|
||||
{
|
||||
get => _data->Get(id);
|
||||
set => _data->Set(id, value);
|
||||
}
|
||||
|
||||
public static implicit operator CharacterCustomization(CustomizationData* val)
|
||||
=> new(val);
|
||||
|
||||
public static implicit operator CharacterCustomization(IntPtr val)
|
||||
=> new((CustomizationData*)val);
|
||||
|
||||
public static implicit operator bool(CharacterCustomization customize)
|
||||
=> customize._data != null;
|
||||
|
||||
public static bool operator true(CharacterCustomization customize)
|
||||
=> customize._data != null;
|
||||
|
||||
public static bool operator false(CharacterCustomization customize)
|
||||
=> customize._data == null;
|
||||
|
||||
public static bool operator !(CharacterCustomization customize)
|
||||
=> customize._data == null;
|
||||
}
|
||||
105
Glamourer.GameData/CharacterEquip.cs
Normal file
105
Glamourer.GameData/CharacterEquip.cs
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
using System;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer;
|
||||
|
||||
public readonly unsafe struct CharacterEquip
|
||||
{
|
||||
public static readonly CharacterEquip Null = new(null);
|
||||
|
||||
private readonly CharacterArmor* _armor;
|
||||
|
||||
public IntPtr Address
|
||||
=> (IntPtr)_armor;
|
||||
|
||||
public ref CharacterArmor this[int idx]
|
||||
=> ref _armor[idx];
|
||||
|
||||
public ref CharacterArmor this[uint idx]
|
||||
=> ref _armor[idx];
|
||||
|
||||
public ref CharacterArmor this[EquipSlot slot]
|
||||
=> ref _armor[IndexOf(slot)];
|
||||
|
||||
|
||||
public ref CharacterArmor Head
|
||||
=> ref _armor[0];
|
||||
|
||||
public ref CharacterArmor Body
|
||||
=> ref _armor[1];
|
||||
|
||||
public ref CharacterArmor Hands
|
||||
=> ref _armor[2];
|
||||
|
||||
public ref CharacterArmor Legs
|
||||
=> ref _armor[3];
|
||||
|
||||
public ref CharacterArmor Feet
|
||||
=> ref _armor[4];
|
||||
|
||||
public ref CharacterArmor Ears
|
||||
=> ref _armor[5];
|
||||
|
||||
public ref CharacterArmor Neck
|
||||
=> ref _armor[6];
|
||||
|
||||
public ref CharacterArmor Wrists
|
||||
=> ref _armor[7];
|
||||
|
||||
public ref CharacterArmor RFinger
|
||||
=> ref _armor[8];
|
||||
|
||||
public ref CharacterArmor LFinger
|
||||
=> ref _armor[9];
|
||||
|
||||
public CharacterEquip(CharacterArmor* val)
|
||||
=> _armor = val;
|
||||
|
||||
public static implicit operator CharacterEquip(CharacterArmor* val)
|
||||
=> new(val);
|
||||
|
||||
public static implicit operator CharacterEquip(IntPtr val)
|
||||
=> new((CharacterArmor*)val);
|
||||
|
||||
public static implicit operator CharacterEquip(ReadOnlySpan<CharacterArmor> val)
|
||||
{
|
||||
if (val.Length != 10)
|
||||
throw new ArgumentException("Invalid number of equipment pieces in span.");
|
||||
|
||||
fixed (CharacterArmor* ptr = val)
|
||||
{
|
||||
return new CharacterEquip(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
public static implicit operator bool(CharacterEquip equip)
|
||||
=> equip._armor != null;
|
||||
|
||||
public static bool operator true(CharacterEquip equip)
|
||||
=> equip._armor != null;
|
||||
|
||||
public static bool operator false(CharacterEquip equip)
|
||||
=> equip._armor == null;
|
||||
|
||||
public static bool operator !(CharacterEquip equip)
|
||||
=> equip._armor == null;
|
||||
|
||||
private static int IndexOf(EquipSlot slot)
|
||||
{
|
||||
return slot switch
|
||||
{
|
||||
EquipSlot.Head => 0,
|
||||
EquipSlot.Body => 1,
|
||||
EquipSlot.Hands => 2,
|
||||
EquipSlot.Legs => 3,
|
||||
EquipSlot.Feet => 4,
|
||||
EquipSlot.Ears => 5,
|
||||
EquipSlot.Neck => 6,
|
||||
EquipSlot.Wrists => 7,
|
||||
EquipSlot.RFinger => 8,
|
||||
EquipSlot.LFinger => 9,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(slot), slot, null),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.ComponentModel;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Glamourer.Structs;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
|
@ -17,20 +20,14 @@ public static class GameData
|
|||
private static Dictionary<EquipSlot, List<Item>>? _itemsBySlot;
|
||||
private static Dictionary<byte, Job>? _jobs;
|
||||
private static Dictionary<ushort, JobGroup>? _jobGroups;
|
||||
private static SortedList<uint, ModelChara>? _models;
|
||||
private static ModelData? _models;
|
||||
private static RestrictedGear? _restrictedGear;
|
||||
|
||||
public static IReadOnlyDictionary<uint, ModelChara> Models(DataManager dataManager)
|
||||
{
|
||||
if (_models != null)
|
||||
return _models;
|
||||
public static RestrictedGear RestrictedGear(DataManager dataManager)
|
||||
=> _restrictedGear ??= new RestrictedGear(dataManager);
|
||||
|
||||
var sheet = dataManager.GetExcelSheet<ModelChara>()!;
|
||||
|
||||
_models = new SortedList<uint, ModelChara>((int)sheet.RowCount);
|
||||
foreach (var model in sheet.Where(m => m.Type != 0))
|
||||
_models.Add(model.RowId, model);
|
||||
return _models;
|
||||
}
|
||||
public static ModelData Models(DataManager dataManager)
|
||||
=> _models ??= new ModelData(dataManager);
|
||||
|
||||
public static IReadOnlyDictionary<byte, Stain> Stains(DataManager dataManager)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -39,6 +39,10 @@
|
|||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="FFXIVClientStructs">
|
||||
<HintPath>$(appdata)\XIVLauncher\addon\Hooks\dev\FFXIVClientStructs.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
</Reference>
|
||||
<Reference Include="ImGuiScene">
|
||||
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\ImGuiScene.dll</HintPath>
|
||||
<Private>False</Private>
|
||||
|
|
|
|||
75
Glamourer.GameData/ModelData.cs
Normal file
75
Glamourer.GameData/ModelData.cs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.Utility;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Companion = Lumina.Excel.GeneratedSheets.Companion;
|
||||
|
||||
namespace Glamourer;
|
||||
|
||||
public class ModelData
|
||||
{
|
||||
public struct Data
|
||||
{
|
||||
public readonly ModelChara Model;
|
||||
|
||||
public string FirstName { get; }
|
||||
public string AllNames { get; internal set; }
|
||||
|
||||
public Data(ModelChara model, string name)
|
||||
{
|
||||
Model = model;
|
||||
FirstName = $"{name} #{model.RowId:D4}";
|
||||
AllNames = $"#{model.RowId:D4}\n{name}";
|
||||
}
|
||||
}
|
||||
|
||||
private readonly SortedList<uint, Data> _models;
|
||||
|
||||
public IReadOnlyDictionary<uint, Data> Models
|
||||
=> _models;
|
||||
|
||||
public ModelData(DataManager dataManager)
|
||||
{
|
||||
var modelSheet = dataManager.GetExcelSheet<ModelChara>();
|
||||
|
||||
_models = new SortedList<uint, Data>(NpcNames.ModelCharas.Count);
|
||||
|
||||
void UpdateData(uint model, string name)
|
||||
{
|
||||
name = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(name);
|
||||
if (_models.TryGetValue(model, out var data))
|
||||
data.AllNames = $"{data.AllNames}\n{name}";
|
||||
else
|
||||
data = new Data(modelSheet!.GetRow(model)!, name);
|
||||
_models[model] = data;
|
||||
}
|
||||
|
||||
var companionSheet = dataManager.GetExcelSheet<Companion>()!;
|
||||
foreach (var companion in companionSheet.Where(c => c.Model.Row != 0 && c.Singular.RawData.Length > 0))
|
||||
UpdateData(companion.Model.Row, companion.Singular.ToDalamudString().TextValue);
|
||||
|
||||
var mountSheet = dataManager.GetExcelSheet<Mount>()!;
|
||||
foreach (var mount in mountSheet.Where(c => c.ModelChara.Row != 0 && c.Singular.RawData.Length > 0))
|
||||
UpdateData(mount.ModelChara.Row, mount.Singular.ToDalamudString().TextValue);
|
||||
|
||||
var bNpcNames = dataManager.GetExcelSheet<BNpcName>()!;
|
||||
foreach (var (model, list) in NpcNames.ModelCharas)
|
||||
{
|
||||
foreach (var nameId in list)
|
||||
{
|
||||
var name = nameId >= 0
|
||||
? bNpcNames.GetRow((uint)nameId)?.Singular.ToDalamudString().TextValue ?? string.Empty
|
||||
: NpcNames.Names[~nameId];
|
||||
if (name.Length == 0)
|
||||
continue;
|
||||
|
||||
UpdateData(model, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3254
Glamourer.GameData/ModelNames.cs
Normal file
3254
Glamourer.GameData/ModelNames.cs
Normal file
File diff suppressed because it is too large
Load diff
441
Glamourer.GameData/RestrictedGear.cs
Normal file
441
Glamourer.GameData/RestrictedGear.cs
Normal file
|
|
@ -0,0 +1,441 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Race = Penumbra.GameData.Enums.Race;
|
||||
|
||||
namespace Glamourer;
|
||||
|
||||
// Handle gender- or race-locked gear in the draw model itself.
|
||||
// Racial gear gets swapped to the correct current race and gender (it is one set each).
|
||||
// Gender-locked gear gets swapped to the equivalent set if it exists (most of them do),
|
||||
// with some items getting send to emperor's new clothes and a few funny entries.
|
||||
public class RestrictedGear
|
||||
{
|
||||
private readonly ExcelSheet<Item> _items;
|
||||
private readonly ExcelSheet<EquipRaceCategory> _categories;
|
||||
|
||||
private readonly HashSet<uint> _raceGenderSet = RaceGenderGroup.Where(c => c != 0).ToHashSet();
|
||||
private readonly Dictionary<uint, uint> _maleToFemale = new();
|
||||
private readonly Dictionary<uint, uint> _femaleToMale = new();
|
||||
|
||||
internal RestrictedGear(DataManager gameData)
|
||||
{
|
||||
_items = gameData.GetExcelSheet<Item>()!;
|
||||
_categories = gameData.GetExcelSheet<EquipRaceCategory>()!;
|
||||
AddKnown();
|
||||
UnhandledRestrictedGear(false); // Set this to true to create a print of unassigned gear on launch.
|
||||
}
|
||||
|
||||
// Resolve a model given by its model id, variant and slot for your current race and gender.
|
||||
public (bool Replaced, SetId ModelId, byte Variant) ResolveRestricted(SetId modelId, byte variant, EquipSlot slot, Race race,
|
||||
Gender gender)
|
||||
{
|
||||
var quad = modelId.Value | ((uint)variant << 16);
|
||||
// Check racial gear, this does not need slots.
|
||||
if (RaceGenderGroup.Contains(quad))
|
||||
{
|
||||
var idx = ((int)race - 1) * 2 + (gender is Gender.Female or Gender.FemaleNpc ? 1 : 0);
|
||||
var value = RaceGenderGroup[idx];
|
||||
return (value != quad, (ushort)value, (byte)(value >> 16));
|
||||
}
|
||||
|
||||
// Check gender slots. If current gender is female, check if anything needs to be changed from male to female,
|
||||
// and vice versa.
|
||||
// Some items lead to the exact same model- and variant id just gender specified,
|
||||
// so check for actual difference in the Replaced bool.
|
||||
var needle = quad | ((uint)slot.ToSlot() << 24);
|
||||
if (gender is Gender.Female or Gender.FemaleNpc && _maleToFemale.TryGetValue(needle, out var newValue)
|
||||
|| gender is Gender.Male or Gender.MaleNpc && _femaleToMale.TryGetValue(needle, out newValue))
|
||||
return (quad != newValue, (ushort)newValue, (byte)(newValue >> 16));
|
||||
|
||||
// The gear is not restricted.
|
||||
return (false, modelId, variant);
|
||||
}
|
||||
|
||||
// Add all unknown restricted gear and pair it with emperor's new gear on start up.
|
||||
// Can also print unhandled items.
|
||||
public void UnhandledRestrictedGear(bool print = false)
|
||||
{
|
||||
if (print)
|
||||
PluginLog.Information("#### MALE ONLY ######");
|
||||
|
||||
void AddEmperor(Item item, bool male, bool female)
|
||||
{
|
||||
var slot = ((EquipSlot)item.EquipSlotCategory.Row).ToSlot();
|
||||
var emperor = slot switch
|
||||
{
|
||||
EquipSlot.Head => 10032u,
|
||||
EquipSlot.Body => 10033u,
|
||||
EquipSlot.Hands => 10034u,
|
||||
EquipSlot.Legs => 10035u,
|
||||
EquipSlot.Feet => 10036u,
|
||||
EquipSlot.Ears => 09293u,
|
||||
EquipSlot.Neck => 09292u,
|
||||
EquipSlot.Wrists => 09294u,
|
||||
EquipSlot.RFinger => 09295u,
|
||||
EquipSlot.LFinger => 09295u,
|
||||
_ => 0u,
|
||||
};
|
||||
if (emperor == 0)
|
||||
return;
|
||||
|
||||
if (male)
|
||||
AddItem(item.RowId, emperor, true, false);
|
||||
if (female)
|
||||
AddItem(emperor, item.RowId, false, true);
|
||||
}
|
||||
|
||||
var unhandled = 0;
|
||||
foreach (var item in _items.Where(i => i.EquipRestriction == 2))
|
||||
{
|
||||
if (_maleToFemale.ContainsKey((uint)item.ModelMain | ((uint)((EquipSlot)item.EquipSlotCategory.Row).ToSlot() << 24)))
|
||||
continue;
|
||||
|
||||
++unhandled;
|
||||
AddEmperor(item, true, false);
|
||||
|
||||
if (print)
|
||||
PluginLog.Information($"{item.RowId:D5} {item.Name.ToDalamudString().TextValue}");
|
||||
}
|
||||
|
||||
if (print)
|
||||
PluginLog.Information("#### FEMALE ONLY ####");
|
||||
foreach (var item in _items.Where(i => i.EquipRestriction == 3))
|
||||
{
|
||||
if (_femaleToMale.ContainsKey((uint)item.ModelMain | ((uint)((EquipSlot)item.EquipSlotCategory.Row).ToSlot() << 24)))
|
||||
continue;
|
||||
|
||||
++unhandled;
|
||||
AddEmperor(item, false, true);
|
||||
|
||||
if (print)
|
||||
PluginLog.Information($"{item.RowId:D5} {item.Name.ToDalamudString().TextValue}");
|
||||
}
|
||||
|
||||
if (print)
|
||||
PluginLog.Information("#### OTHER #########");
|
||||
|
||||
foreach (var item in _items.Where(i => i.EquipRestriction > 3))
|
||||
{
|
||||
if (_raceGenderSet.Contains((uint)item.ModelMain))
|
||||
continue;
|
||||
|
||||
++unhandled;
|
||||
if (print)
|
||||
PluginLog.Information(
|
||||
$"{item.RowId:D5} {item.Name.ToDalamudString().TextValue} RestrictionGroup {_categories.GetRow(item.EquipRestriction)!.RowId:D2}");
|
||||
}
|
||||
|
||||
if (unhandled > 0)
|
||||
PluginLog.Warning("There were {Num} restricted items not handled and directed to Emperor's New Set.", unhandled);
|
||||
}
|
||||
|
||||
// Add a item redirection by its item - NOT MODEL - id.
|
||||
// This uses the items model as well as its slot.
|
||||
// Creates a <-> redirection by default but can add -> or <- redirections by setting the corresponding bools to false.
|
||||
// Prints warnings if anything does not make sense.
|
||||
private void AddItem(uint itemIdMale, uint itemIdFemale, bool addMale = true, bool addFemale = true)
|
||||
{
|
||||
if (!addMale && !addFemale)
|
||||
return;
|
||||
|
||||
var mItem = _items.GetRow(itemIdMale);
|
||||
var fItem = _items.GetRow(itemIdFemale);
|
||||
if (mItem == null || fItem == null)
|
||||
{
|
||||
PluginLog.Warning($"Could not add item {itemIdMale} or {itemIdFemale} to restricted items.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mItem.EquipRestriction != 2 && addMale)
|
||||
{
|
||||
PluginLog.Warning($"{mItem.Name.ToDalamudString().TextValue} is not restricted anymore.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (fItem.EquipRestriction != 3 && addFemale)
|
||||
{
|
||||
PluginLog.Warning($"{fItem.Name.ToDalamudString().TextValue} is not restricted anymore.");
|
||||
return;
|
||||
}
|
||||
|
||||
var mSlot = ((EquipSlot)mItem.EquipSlotCategory.Row).ToSlot();
|
||||
var fSlot = ((EquipSlot)fItem.EquipSlotCategory.Row).ToSlot();
|
||||
if (!mSlot.IsAccessory() && !mSlot.IsEquipment())
|
||||
{
|
||||
PluginLog.Warning($"{mItem.Name.ToDalamudString().TextValue} is not equippable to a known slot.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (mSlot != fSlot)
|
||||
{
|
||||
PluginLog.Warning($"{mItem.Name.ToDalamudString().TextValue} and {fItem.Name.ToDalamudString().TextValue} are not compatible.");
|
||||
return;
|
||||
}
|
||||
|
||||
var mModelIdSlot = (uint)mItem.ModelMain | ((uint)mSlot << 24);
|
||||
var fModelIdSlot = (uint)fItem.ModelMain | ((uint)fSlot << 24);
|
||||
|
||||
if (addMale)
|
||||
_maleToFemale.TryAdd(mModelIdSlot, fModelIdSlot);
|
||||
if (addFemale)
|
||||
_femaleToMale.TryAdd(fModelIdSlot, mModelIdSlot);
|
||||
}
|
||||
|
||||
// Add all currently existing and known gender restricted items.
|
||||
private void AddKnown()
|
||||
{
|
||||
AddItem(02967, 02970); // Lord's Yukata (Blue) <-> Lady's Yukata (Red)
|
||||
AddItem(02968, 02971); // Lord's Yukata (Green) <-> Lady's Yukata (Blue)
|
||||
AddItem(02969, 02972); // Lord's Yukata (Grey) <-> Lady's Yukata (Black)
|
||||
AddItem(02973, 02978); // Red Summer Top <-> Red Summer Halter
|
||||
AddItem(02974, 02979); // Green Summer Top <-> Green Summer Halter
|
||||
AddItem(02975, 02980); // Blue Summer Top <-> Blue Summer Halter
|
||||
AddItem(02976, 02981); // Solar Summer Top <-> Solar Summer Halter
|
||||
AddItem(02977, 02982); // Lunar Summer Top <-> Lunar Summer Halter
|
||||
AddItem(02996, 02997); // Hempen Undershirt <-> Hempen Camise
|
||||
AddItem(03280, 03283); // Lord's Drawers (Black) <-> Lady's Knickers (Black)
|
||||
AddItem(03281, 03284); // Lord's Drawers (White) <-> Lady's Knickers (White)
|
||||
AddItem(03282, 03285); // Lord's Drawers (Gold) <-> Lady's Knickers (Gold)
|
||||
AddItem(03286, 03291); // Red Summer Trunks <-> Red Summer Tanga
|
||||
AddItem(03287, 03292); // Green Summer Trunks <-> Green Summer Tanga
|
||||
AddItem(03288, 03293); // Blue Summer Trunks <-> Blue Summer Tanga
|
||||
AddItem(03289, 03294); // Solar Summer Trunks <-> Solar Summer Tanga
|
||||
AddItem(03290, 03295); // Lunar Summer Trunks <-> Lunar Summer Tanga
|
||||
AddItem(03307, 03308); // Hempen Underpants <-> Hempen Pantalettes
|
||||
AddItem(03748, 03749); // Lord's Clogs <-> Lady's Clogs
|
||||
AddItem(06045, 06041); // Bohemian's Coat <-> Guardian Corps Coat
|
||||
AddItem(06046, 06042); // Bohemian's Gloves <-> Guardian Corps Gauntlets
|
||||
AddItem(06047, 06043); // Bohemian's Trousers <-> Guardian Corps Skirt
|
||||
AddItem(06048, 06044); // Bohemian's Boots <-> Guardian Corps Boots
|
||||
AddItem(06094, 06098); // Summer Evening Top <-> Summer Morning Halter
|
||||
AddItem(06095, 06099); // Summer Evening Trunks <-> Summer Morning Tanga
|
||||
AddItem(06096, 06100); // Striped Summer Top <-> Striped Summer Halter
|
||||
AddItem(06097, 06101); // Striped Summer Trunks <-> Striped Summer Tanga
|
||||
AddItem(06102, 06104); // Black Summer Top <-> Black Summer Halter
|
||||
AddItem(06103, 06105); // Black Summer Trunks <-> Black Summer Tanga
|
||||
AddItem(06972, 06973); // Valentione Apron <-> Valentione Apron Dress
|
||||
AddItem(06975, 06976); // Valentione Trousers <-> Valentione Skirt
|
||||
AddItem(08532, 08535); // Lord's Yukata (Blackflame) <-> Lady's Yukata (Redfly)
|
||||
AddItem(08533, 08536); // Lord's Yukata (Whiteflame) <-> Lady's Yukata (Bluefly)
|
||||
AddItem(08534, 08537); // Lord's Yukata (Blueflame) <-> Lady's Yukata (Pinkfly)
|
||||
AddItem(08542, 08549); // Ti Leaf Lei <-> Coronal Summer Halter
|
||||
AddItem(08543, 08550); // Red Summer Maro <-> Red Summer Pareo
|
||||
AddItem(08544, 08551); // South Seas Talisman <-> Sea Breeze Summer Halter
|
||||
AddItem(08545, 08552); // Blue Summer Maro <-> Sea Breeze Summer Pareo
|
||||
AddItem(08546, 08553); // Coeurl Talisman <-> Coeurl Beach Halter
|
||||
AddItem(08547, 08554); // Coeurl Beach Maro <-> Coeurl Beach Pareo
|
||||
AddItem(08548, 08555); // Coeurl Beach Briefs <-> Coeurl Beach Tanga
|
||||
AddItem(10316, 10317); // Southern Seas Vest <-> Southern Seas Swimsuit
|
||||
AddItem(10318, 10319); // Southern Seas Trunks <-> Southern Seas Tanga
|
||||
AddItem(10320, 10321); // Striped Southern Seas Vest <-> Striped Southern Seas Swimsuit
|
||||
AddItem(13298, 13567); // Black-feathered Flat Hat <-> Red-feathered Flat Hat
|
||||
AddItem(13300, 13639); // Lord's Suikan <-> Lady's Suikan
|
||||
AddItem(13724, 13725); // Little Lord's Clogs <-> Little Lady's Clogs
|
||||
AddItem(14854, 14857); // Eastern Lord's Togi <-> Eastern Lady's Togi
|
||||
AddItem(14855, 14858); // Eastern Lord's Trousers <-> Eastern Lady's Loincloth
|
||||
AddItem(14856, 14859); // Eastern Lord's Crakows <-> Eastern Lady's Crakows
|
||||
AddItem(15639, 15642); // Far Eastern Patriarch's Hat <-> Far Eastern Matriarch's Sun Hat
|
||||
AddItem(15640, 15643); // Far Eastern Patriarch's Tunic <-> Far Eastern Matriarch's Dress
|
||||
AddItem(15641, 15644); // Far Eastern Patriarch's Longboots <-> Far Eastern Matriarch's Boots
|
||||
AddItem(15922, 15925); // Moonfire Vest <-> Moonfire Halter
|
||||
AddItem(15923, 15926); // Moonfire Trunks <-> Moonfire Tanga
|
||||
AddItem(15924, 15927); // Moonfire Caligae <-> Moonfire Sandals
|
||||
AddItem(16106, 16111); // Makai Mauler's Facemask <-> Makai Manhandler's Facemask
|
||||
AddItem(16107, 16112); // Makai Mauler's Oilskin <-> Makai Manhandler's Jerkin
|
||||
AddItem(16108, 16113); // Makai Mauler's Fingerless Gloves <-> Makai Manhandler's Fingerless Gloves
|
||||
AddItem(16109, 16114); // Makai Mauler's Leggings <-> Makai Manhandler's Quartertights
|
||||
AddItem(16110, 16115); // Makai Mauler's Boots <-> Makai Manhandler's Longboots
|
||||
AddItem(16116, 16121); // Makai Marksman's Eyepatch <-> Makai Markswoman's Ribbon
|
||||
AddItem(16117, 16122); // Makai Marksman's Battlegarb <-> Makai Markswoman's Battledress
|
||||
AddItem(16118, 16123); // Makai Marksman's Fingerless Gloves <-> Makai Markswoman's Fingerless Gloves
|
||||
AddItem(16119, 16124); // Makai Marksman's Slops <-> Makai Markswoman's Quartertights
|
||||
AddItem(16120, 16125); // Makai Marksman's Boots <-> Makai Markswoman's Longboots
|
||||
AddItem(16126, 16131); // Makai Sun Guide's Circlet <-> Makai Moon Guide's Circlet
|
||||
AddItem(16127, 16132); // Makai Sun Guide's Oilskin <-> Makai Moon Guide's Gown
|
||||
AddItem(16128, 16133); // Makai Sun Guide's Fingerless Gloves <-> Makai Moon Guide's Fingerless Gloves
|
||||
AddItem(16129, 16134); // Makai Sun Guide's Slops <-> Makai Moon Guide's Quartertights
|
||||
AddItem(16130, 16135); // Makai Sun Guide's Boots <-> Makai Moon Guide's Longboots
|
||||
AddItem(16136, 16141); // Makai Priest's Coronet <-> Makai Priestess's Headdress
|
||||
AddItem(16137, 16142); // Makai Priest's Doublet Robe <-> Makai Priestess's Jerkin
|
||||
AddItem(16138, 16143); // Makai Priest's Fingerless Gloves <-> Makai Priestess's Fingerless Gloves
|
||||
AddItem(16139, 16144); // Makai Priest's Slops <-> Makai Priestess's Skirt
|
||||
AddItem(16140, 16145); // Makai Priest's Boots <-> Makai Priestess's Longboots
|
||||
AddItem(16588, 16592); // Far Eastern Gentleman's Hat <-> Far Eastern Beauty's Hairpin
|
||||
AddItem(16589, 16593); // Far Eastern Gentleman's Robe <-> Far Eastern Beauty's Robe
|
||||
AddItem(16590, 16594); // Far Eastern Gentleman's Haidate <-> Far Eastern Beauty's Koshita
|
||||
AddItem(16591, 16595); // Far Eastern Gentleman's Boots <-> Far Eastern Beauty's Boots
|
||||
AddItem(17204, 17209); // Common Makai Mauler's Facemask <-> Common Makai Manhandler's Facemask
|
||||
AddItem(17205, 17210); // Common Makai Mauler's Oilskin <-> Common Makai Manhandler's Jerkin
|
||||
AddItem(17206, 17211); // Common Makai Mauler's Fingerless Gloves <-> Common Makai Manhandler's Fingerless Glove
|
||||
AddItem(17207, 17212); // Common Makai Mauler's Leggings <-> Common Makai Manhandler's Quartertights
|
||||
AddItem(17208, 17213); // Common Makai Mauler's Boots <-> Common Makai Manhandler's Longboots
|
||||
AddItem(17214, 17219); // Common Makai Marksman's Eyepatch <-> Common Makai Markswoman's Ribbon
|
||||
AddItem(17215, 17220); // Common Makai Marksman's Battlegarb <-> Common Makai Markswoman's Battledress
|
||||
AddItem(17216, 17221); // Common Makai Marksman's Fingerless Gloves <-> Common Makai Markswoman's Fingerless Glove
|
||||
AddItem(17217, 17222); // Common Makai Marksman's Slops <-> Common Makai Markswoman's Quartertights
|
||||
AddItem(17218, 17223); // Common Makai Marksman's Boots <-> Common Makai Markswoman's Longboots
|
||||
AddItem(17224, 17229); // Common Makai Sun Guide's Circlet <-> Common Makai Moon Guide's Circlet
|
||||
AddItem(17225, 17230); // Common Makai Sun Guide's Oilskin <-> Common Makai Moon Guide's Gown
|
||||
AddItem(17226, 17231); // Common Makai Sun Guide's Fingerless Gloves <-> Common Makai Moon Guide's Fingerless Glove
|
||||
AddItem(17227, 17232); // Common Makai Sun Guide's Slops <-> Common Makai Moon Guide's Quartertights
|
||||
AddItem(17228, 17233); // Common Makai Sun Guide's Boots <-> Common Makai Moon Guide's Longboots
|
||||
AddItem(17234, 17239); // Common Makai Priest's Coronet <-> Common Makai Priestess's Headdress
|
||||
AddItem(17235, 17240); // Common Makai Priest's Doublet Robe <-> Common Makai Priestess's Jerkin
|
||||
AddItem(17236, 17241); // Common Makai Priest's Fingerless Gloves <-> Common Makai Priestess's Fingerless Gloves
|
||||
AddItem(17237, 17242); // Common Makai Priest's Slops <-> Common Makai Priestess's Skirt
|
||||
AddItem(17238, 17243); // Common Makai Priest's Boots <-> Common Makai Priestess's Longboots
|
||||
AddItem(17481, 17476); // Royal Seneschal's Chapeau <-> Songbird Hat
|
||||
AddItem(17482, 17477); // Royal Seneschal's Coat <-> Songbird Jacket
|
||||
AddItem(17483, 17478); // Royal Seneschal's Fingerless Gloves <-> Songbird Gloves
|
||||
AddItem(17484, 17479); // Royal Seneschal's Breeches <-> Songbird Skirt
|
||||
AddItem(17485, 17480); // Royal Seneschal's Boots <-> Songbird Boots
|
||||
AddItem(20479, 20484); // Star of the Nezha Lord <-> Star of the Nezha Lady
|
||||
AddItem(20480, 20485); // Nezha Lord's Togi <-> Nezha Lady's Togi
|
||||
AddItem(20481, 20486); // Nezha Lord's Gloves <-> Nezha Lady's Gloves
|
||||
AddItem(20482, 20487); // Nezha Lord's Slops <-> Nezha Lady's Slops
|
||||
AddItem(20483, 20488); // Nezha Lord's Boots <-> Nezha Lady's Kneeboots
|
||||
AddItem(22367, 22372); // Faerie Tale Prince's Circlet <-> Faerie Tale Princess's Tiara
|
||||
AddItem(22368, 22373); // Faerie Tale Prince's Vest <-> Faerie Tale Princess's Dress
|
||||
AddItem(22369, 22374); // Faerie Tale Prince's Gloves <-> Faerie Tale Princess's Gloves
|
||||
AddItem(22370, 22375); // Faerie Tale Prince's Slops <-> Faerie Tale Princess's Long Skirt
|
||||
AddItem(22371, 22376); // Faerie Tale Prince's Boots <-> Faerie Tale Princess's Heels
|
||||
AddItem(24599, 24602); // Far Eastern Schoolboy's Hat <-> Far Eastern Schoolgirl's Hair Ribbon
|
||||
AddItem(24600, 24603); // Far Eastern Schoolboy's Hakama <-> Far Eastern Schoolgirl's Hakama
|
||||
AddItem(24601, 24604); // Far Eastern Schoolboy's Zori <-> Far Eastern Schoolgirl's Boots
|
||||
AddItem(28558, 28573); // Valentione Rose Hat <-> Valentione Rose Ribboned Hat
|
||||
AddItem(28559, 28574); // Valentione Rose Waistcoat <-> Valentione Rose Dress
|
||||
AddItem(28560, 28575); // Valentione Rose Gloves <-> Valentione Rose Ribboned Gloves
|
||||
AddItem(28561, 28576); // Valentione Rose Slacks <-> Valentione Rose Tights
|
||||
AddItem(28562, 28577); // Valentione Rose Shoes <-> Valentione Rose Heels
|
||||
AddItem(28563, 28578); // Valentione Forget-me-not Hat <-> Valentione Forget-me-not Ribboned Hat
|
||||
AddItem(28564, 28579); // Valentione Forget-me-not Waistcoat <-> Valentione Forget-me-not Dress
|
||||
AddItem(28565, 28580); // Valentione Forget-me-not Gloves <-> Valentione Forget-me-not Ribboned Gloves
|
||||
AddItem(28566, 28581); // Valentione Forget-me-not Slacks <-> Valentione Forget-me-not Tights
|
||||
AddItem(28567, 28582); // Valentione Forget-me-not Shoes <-> Valentione Forget-me-not Heels
|
||||
AddItem(28568, 28583); // Valentione Acacia Hat <-> Valentione Acacia Ribboned Hat
|
||||
AddItem(28569, 28584); // Valentione Acacia Waistcoat <-> Valentione Acacia Dress
|
||||
AddItem(28570, 28585); // Valentione Acacia Gloves <-> Valentione Acacia Ribboned Gloves
|
||||
AddItem(28571, 28586); // Valentione Acacia Slacks <-> Valentione Acacia Tights
|
||||
AddItem(28572, 28587); // Valentione Acacia Shoes <-> Valentione Acacia Heels
|
||||
AddItem(28600, 28605); // Eastern Lord Errant's Hat <-> Eastern Lady Errant's Hat
|
||||
AddItem(28601, 28606); // Eastern Lord Errant's Jacket <-> Eastern Lady Errant's Coat
|
||||
AddItem(28602, 28607); // Eastern Lord Errant's Wristbands <-> Eastern Lady Errant's Gloves
|
||||
AddItem(28603, 28608); // Eastern Lord Errant's Trousers <-> Eastern Lady Errant's Skirt
|
||||
AddItem(28604, 28609); // Eastern Lord Errant's Shoes <-> Eastern Lady Errant's Boots
|
||||
AddItem(31408, 31413); // Bergsteiger's Hat <-> Dirndl's Hat
|
||||
AddItem(31409, 31414); // Bergsteiger's Jacket <-> Dirndl's Bodice
|
||||
AddItem(31410, 31415); // Bergsteiger's Halfgloves <-> Dirndl's Wrist Torque
|
||||
AddItem(31411, 31416); // Bergsteiger's Halfslops <-> Dirndl's Long Skirt
|
||||
AddItem(31412, 31417); // Bergsteiger's Boots <-> Dirndl's Pumps
|
||||
AddItem(36336, 36337); // Omega-M Attire <-> Omega-F Attire
|
||||
AddItem(36338, 36339); // Omega-M Ear Cuffs <-> Omega-F Earrings
|
||||
AddItem(37442, 37447); // Makai Vanguard's Monocle <-> Makai Vanbreaker's Ribbon
|
||||
AddItem(37443, 37448); // Makai Vanguard's Battlegarb <-> Makai Vanbreaker's Battledress
|
||||
AddItem(37444, 37449); // Makai Vanguard's Fingerless Gloves <-> Makai Vanbreaker's Fingerless Gloves
|
||||
AddItem(37445, 37450); // Makai Vanguard's Leggings <-> Makai Vanbreaker's Quartertights
|
||||
AddItem(37446, 37451); // Makai Vanguard's Boots <-> Makai Vanbreaker's Longboots
|
||||
AddItem(37452, 37457); // Makai Harbinger's Facemask <-> Makai Harrower's Facemask
|
||||
AddItem(37453, 37458); // Makai Harbinger's Battlegarb <-> Makai Harrower's Jerkin
|
||||
AddItem(37454, 37459); // Makai Harbinger's Fingerless Gloves <-> Makai Harrower's Fingerless Gloves
|
||||
AddItem(37455, 37460); // Makai Harbinger's Leggings <-> Makai Harrower's Quartertights
|
||||
AddItem(37456, 37461); // Makai Harbinger's Boots <-> Makai Harrower's Longboots
|
||||
AddItem(37462, 37467); // Common Makai Vanguard's Monocle <-> Common Makai Vanbreaker's Ribbon
|
||||
AddItem(37463, 37468); // Common Makai Vanguard's Battlegarb <-> Common Makai Vanbreaker's Battledress
|
||||
AddItem(37464, 37469); // Common Makai Vanguard's Fingerless Gloves <-> Common Makai Vanbreaker's Fingerless Gloves
|
||||
AddItem(37465, 37470); // Common Makai Vanguard's Leggings <-> Common Makai Vanbreaker's Quartertights
|
||||
AddItem(37466, 37471); // Common Makai Vanguard's Boots <-> Common Makai Vanbreaker's Longboots
|
||||
AddItem(37472, 37477); // Common Makai Harbinger's Facemask <-> Common Makai Harrower's Facemask
|
||||
AddItem(37473, 37478); // Common Makai Harbinger's Battlegarb <-> Common Makai Harrower's Jerkin
|
||||
AddItem(37474, 37479); // Common Makai Harbinger's Fingerless Gloves <-> Common Makai Harrower's Fingerless Gloves
|
||||
AddItem(37475, 37480); // Common Makai Harbinger's Leggings <-> Common Makai Harrower's Quartertights
|
||||
AddItem(37476, 37481); // Common Makai Harbinger's Boots <-> Common Makai Harrower's Longboots
|
||||
AddItem(23003, 23008); // Mun'gaek Hat <-> Eastern Socialite's Hat
|
||||
AddItem(23004, 23009); // Mun'gaek Uibok <-> Eastern Socialite's Cheongsam
|
||||
AddItem(23005, 23010); // Mun'gaek Cuffs <-> Eastern Socialite's Gloves
|
||||
AddItem(23006, 23011); // Mun'gaek Trousers <-> Eastern Socialite's Skirt
|
||||
AddItem(23007, 23012); // Mun'gaek Boots <-> Eastern Socialite's Boots
|
||||
AddItem(24148, 24153); // Far Eastern Officer's Hat <-> Far Eastern Maiden's Hat
|
||||
AddItem(24149, 24154); // Far Eastern Officer's Robe <-> Far Eastern Maiden's Tunic
|
||||
AddItem(24150, 24155); // Far Eastern Officer's Armband <-> Far Eastern Maiden's Armband
|
||||
AddItem(24151, 24156); // Far Eastern Officer's Bottoms <-> Far Eastern Maiden's Bottoms
|
||||
AddItem(24152, 24157); // Far Eastern Officer's Boots <-> Far Eastern Maiden's Boots
|
||||
AddItem(13323, 13322); // Scion Thief's Tunic <-> Scion Conjurer's Dalmatica
|
||||
AddItem(13693, 10034, true, false); // Scion Thief's Halfgloves -> The Emperor's New Gloves
|
||||
AddItem(13694, 13691); // Scion Thief's Gaskins <-> Scion Conjurer's Chausses
|
||||
AddItem(13695, 13692); // Scion Thief's Armored Caligae <-> Scion Conjurer's Pattens
|
||||
AddItem(13326, 30063); // Scion Thaumaturge's Robe <-> Scion Sorceress's Headdress
|
||||
AddItem(13696, 30062); // Scion Thaumaturge's Monocle <-> Scion Sorceress's Robe
|
||||
AddItem(13697, 30064); // Scion Thaumaturge's Gauntlets <-> Scion Sorceress's Shadowtalons
|
||||
AddItem(13698, 10035, true, false); // Scion Thaumaturge's Gaskins -> The Emperor's New Breeches
|
||||
AddItem(13699, 30065); // Scion Thaumaturge's Moccasins <-> Scion Sorceress's High Boots
|
||||
AddItem(13327, 15942); // Scion Chronocler's Cowl <-> Scion Healer's Robe
|
||||
AddItem(13700, 10034, true, false); // Scion Chronocler's Ringbands -> The Emperor's New Gloves
|
||||
AddItem(13701, 15943); // Scion Chronocler's Tights <-> Scion Healer's Halftights
|
||||
AddItem(13702, 15944); // Scion Chronocler's Caligae <-> Scion Healer's Highboots
|
||||
AddItem(14861, 13324); // Head Engineer's Goggles <-> Scion Striker's Visor
|
||||
AddItem(14862, 13325); // Head Engineer's Attire <-> Scion Striker's Attire
|
||||
AddItem(15938, 33751); // Scion Rogue's Jacket <-> Oracle Top
|
||||
AddItem(15939, 10034, true, false); // Scion Rogue's Armguards -> The Emperor's New Gloves
|
||||
AddItem(15940, 33752); // Scion Rogue's Gaskins <-> Oracle Leggings
|
||||
AddItem(15941, 33753); // Scion Rogue's Boots <-> Oracle Pantalettes
|
||||
AddItem(16042, 16046); // Abes Jacket <-> High Summoner's Dress
|
||||
AddItem(16043, 16047); // Abes Gloves <-> High Summoner's Armlets
|
||||
AddItem(16044, 10035, true, false); // Abes Halfslops -> The Emperor's New Breeches
|
||||
AddItem(16045, 16048); // Abes Boots <-> High Summoner's Boots
|
||||
AddItem(17473, 28553); // Lord Commander's Coat <-> Majestic Dress
|
||||
AddItem(17474, 28554); // Lord Commander's Gloves <-> Majestic Wristdresses
|
||||
AddItem(10036, 28555, false); // Emperor's New Boots <- Majestic Boots
|
||||
AddItem(21021, 21026); // Werewolf Feet <-> Werewolf Legs
|
||||
AddItem(22452, 20633); // Cracked Manderville Monocle <-> Blackbosom Hat
|
||||
AddItem(22453, 20634); // Torn Manderville Coatee <-> Blackbosom Dress
|
||||
AddItem(22454, 20635); // Singed Manderville Gloves <-> Blackbosom Dress Gloves
|
||||
AddItem(22455, 10035, true, false); // Stained Manderville Bottoms -> The Emperor's New Breeches
|
||||
AddItem(22456, 20636); // Scuffed Manderville Gaiters <-> lackbosom Boots
|
||||
AddItem(23013, 21302); // Doman Liege's Dogi <-> Scion Liberator's Jacket
|
||||
AddItem(23014, 21303); // Doman Liege's Kote <-> Scion Liberator's Fingerless Gloves
|
||||
AddItem(23015, 21304); // Doman Liege's Kyakui <-> Scion Liberator's Pantalettes
|
||||
AddItem(23016, 21305); // Doman Liege's Kyahan <-> Scion Liberator's Sabatons
|
||||
AddItem(09293, 21306, false); // The Emperor's New Earrings <- Scion Liberator's Earrings
|
||||
AddItem(24158, 23008); // Leal Samurai's Kasa <-> Eastern Socialite's Hat
|
||||
AddItem(24159, 23009); // Leal Samurai's Dogi <-> Eastern Socialite's Cheongsam
|
||||
AddItem(24160, 23010); // Leal Samurai's Tekko <-> Eastern Socialite's Gloves
|
||||
AddItem(24161, 23011); // Leal Samurai's Tsutsu-hakama <-> Eastern Socialite's Skirt
|
||||
AddItem(24162, 23012); // Leal Samurai's Geta <-> Eastern Socialite's Boots
|
||||
AddItem(02966, 13321, false); // Reindeer Suit <- Antecedent's Attire
|
||||
AddItem(15479, 36843, false); // Swine Body <- Lyse's Leadership Attire
|
||||
AddItem(21941, 24999, false); // Ala Mhigan Gown <- Gown of Light
|
||||
AddItem(30757, 25000, false); // Southern Seas Skirt <- Skirt of Light
|
||||
AddItem(36821, 27933, false); // Archfiend Helm <- Scion Hearer's Hood
|
||||
AddItem(36822, 27934, false); // Archfiend Armor <- Scion Hearer's Coat
|
||||
AddItem(36825, 27935, false); // Archfiend Sabatons <- Scion Hearer's Shoes
|
||||
}
|
||||
|
||||
// The racial starter sets are available for all 4 slots each,
|
||||
// but have no associated accessories or hats.
|
||||
private static readonly uint[] RaceGenderGroup =
|
||||
{
|
||||
0x020054,
|
||||
0x020055,
|
||||
0x020056,
|
||||
0x020057,
|
||||
0x02005C,
|
||||
0x02005D,
|
||||
0x020058,
|
||||
0x020059,
|
||||
0x02005A,
|
||||
0x02005B,
|
||||
0x020101,
|
||||
0x020102,
|
||||
0x010255,
|
||||
uint.MaxValue, // TODO: Female Hrothgar
|
||||
0x0102E8,
|
||||
0x010245,
|
||||
};
|
||||
}
|
||||
|
|
@ -3,23 +3,25 @@ using Penumbra.GameData.Enums;
|
|||
|
||||
namespace Glamourer.Structs;
|
||||
|
||||
|
||||
// Turn EquipSlot into a bitfield flag enum.
|
||||
[Flags]
|
||||
public enum CharacterEquipMask : ushort
|
||||
{
|
||||
None = 0,
|
||||
None = 0,
|
||||
MainHand = 0b000000000001,
|
||||
OffHand = 0b000000000010,
|
||||
Head = 0b000000000100,
|
||||
Body = 0b000000001000,
|
||||
Hands = 0b000000010000,
|
||||
Legs = 0b000000100000,
|
||||
Feet = 0b000001000000,
|
||||
Ears = 0b000010000000,
|
||||
Neck = 0b000100000000,
|
||||
Wrists = 0b001000000000,
|
||||
RFinger = 0b010000000000,
|
||||
LFinger = 0b100000000000,
|
||||
All = 0b111111111111,
|
||||
OffHand = 0b000000000010,
|
||||
Head = 0b000000000100,
|
||||
Body = 0b000000001000,
|
||||
Hands = 0b000000010000,
|
||||
Legs = 0b000000100000,
|
||||
Feet = 0b000001000000,
|
||||
Ears = 0b000010000000,
|
||||
Neck = 0b000100000000,
|
||||
Wrists = 0b001000000000,
|
||||
RFinger = 0b010000000000,
|
||||
LFinger = 0b100000000000,
|
||||
All = 0b111111111111,
|
||||
}
|
||||
|
||||
public static class CharacterEquipMaskExtensions
|
||||
|
|
@ -28,16 +30,16 @@ public static class CharacterEquipMaskExtensions
|
|||
=> slot switch
|
||||
{
|
||||
EquipSlot.Unknown => false,
|
||||
EquipSlot.Head => mask.HasFlag(CharacterEquipMask.Head),
|
||||
EquipSlot.Body => mask.HasFlag(CharacterEquipMask.Body),
|
||||
EquipSlot.Hands => mask.HasFlag(CharacterEquipMask.Hands),
|
||||
EquipSlot.Legs => mask.HasFlag(CharacterEquipMask.Legs),
|
||||
EquipSlot.Feet => mask.HasFlag(CharacterEquipMask.Feet),
|
||||
EquipSlot.Ears => mask.HasFlag(CharacterEquipMask.Ears),
|
||||
EquipSlot.Neck => mask.HasFlag(CharacterEquipMask.Neck),
|
||||
EquipSlot.Wrists => mask.HasFlag(CharacterEquipMask.Wrists),
|
||||
EquipSlot.Head => mask.HasFlag(CharacterEquipMask.Head),
|
||||
EquipSlot.Body => mask.HasFlag(CharacterEquipMask.Body),
|
||||
EquipSlot.Hands => mask.HasFlag(CharacterEquipMask.Hands),
|
||||
EquipSlot.Legs => mask.HasFlag(CharacterEquipMask.Legs),
|
||||
EquipSlot.Feet => mask.HasFlag(CharacterEquipMask.Feet),
|
||||
EquipSlot.Ears => mask.HasFlag(CharacterEquipMask.Ears),
|
||||
EquipSlot.Neck => mask.HasFlag(CharacterEquipMask.Neck),
|
||||
EquipSlot.Wrists => mask.HasFlag(CharacterEquipMask.Wrists),
|
||||
EquipSlot.RFinger => mask.HasFlag(CharacterEquipMask.RFinger),
|
||||
EquipSlot.LFinger => mask.HasFlag(CharacterEquipMask.LFinger),
|
||||
_ => false,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,37 @@ using Penumbra.GameData.Structs;
|
|||
|
||||
namespace Glamourer.Structs;
|
||||
|
||||
// An Item wrapper struct that contains the item table, a precomputed name and the associated equip slot.
|
||||
public readonly struct Item
|
||||
{
|
||||
public readonly Lumina.Excel.GeneratedSheets.Item Base;
|
||||
public readonly string Name;
|
||||
public readonly EquipSlot EquippableTo;
|
||||
|
||||
// Obtain the main model info used by the item.
|
||||
public (SetId id, WeaponType type, ushort variant) MainModel
|
||||
=> ParseModel(EquippableTo, Base.ModelMain);
|
||||
|
||||
// Obtain the sub model info used by the item. Will be 0 if the item has no sub model.
|
||||
public (SetId id, WeaponType type, ushort variant) SubModel
|
||||
=> ParseModel(EquippableTo, Base.ModelSub);
|
||||
|
||||
public bool HasSubModel
|
||||
=> Base.ModelSub != 0;
|
||||
|
||||
// Create a new item from its sheet list with the given name and either the inferred equip slot or the given one.
|
||||
public Item(Lumina.Excel.GeneratedSheets.Item item, string name, EquipSlot slot = EquipSlot.Unknown)
|
||||
{
|
||||
Base = item;
|
||||
Name = name;
|
||||
EquippableTo = slot == EquipSlot.Unknown ? ((EquipSlot)item.EquipSlotCategory.Row).ToSlot() : slot;
|
||||
}
|
||||
|
||||
// Create empty Nothing items.
|
||||
public static Item Nothing(EquipSlot slot)
|
||||
=> new("Nothing", slot);
|
||||
|
||||
// Produce the relevant model information for a given item and equip slot.
|
||||
private static (SetId id, WeaponType type, ushort variant) ParseModel(EquipSlot slot, ulong data)
|
||||
{
|
||||
if (slot is EquipSlot.MainHand or EquipSlot.OffHand)
|
||||
|
|
@ -22,33 +51,14 @@ public readonly struct Item
|
|||
}
|
||||
}
|
||||
|
||||
public readonly Lumina.Excel.GeneratedSheets.Item Base;
|
||||
public readonly string Name;
|
||||
public readonly EquipSlot EquippableTo;
|
||||
|
||||
public (SetId id, WeaponType type, ushort variant) MainModel
|
||||
=> ParseModel(EquippableTo, Base.ModelMain);
|
||||
|
||||
public bool HasSubModel
|
||||
=> Base.ModelSub != 0;
|
||||
|
||||
public (SetId id, WeaponType type, ushort variant) SubModel
|
||||
=> ParseModel(EquippableTo, Base.ModelSub);
|
||||
|
||||
public Item(Lumina.Excel.GeneratedSheets.Item item, string name, EquipSlot slot = EquipSlot.Unknown)
|
||||
{
|
||||
Base = item;
|
||||
Name = name;
|
||||
EquippableTo = slot == EquipSlot.Unknown ? ((EquipSlot)item.EquipSlotCategory.Row).ToSlot() : slot;
|
||||
}
|
||||
|
||||
public static Item Nothing(EquipSlot slot)
|
||||
=> new("Nothing", slot);
|
||||
|
||||
// Used for 'Nothing' items.
|
||||
private Item(string name, EquipSlot slot)
|
||||
{
|
||||
Name = name;
|
||||
Base = new Lumina.Excel.GeneratedSheets.Item();
|
||||
EquippableTo = slot;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> Name;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
using Lumina.Excel.GeneratedSheets;
|
||||
using Dalamud.Utility;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Glamourer.Structs;
|
||||
|
||||
// A struct containing the different jobs the game supports.
|
||||
// Also contains the jobs Name and Abbreviation as strings.
|
||||
public readonly struct Job
|
||||
{
|
||||
public readonly string Name;
|
||||
|
|
@ -14,7 +17,10 @@ public readonly struct Job
|
|||
public Job(ClassJob job)
|
||||
{
|
||||
Base = job;
|
||||
Name = job.Name.ToString();
|
||||
Abbreviation = job.Abbreviation.ToString();
|
||||
Name = job.Name.ToDalamudString().ToString();
|
||||
Abbreviation = job.Abbreviation.ToDalamudString().ToString();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> Name;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,29 +1,31 @@
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Glamourer.Structs;
|
||||
|
||||
// The game specifies different job groups that can contain specific jobs or not.
|
||||
public readonly struct JobGroup
|
||||
{
|
||||
public readonly string Name;
|
||||
private readonly ulong _flags;
|
||||
public readonly int Count;
|
||||
public readonly uint Id;
|
||||
public readonly string Name;
|
||||
public readonly int Count;
|
||||
public readonly uint Id;
|
||||
private readonly ulong _flags;
|
||||
|
||||
// Create a job group from a given category and the ClassJob sheet.
|
||||
// It looks up the different jobs contained in the category and sets the flags appropriately.
|
||||
public JobGroup(ClassJobCategory group, ExcelSheet<ClassJob> jobs)
|
||||
{
|
||||
Count = 0;
|
||||
Count = 0;
|
||||
_flags = 0ul;
|
||||
Id = group.RowId;
|
||||
Name = group.Name.ToString();
|
||||
Id = group.RowId;
|
||||
Name = group.Name.ToString();
|
||||
|
||||
Debug.Assert(jobs.RowCount < 64);
|
||||
foreach (var job in jobs)
|
||||
{
|
||||
var abbr = job.Abbreviation.ToString();
|
||||
if (!abbr.Any())
|
||||
if (abbr.Length == 0)
|
||||
continue;
|
||||
|
||||
var prop = group.GetType().GetProperty(abbr);
|
||||
|
|
@ -37,9 +39,11 @@ public readonly struct JobGroup
|
|||
}
|
||||
}
|
||||
|
||||
// Check if a job is contained inside this group.
|
||||
public bool Fits(Job job)
|
||||
=> Fits(job.Id);
|
||||
|
||||
// Check if a job is contained inside this group.
|
||||
public bool Fits(uint jobId)
|
||||
{
|
||||
var flag = 1ul << (int)jobId;
|
||||
|
|
|
|||
|
|
@ -1,22 +1,28 @@
|
|||
using Penumbra.GameData.Structs;
|
||||
using Dalamud.Utility;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Structs;
|
||||
|
||||
// A wrapper for the clothing dyes the game provides with their RGBA color value, game ID, unmodified color value and name.
|
||||
public readonly struct Stain
|
||||
{
|
||||
public readonly string Name;
|
||||
public readonly uint RgbaColor;
|
||||
// An empty stain with transparent color.
|
||||
public static readonly Stain None = new("None");
|
||||
|
||||
public readonly string Name;
|
||||
public readonly uint RgbaColor;
|
||||
|
||||
// Combine the Id byte with the 3 bytes of color values.
|
||||
private readonly uint _seColorId;
|
||||
|
||||
public byte R
|
||||
=> (byte)(RgbaColor & 0xFF);
|
||||
|
||||
public byte G
|
||||
=> (byte)(RgbaColor >> 8 & 0xFF);
|
||||
=> (byte)((RgbaColor >> 8) & 0xFF);
|
||||
|
||||
public byte B
|
||||
=> (byte)(RgbaColor >> 16 & 0xFF);
|
||||
=> (byte)((RgbaColor >> 16) & 0xFF);
|
||||
|
||||
public byte Intensity
|
||||
=> (byte)((1 + R + G + B) / 3);
|
||||
|
|
@ -27,23 +33,22 @@ public readonly struct Stain
|
|||
public StainId RowIndex
|
||||
=> (StainId)(_seColorId >> 24);
|
||||
|
||||
|
||||
// R and B need to be shuffled and Alpha set to max.
|
||||
public static uint SeColorToRgba(uint color)
|
||||
=> (color & 0xFF) << 16 | color >> 16 & 0xFF | color & 0xFF00 | 0xFF000000;
|
||||
=> ((color & 0xFF) << 16) | ((color >> 16) & 0xFF) | (color & 0xFF00) | 0xFF000000;
|
||||
|
||||
public Stain(byte index, Lumina.Excel.GeneratedSheets.Stain stain)
|
||||
{
|
||||
Name = stain.Name.ToString();
|
||||
_seColorId = stain.Color | (uint)index << 24;
|
||||
RgbaColor = SeColorToRgba(stain.Color);
|
||||
Name = stain.Name.ToDalamudString().ToString();
|
||||
_seColorId = stain.Color | ((uint)index << 24);
|
||||
RgbaColor = SeColorToRgba(stain.Color);
|
||||
}
|
||||
|
||||
public static readonly Stain None = new("None");
|
||||
|
||||
// Only used by None.
|
||||
private Stain(string name)
|
||||
{
|
||||
Name = name;
|
||||
Name = name;
|
||||
_seColorId = 0;
|
||||
RgbaColor = 0;
|
||||
RgbaColor = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue