Remove GameData, move a bunch of customization data to Penumbra.GameData and the rest to Glamourer, update accordingly. Some reformatting and cleanup.

This commit is contained in:
Ottermandias 2023-12-22 14:20:50 +01:00
parent e9d0e61b4c
commit 987c26a51d
83 changed files with 444 additions and 1620 deletions

View file

@ -1,46 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Logging;
using Dalamud.Plugin.Services;
namespace Glamourer.Customization;
// 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
{
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(IDataManager gameData, IPluginLog log)
{
try
{
_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)
{
log.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>();
}
}
}

View file

@ -1,124 +0,0 @@
using System;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Customization;
public unsafe struct Customize
{
public CustomizeArray Data;
public Customize(in CustomizeArray data)
{
Data = data.Clone();
}
public Race Race
{
get => (Race)Data.Get(CustomizeIndex.Race).Value;
set => Data.Set(CustomizeIndex.Race, (CustomizeValue)(byte)value);
}
public Gender Gender
{
get => (Gender)Data.Get(CustomizeIndex.Gender).Value + 1;
set => Data.Set(CustomizeIndex.Gender, (CustomizeValue)(byte)value - 1);
}
public CustomizeValue BodyType
{
get => Data.Get(CustomizeIndex.BodyType);
set => Data.Set(CustomizeIndex.BodyType, value);
}
public SubRace Clan
{
get => (SubRace)Data.Get(CustomizeIndex.Clan).Value;
set => Data.Set(CustomizeIndex.Clan, (CustomizeValue)(byte)value);
}
public CustomizeValue Face
{
get => Data.Get(CustomizeIndex.Face);
set => Data.Set(CustomizeIndex.Face, value);
}
public static readonly Customize Default = GenerateDefault();
public static readonly Customize Empty = new();
public CustomizeValue Get(CustomizeIndex index)
=> Data.Get(index);
public bool Set(CustomizeIndex flag, CustomizeValue index)
=> Data.Set(flag, index);
public bool Equals(Customize other)
=> Equals(Data, other.Data);
public CustomizeValue this[CustomizeIndex index]
{
get => Get(index);
set => Set(index, value);
}
private static Customize GenerateDefault()
{
var ret = new Customize
{
Race = Race.Hyur,
Clan = SubRace.Midlander,
Gender = Gender.Male,
};
ret.Set(CustomizeIndex.BodyType, (CustomizeValue)1);
ret.Set(CustomizeIndex.Height, (CustomizeValue)50);
ret.Set(CustomizeIndex.Face, (CustomizeValue)1);
ret.Set(CustomizeIndex.Hairstyle, (CustomizeValue)1);
ret.Set(CustomizeIndex.SkinColor, (CustomizeValue)1);
ret.Set(CustomizeIndex.EyeColorRight, (CustomizeValue)1);
ret.Set(CustomizeIndex.HighlightsColor, (CustomizeValue)1);
ret.Set(CustomizeIndex.TattooColor, (CustomizeValue)1);
ret.Set(CustomizeIndex.Eyebrows, (CustomizeValue)1);
ret.Set(CustomizeIndex.EyeColorLeft, (CustomizeValue)1);
ret.Set(CustomizeIndex.EyeShape, (CustomizeValue)1);
ret.Set(CustomizeIndex.Nose, (CustomizeValue)1);
ret.Set(CustomizeIndex.Jaw, (CustomizeValue)1);
ret.Set(CustomizeIndex.Mouth, (CustomizeValue)1);
ret.Set(CustomizeIndex.LipColor, (CustomizeValue)1);
ret.Set(CustomizeIndex.MuscleMass, (CustomizeValue)50);
ret.Set(CustomizeIndex.TailShape, (CustomizeValue)1);
ret.Set(CustomizeIndex.BustSize, (CustomizeValue)50);
ret.Set(CustomizeIndex.FacePaint, (CustomizeValue)1);
ret.Set(CustomizeIndex.FacePaintColor, (CustomizeValue)1);
return ret;
}
public void Load(Customize other)
=> Data.Read(&other.Data);
public readonly void Write(nint target)
=> Data.Write((void*)target);
public bool LoadBase64(string data)
=> Data.LoadBase64(data);
public readonly string WriteBase64()
=> Data.WriteBase64();
public static CustomizeFlag Compare(Customize lhs, Customize rhs)
{
CustomizeFlag ret = 0;
foreach (var idx in Enum.GetValues<CustomizeIndex>())
{
var l = lhs[idx];
var r = rhs[idx];
if (l.Value != r.Value)
ret |= idx.ToFlag();
}
return ret;
}
public override string ToString()
=> Data.ToString();
}

View file

@ -1,114 +0,0 @@
using System;
using System.Runtime.CompilerServices;
namespace Glamourer.Customization;
[Flags]
public enum CustomizeFlag : ulong
{
Invalid = 0,
Race = 1ul << CustomizeIndex.Race,
Gender = 1ul << CustomizeIndex.Gender,
BodyType = 1ul << CustomizeIndex.BodyType,
Height = 1ul << CustomizeIndex.Height,
Clan = 1ul << CustomizeIndex.Clan,
Face = 1ul << CustomizeIndex.Face,
Hairstyle = 1ul << CustomizeIndex.Hairstyle,
Highlights = 1ul << CustomizeIndex.Highlights,
SkinColor = 1ul << CustomizeIndex.SkinColor,
EyeColorRight = 1ul << CustomizeIndex.EyeColorRight,
HairColor = 1ul << CustomizeIndex.HairColor,
HighlightsColor = 1ul << CustomizeIndex.HighlightsColor,
FacialFeature1 = 1ul << CustomizeIndex.FacialFeature1,
FacialFeature2 = 1ul << CustomizeIndex.FacialFeature2,
FacialFeature3 = 1ul << CustomizeIndex.FacialFeature3,
FacialFeature4 = 1ul << CustomizeIndex.FacialFeature4,
FacialFeature5 = 1ul << CustomizeIndex.FacialFeature5,
FacialFeature6 = 1ul << CustomizeIndex.FacialFeature6,
FacialFeature7 = 1ul << CustomizeIndex.FacialFeature7,
LegacyTattoo = 1ul << CustomizeIndex.LegacyTattoo,
TattooColor = 1ul << CustomizeIndex.TattooColor,
Eyebrows = 1ul << CustomizeIndex.Eyebrows,
EyeColorLeft = 1ul << CustomizeIndex.EyeColorLeft,
EyeShape = 1ul << CustomizeIndex.EyeShape,
SmallIris = 1ul << CustomizeIndex.SmallIris,
Nose = 1ul << CustomizeIndex.Nose,
Jaw = 1ul << CustomizeIndex.Jaw,
Mouth = 1ul << CustomizeIndex.Mouth,
Lipstick = 1ul << CustomizeIndex.Lipstick,
LipColor = 1ul << CustomizeIndex.LipColor,
MuscleMass = 1ul << CustomizeIndex.MuscleMass,
TailShape = 1ul << CustomizeIndex.TailShape,
BustSize = 1ul << CustomizeIndex.BustSize,
FacePaint = 1ul << CustomizeIndex.FacePaint,
FacePaintReversed = 1ul << CustomizeIndex.FacePaintReversed,
FacePaintColor = 1ul << CustomizeIndex.FacePaintColor,
}
public static class CustomizeFlagExtensions
{
public const CustomizeFlag All = (CustomizeFlag)(((ulong)CustomizeFlag.FacePaintColor << 1) - 1ul);
public const CustomizeFlag AllRelevant = All & ~CustomizeFlag.BodyType & ~CustomizeFlag.Race;
public const CustomizeFlag RedrawRequired =
CustomizeFlag.Race | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.Face | CustomizeFlag.BodyType;
public static CustomizeFlag FixApplication(this CustomizeFlag flag, CustomizationSet set)
=> flag & (set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender);
public static bool RequiresRedraw(this CustomizeFlag flags)
=> (flags & RedrawRequired) != 0;
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static CustomizeIndex ToIndex(this CustomizeFlag flag)
=> flag switch
{
CustomizeFlag.Race => CustomizeIndex.Race,
CustomizeFlag.Gender => CustomizeIndex.Gender,
CustomizeFlag.BodyType => CustomizeIndex.BodyType,
CustomizeFlag.Height => CustomizeIndex.Height,
CustomizeFlag.Clan => CustomizeIndex.Clan,
CustomizeFlag.Face => CustomizeIndex.Face,
CustomizeFlag.Hairstyle => CustomizeIndex.Hairstyle,
CustomizeFlag.Highlights => CustomizeIndex.Highlights,
CustomizeFlag.SkinColor => CustomizeIndex.SkinColor,
CustomizeFlag.EyeColorRight => CustomizeIndex.EyeColorRight,
CustomizeFlag.HairColor => CustomizeIndex.HairColor,
CustomizeFlag.HighlightsColor => CustomizeIndex.HighlightsColor,
CustomizeFlag.FacialFeature1 => CustomizeIndex.FacialFeature1,
CustomizeFlag.FacialFeature2 => CustomizeIndex.FacialFeature2,
CustomizeFlag.FacialFeature3 => CustomizeIndex.FacialFeature3,
CustomizeFlag.FacialFeature4 => CustomizeIndex.FacialFeature4,
CustomizeFlag.FacialFeature5 => CustomizeIndex.FacialFeature5,
CustomizeFlag.FacialFeature6 => CustomizeIndex.FacialFeature6,
CustomizeFlag.FacialFeature7 => CustomizeIndex.FacialFeature7,
CustomizeFlag.LegacyTattoo => CustomizeIndex.LegacyTattoo,
CustomizeFlag.TattooColor => CustomizeIndex.TattooColor,
CustomizeFlag.Eyebrows => CustomizeIndex.Eyebrows,
CustomizeFlag.EyeColorLeft => CustomizeIndex.EyeColorLeft,
CustomizeFlag.EyeShape => CustomizeIndex.EyeShape,
CustomizeFlag.SmallIris => CustomizeIndex.SmallIris,
CustomizeFlag.Nose => CustomizeIndex.Nose,
CustomizeFlag.Jaw => CustomizeIndex.Jaw,
CustomizeFlag.Mouth => CustomizeIndex.Mouth,
CustomizeFlag.Lipstick => CustomizeIndex.Lipstick,
CustomizeFlag.LipColor => CustomizeIndex.LipColor,
CustomizeFlag.MuscleMass => CustomizeIndex.MuscleMass,
CustomizeFlag.TailShape => CustomizeIndex.TailShape,
CustomizeFlag.BustSize => CustomizeIndex.BustSize,
CustomizeFlag.FacePaint => CustomizeIndex.FacePaint,
CustomizeFlag.FacePaintReversed => CustomizeIndex.FacePaintReversed,
CustomizeFlag.FacePaintColor => CustomizeIndex.FacePaintColor,
_ => (CustomizeIndex)byte.MaxValue,
};
public static bool SetIfDifferent(ref this CustomizeFlag flags, CustomizeFlag flag, bool value)
{
var newValue = value ? flags | flag : flags & ~flag;
if (newValue == flags)
return false;
flags = newValue;
return true;
}
}

View file

@ -1,183 +0,0 @@
using System;
using System.Linq;
using System.Runtime.CompilerServices;
namespace Glamourer.Customization;
public enum CustomizeIndex : byte
{
Race,
Gender,
BodyType,
Height,
Clan,
Face,
Hairstyle,
Highlights,
SkinColor,
EyeColorRight,
HairColor,
HighlightsColor,
FacialFeature1,
FacialFeature2,
FacialFeature3,
FacialFeature4,
FacialFeature5,
FacialFeature6,
FacialFeature7,
LegacyTattoo,
TattooColor,
Eyebrows,
EyeColorLeft,
EyeShape,
SmallIris,
Nose,
Jaw,
Mouth,
Lipstick,
LipColor,
MuscleMass,
TailShape,
BustSize,
FacePaint,
FacePaintReversed,
FacePaintColor,
}
public static class CustomizationExtensions
{
public const int NumIndices = (int)CustomizeIndex.FacePaintColor + 1;
public static readonly CustomizeIndex[] All = Enum.GetValues<CustomizeIndex>()
.Where(v => v is not CustomizeIndex.Race and not CustomizeIndex.BodyType).ToArray();
public static readonly CustomizeIndex[] AllBasic = All
.Where(v => v is not CustomizeIndex.Gender and not CustomizeIndex.Clan).ToArray();
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static (int ByteIdx, byte Mask) ToByteAndMask(this CustomizeIndex index)
=> index switch
{
CustomizeIndex.Race => (0, 0xFF),
CustomizeIndex.Gender => (1, 0xFF),
CustomizeIndex.BodyType => (2, 0xFF),
CustomizeIndex.Height => (3, 0xFF),
CustomizeIndex.Clan => (4, 0xFF),
CustomizeIndex.Face => (5, 0xFF),
CustomizeIndex.Hairstyle => (6, 0xFF),
CustomizeIndex.Highlights => (7, 0x80),
CustomizeIndex.SkinColor => (8, 0xFF),
CustomizeIndex.EyeColorRight => (9, 0xFF),
CustomizeIndex.HairColor => (10, 0xFF),
CustomizeIndex.HighlightsColor => (11, 0xFF),
CustomizeIndex.FacialFeature1 => (12, 0x01),
CustomizeIndex.FacialFeature2 => (12, 0x02),
CustomizeIndex.FacialFeature3 => (12, 0x04),
CustomizeIndex.FacialFeature4 => (12, 0x08),
CustomizeIndex.FacialFeature5 => (12, 0x10),
CustomizeIndex.FacialFeature6 => (12, 0x20),
CustomizeIndex.FacialFeature7 => (12, 0x40),
CustomizeIndex.LegacyTattoo => (12, 0x80),
CustomizeIndex.TattooColor => (13, 0xFF),
CustomizeIndex.Eyebrows => (14, 0xFF),
CustomizeIndex.EyeColorLeft => (15, 0xFF),
CustomizeIndex.EyeShape => (16, 0x7F),
CustomizeIndex.SmallIris => (16, 0x80),
CustomizeIndex.Nose => (17, 0xFF),
CustomizeIndex.Jaw => (18, 0xFF),
CustomizeIndex.Mouth => (19, 0x7F),
CustomizeIndex.Lipstick => (19, 0x80),
CustomizeIndex.LipColor => (20, 0xFF),
CustomizeIndex.MuscleMass => (21, 0xFF),
CustomizeIndex.TailShape => (22, 0xFF),
CustomizeIndex.BustSize => (23, 0xFF),
CustomizeIndex.FacePaint => (24, 0x7F),
CustomizeIndex.FacePaintReversed => (24, 0x80),
CustomizeIndex.FacePaintColor => (25, 0xFF),
_ => (0, 0x00),
};
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static CustomizeFlag ToFlag(this CustomizeIndex index)
=> (CustomizeFlag)(1ul << (int)index);
public static string ToDefaultName(this CustomizeIndex customizeIndex)
=> customizeIndex switch
{
CustomizeIndex.Race => "Race",
CustomizeIndex.Gender => "Gender",
CustomizeIndex.BodyType => "Body Type",
CustomizeIndex.Height => "Height",
CustomizeIndex.Clan => "Clan",
CustomizeIndex.Face => "Head Style",
CustomizeIndex.Hairstyle => "Hair Style",
CustomizeIndex.Highlights => "Enable Highlights",
CustomizeIndex.SkinColor => "Skin Color",
CustomizeIndex.EyeColorRight => "Left Eye", // inverted due to compatibility fuckup.
CustomizeIndex.HairColor => "Hair Color",
CustomizeIndex.HighlightsColor => "Highlights Color",
CustomizeIndex.TattooColor => "Tattoo Color",
CustomizeIndex.Eyebrows => "Eyebrow Style",
CustomizeIndex.EyeColorLeft => "Right Eye", // inverted due to compatibility fuckup.
CustomizeIndex.EyeShape => "Small Pupils",
CustomizeIndex.Nose => "Nose Style",
CustomizeIndex.Jaw => "Jaw Style",
CustomizeIndex.Mouth => "Mouth Style",
CustomizeIndex.MuscleMass => "Muscle Tone",
CustomizeIndex.TailShape => "Tail Shape",
CustomizeIndex.BustSize => "Bust Size",
CustomizeIndex.FacePaint => "Face Paint",
CustomizeIndex.FacePaintColor => "Face Paint Color",
CustomizeIndex.LipColor => "Lip Color",
CustomizeIndex.FacialFeature1 => "Facial Feature 1",
CustomizeIndex.FacialFeature2 => "Facial Feature 2",
CustomizeIndex.FacialFeature3 => "Facial Feature 3",
CustomizeIndex.FacialFeature4 => "Facial Feature 4",
CustomizeIndex.FacialFeature5 => "Facial Feature 5",
CustomizeIndex.FacialFeature6 => "Facial Feature 6",
CustomizeIndex.FacialFeature7 => "Facial Feature 7",
CustomizeIndex.LegacyTattoo => "Legacy Tattoo",
CustomizeIndex.SmallIris => "Small Iris",
CustomizeIndex.Lipstick => "Enable Lipstick",
CustomizeIndex.FacePaintReversed => "Reverse Face Paint",
_ => string.Empty,
};
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static unsafe CustomizeValue Get(this in Penumbra.GameData.Structs.CustomizeArray data, CustomizeIndex index)
{
var (offset, mask) = index.ToByteAndMask();
return (CustomizeValue)(data.Data[offset] & mask);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static unsafe bool Set(this ref Penumbra.GameData.Structs.CustomizeArray data, CustomizeIndex index, CustomizeValue value)
{
var (offset, mask) = index.ToByteAndMask();
return mask != 0xFF
? SetIfDifferentMasked(ref data.Data[offset], value, mask)
: SetIfDifferent(ref data.Data[offset], value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static bool SetIfDifferentMasked(ref byte oldValue, CustomizeValue newValue, byte mask)
{
var tmp = (byte)(newValue.Value & mask);
tmp = (byte)(tmp | (oldValue & ~mask));
if (oldValue == tmp)
return false;
oldValue = tmp;
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static bool SetIfDifferent(ref byte oldValue, CustomizeValue newValue)
{
if (oldValue == newValue.Value)
return false;
oldValue = newValue.Value;
return true;
}
}

View file

@ -1,34 +0,0 @@
namespace Glamourer.Customization;
public record struct CustomizeValue(byte Value)
{
public static readonly CustomizeValue Zero = new(0);
public static readonly CustomizeValue Max = new(0xFF);
public static CustomizeValue Bool(bool b)
=> b ? Max : Zero;
public static explicit operator CustomizeValue(byte value)
=> new(value);
public static CustomizeValue operator ++(CustomizeValue v)
=> new(++v.Value);
public static CustomizeValue operator --(CustomizeValue v)
=> new(--v.Value);
public static bool operator <(CustomizeValue v, int count)
=> v.Value < count;
public static bool operator >(CustomizeValue v, int count)
=> v.Value > count;
public static CustomizeValue operator +(CustomizeValue v, int rhs)
=> new((byte)(v.Value + rhs));
public static CustomizeValue operator -(CustomizeValue v, int rhs)
=> new((byte)(v.Value - rhs));
public override string ToString()
=> Value.ToString();
}

View file

@ -1,149 +0,0 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Dalamud.Memory;
using Penumbra.GameData.Structs;
namespace Glamourer.Customization;
[StructLayout(LayoutKind.Explicit, Size = Size)]
public unsafe struct DatCharacterFile
{
public const int Size = 4 + 4 + 4 + 4 + CustomizeArray.Size + 2 + 4 + 41 * 4; // 212
[FieldOffset(0)]
private fixed byte _data[Size];
[FieldOffset(0)]
public readonly uint Magic = 0x2013FF14;
[FieldOffset(4)]
public readonly uint Version = 0x05;
[FieldOffset(8)]
private uint _checksum;
[FieldOffset(12)]
private readonly uint _padding = 0;
[FieldOffset(16)]
private CustomizeArray _customize;
[FieldOffset(16 + CustomizeArray.Size)]
private ushort _voice;
[FieldOffset(16 + CustomizeArray.Size + 2)]
private uint _timeStamp;
[FieldOffset(Size - 41 * 4)]
private fixed byte _description[41 * 4];
public readonly void Write(Stream stream)
{
for (var i = 0; i < Size; ++i)
stream.WriteByte(_data[i]);
}
public static bool Read(Stream stream, out DatCharacterFile file)
{
if (stream.Length - stream.Position != Size)
{
file = default;
return false;
}
file = new DatCharacterFile(stream);
return true;
}
private DatCharacterFile(Stream stream)
{
for (var i = 0; i < Size; ++i)
_data[i] = (byte)stream.ReadByte();
}
public DatCharacterFile(in Customize customize, byte voice, string text)
{
SetCustomize(customize);
SetVoice(voice);
SetTime(DateTimeOffset.UtcNow);
SetDescription(text);
_checksum = CalculateChecksum();
}
public readonly uint CalculateChecksum()
{
var ret = 0u;
for (var i = 16; i < Size; i++)
ret ^= (uint)(_data[i] << ((i - 16) % 24));
return ret;
}
public readonly uint Checksum
=> _checksum;
public Customize Customize
{
readonly get => new(_customize);
set
{
SetCustomize(value);
_checksum = CalculateChecksum();
}
}
public ushort Voice
{
readonly get => _voice;
set
{
SetVoice(value);
_checksum = CalculateChecksum();
}
}
public string Description
{
readonly get
{
fixed (byte* ptr = _description)
{
return MemoryHelper.ReadStringNullTerminated((nint)ptr);
}
}
set
{
SetDescription(value);
_checksum = CalculateChecksum();
}
}
public DateTimeOffset Time
{
readonly get => DateTimeOffset.FromUnixTimeSeconds(_timeStamp);
set
{
SetTime(value);
_checksum = CalculateChecksum();
}
}
private void SetTime(DateTimeOffset time)
=> _timeStamp = (uint)time.ToUnixTimeSeconds();
private void SetCustomize(in Customize customize)
=> _customize = customize.Data.Clone();
private void SetVoice(ushort voice)
=> _voice = voice;
private void SetDescription(string text)
{
fixed (byte* ptr = _description)
{
var span = new Span<byte>(ptr, 41 * 4);
Encoding.UTF8.GetBytes(text.AsSpan(0, Math.Min(40, text.Length)), span);
}
}
}

View file

@ -1,88 +0,0 @@
using System.Collections.Generic;
using System.Linq;
using Dalamud;
using Dalamud.Plugin.Services;
using Glamourer.Structs;
using Lumina.Excel.GeneratedSheets;
namespace Glamourer;
public static class GameData
{
private static Dictionary<byte, Job>? _jobs;
private static Dictionary<ushort, JobGroup>? _jobGroups;
private static JobGroup[]? _allJobGroups;
public static IReadOnlyDictionary<byte, Job> Jobs(IDataManager dataManager)
{
if (_jobs != null)
return _jobs;
var sheet = dataManager.GetExcelSheet<ClassJob>()!;
_jobs = sheet.Where(j => j.Abbreviation.RawData.Length > 0).ToDictionary(j => (byte)j.RowId, j => new Job(j));
return _jobs;
}
public static IReadOnlyList<JobGroup> AllJobGroups(IDataManager dataManager)
{
if (_allJobGroups != null)
return _allJobGroups;
var sheet = dataManager.GetExcelSheet<ClassJobCategory>()!;
var jobs = dataManager.GetExcelSheet<ClassJob>(ClientLanguage.English)!;
_allJobGroups = sheet.Select(j => new JobGroup(j, jobs)).ToArray();
return _allJobGroups;
}
public static IReadOnlyDictionary<ushort, JobGroup> JobGroups(IDataManager dataManager)
{
if (_jobGroups != null)
return _jobGroups;
static bool ValidIndex(uint idx)
{
if (idx is > 0 and < 36)
return true;
return idx switch
{
// Single jobs and big groups
91 => true,
92 => true,
96 => true,
98 => true,
99 => true,
111 => true,
112 => true,
129 => true,
149 => true,
150 => true,
156 => true,
157 => true,
158 => true,
159 => true,
180 => true,
181 => true,
188 => true,
189 => true,
// Class + Job
38 => true,
41 => true,
44 => true,
47 => true,
50 => true,
53 => true,
55 => true,
69 => true,
68 => true,
93 => true,
_ => false,
};
}
_jobGroups = AllJobGroups(dataManager).Where(j => ValidIndex(j.Id))
.ToDictionary(j => (ushort) j.Id, j => j);
return _jobGroups;
}
}

View file

@ -1,61 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<LangVersion>preview</LangVersion>
<RootNamespace>Glamourer</RootNamespace>
<AssemblyName>Glamourer.GameData</AssemblyName>
<FileVersion>1.0.0.0</FileVersion>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<Company>SoftOtter</Company>
<Product>Glamourer</Product>
<Copyright>Copyright © 2020</Copyright>
<Deterministic>true</Deterministic>
<OutputType>Library</OutputType>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
<OutputPath>bin\$(Configuration)\</OutputPath>
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugType>full</DebugType>
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
</PropertyGroup>
<PropertyGroup>
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
</PropertyGroup>
<ItemGroup>
<Reference Include="Dalamud">
<HintPath>$(DALAMUD_ROOT)\Dalamud.dll</HintPath>
<HintPath>..\libs\Dalamud.dll</HintPath>
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Dalamud.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Lumina">
<HintPath>$(DALAMUD_ROOT)\Lumina.dll</HintPath>
<HintPath>..\libs\Lumina.dll</HintPath>
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Lumina.Excel">
<HintPath>$(DALAMUD_ROOT)\Lumina.Excel.dll</HintPath>
<HintPath>..\libs\Lumina.Excel.dll</HintPath>
<HintPath>$(AppData)\XIVLauncher\addon\Hooks\dev\Lumina.Excel.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Penumbra.GameData">
<HintPath>..\..\Penumbra\Penumbra\bin\$(Configuration)\net472\Penumbra.GameData.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Folder Include="Util\" />
</ItemGroup>
</Project>

View file

@ -1,72 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net7.0-windows</TargetFramework>
<LangVersion>preview</LangVersion>
<PlatformTarget>x64</PlatformTarget>
<RootNamespace>Glamourer</RootNamespace>
<AssemblyName>Glamourer.GameData</AssemblyName>
<FileVersion>1.0.0.0</FileVersion>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<Company>SoftOtter</Company>
<Product>Glamourer</Product>
<Copyright>Copyright © 2020</Copyright>
<Deterministic>true</Deterministic>
<OutputType>Library</OutputType>
<WarningLevel>4</WarningLevel>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
<OutputPath>bin\$(Configuration)\</OutputPath>
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugType>full</DebugType>
<DefineConstants>DEBUG;TRACE</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
</PropertyGroup>
<PropertyGroup>
<MSBuildWarningsAsMessages>$(MSBuildWarningsAsMessages);MSB3277</MSBuildWarningsAsMessages>
</PropertyGroup>
<PropertyGroup>
<DalamudLibPath>$(AppData)\XIVLauncher\addon\Hooks\dev\</DalamudLibPath>
</PropertyGroup>
<ItemGroup>
<Reference Include="Dalamud">
<HintPath>$(DalamudLibPath)Dalamud.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="FFXIVClientStructs">
<HintPath>$(DalamudLibPath)FFXIVClientStructs.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Lumina">
<HintPath>$(DalamudLibPath)Lumina.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Lumina.Excel">
<HintPath>$(DalamudLibPath)Lumina.Excel.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Newtonsoft.Json">
<HintPath>$(DalamudLibPath)Newtonsoft.Json.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ImGuiScene">
<HintPath>$(DalamudLibPath)ImGuiScene.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Penumbra.GameData\Penumbra.GameData.csproj" />
<ProjectReference Include="..\OtterGui\OtterGui.csproj" />
</ItemGroup>
</Project>

View file

@ -1,7 +0,0 @@
namespace Glamourer;
public static class Sigs
{
public const string ChangeJob = "48 89 5C 24 ?? 48 89 6C 24 ?? 48 89 74 24 ?? 57 41 56 41 57 48 83 EC ?? 80 61";
public const string FlagSlotForUpdate = "48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A";
}

View file

@ -1,102 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Penumbra.GameData.Enums;
namespace Glamourer.Structs;
[Flags]
public enum CrestFlag : ushort
{
OffHand = 0x0001,
Head = 0x0002,
Body = 0x0004,
Hands = 0x0008,
Legs = 0x0010,
Feet = 0x0020,
Ears = 0x0040,
Neck = 0x0080,
Wrists = 0x0100,
RFinger = 0x0200,
LFinger = 0x0400,
MainHand = 0x0800,
}
public enum CrestType : byte
{
None,
Human,
Mainhand,
Offhand,
};
public static class CrestExtensions
{
public const CrestFlag All = (CrestFlag)(((ulong)EquipFlag.Mainhand << 1) - 1);
public const CrestFlag AllRelevant = CrestFlag.Head | CrestFlag.Body | CrestFlag.OffHand;
public static readonly IReadOnlyList<CrestFlag> AllRelevantSet = Enum.GetValues<CrestFlag>().Where(f => AllRelevant.HasFlag(f)).ToArray();
public static int ToInternalIndex(this CrestFlag flag)
=> flag switch
{
CrestFlag.Head => 0,
CrestFlag.Body => 1,
CrestFlag.OffHand => 2,
_ => -1,
};
public static (CrestType Type, byte Index) ToIndex(this CrestFlag flag)
=> flag switch
{
CrestFlag.Head => (CrestType.Human, 0),
CrestFlag.Body => (CrestType.Human, 1),
CrestFlag.Hands => (CrestType.Human, 2),
CrestFlag.Legs => (CrestType.Human, 3),
CrestFlag.Feet => (CrestType.Human, 4),
CrestFlag.Ears => (CrestType.None, 0),
CrestFlag.Neck => (CrestType.None, 0),
CrestFlag.Wrists => (CrestType.None, 0),
CrestFlag.RFinger => (CrestType.None, 0),
CrestFlag.LFinger => (CrestType.None, 0),
CrestFlag.MainHand => (CrestType.None, 0),
CrestFlag.OffHand => (CrestType.Offhand, 0),
_ => (CrestType.None, 0),
};
public static CrestFlag ToCrestFlag(this EquipSlot slot)
=> slot switch
{
EquipSlot.MainHand => CrestFlag.MainHand,
EquipSlot.OffHand => CrestFlag.OffHand,
EquipSlot.Head => CrestFlag.Head,
EquipSlot.Body => CrestFlag.Body,
EquipSlot.Hands => CrestFlag.Hands,
EquipSlot.Legs => CrestFlag.Legs,
EquipSlot.Feet => CrestFlag.Feet,
EquipSlot.Ears => CrestFlag.Ears,
EquipSlot.Neck => CrestFlag.Neck,
EquipSlot.Wrists => CrestFlag.Wrists,
EquipSlot.RFinger => CrestFlag.RFinger,
EquipSlot.LFinger => CrestFlag.LFinger,
_ => 0,
};
public static string ToLabel(this CrestFlag flag)
=> flag switch
{
CrestFlag.Head => "Head",
CrestFlag.Body => "Chest",
CrestFlag.Hands => "Gauntlets",
CrestFlag.Legs => "Pants",
CrestFlag.Feet => "Boot",
CrestFlag.Ears => "Earrings",
CrestFlag.Neck => "Necklace",
CrestFlag.Wrists => "Bracelet",
CrestFlag.RFinger => "Right Ring",
CrestFlag.LFinger => "Left Ring",
CrestFlag.MainHand => "Weapon",
CrestFlag.OffHand => "Shield",
_ => string.Empty,
};
}

View file

@ -1,93 +0,0 @@
using System;
using Penumbra.GameData.Enums;
namespace Glamourer.Structs;
[Flags]
public enum EquipFlag : uint
{
Head = 0x00000001,
Body = 0x00000002,
Hands = 0x00000004,
Legs = 0x00000008,
Feet = 0x00000010,
Ears = 0x00000020,
Neck = 0x00000040,
Wrist = 0x00000080,
RFinger = 0x00000100,
LFinger = 0x00000200,
Mainhand = 0x00000400,
Offhand = 0x00000800,
HeadStain = 0x00001000,
BodyStain = 0x00002000,
HandsStain = 0x00004000,
LegsStain = 0x00008000,
FeetStain = 0x00010000,
EarsStain = 0x00020000,
NeckStain = 0x00040000,
WristStain = 0x00080000,
RFingerStain = 0x00100000,
LFingerStain = 0x00200000,
MainhandStain = 0x00400000,
OffhandStain = 0x00800000,
}
public static class EquipFlagExtensions
{
public const EquipFlag All = (EquipFlag)(((uint)EquipFlag.OffhandStain << 1) - 1);
public const int NumEquipFlags = 24;
public static EquipFlag ToFlag(this EquipSlot slot)
=> slot switch
{
EquipSlot.MainHand => EquipFlag.Mainhand,
EquipSlot.OffHand => EquipFlag.Offhand,
EquipSlot.Head => EquipFlag.Head,
EquipSlot.Body => EquipFlag.Body,
EquipSlot.Hands => EquipFlag.Hands,
EquipSlot.Legs => EquipFlag.Legs,
EquipSlot.Feet => EquipFlag.Feet,
EquipSlot.Ears => EquipFlag.Ears,
EquipSlot.Neck => EquipFlag.Neck,
EquipSlot.Wrists => EquipFlag.Wrist,
EquipSlot.RFinger => EquipFlag.RFinger,
EquipSlot.LFinger => EquipFlag.LFinger,
_ => 0,
};
public static EquipFlag ToStainFlag(this EquipSlot slot)
=> slot switch
{
EquipSlot.MainHand => EquipFlag.MainhandStain,
EquipSlot.OffHand => EquipFlag.OffhandStain,
EquipSlot.Head => EquipFlag.HeadStain,
EquipSlot.Body => EquipFlag.BodyStain,
EquipSlot.Hands => EquipFlag.HandsStain,
EquipSlot.Legs => EquipFlag.LegsStain,
EquipSlot.Feet => EquipFlag.FeetStain,
EquipSlot.Ears => EquipFlag.EarsStain,
EquipSlot.Neck => EquipFlag.NeckStain,
EquipSlot.Wrists => EquipFlag.WristStain,
EquipSlot.RFinger => EquipFlag.RFingerStain,
EquipSlot.LFinger => EquipFlag.LFingerStain,
_ => 0,
};
public static EquipFlag ToBothFlags(this EquipSlot slot)
=> slot switch
{
EquipSlot.MainHand => EquipFlag.Mainhand | EquipFlag.MainhandStain,
EquipSlot.OffHand => EquipFlag.Offhand | EquipFlag.OffhandStain,
EquipSlot.Head => EquipFlag.Head | EquipFlag.HeadStain,
EquipSlot.Body => EquipFlag.Body | EquipFlag.BodyStain,
EquipSlot.Hands => EquipFlag.Hands | EquipFlag.HandsStain,
EquipSlot.Legs => EquipFlag.Legs | EquipFlag.LegsStain,
EquipSlot.Feet => EquipFlag.Feet | EquipFlag.FeetStain,
EquipSlot.Ears => EquipFlag.Ears | EquipFlag.EarsStain,
EquipSlot.Neck => EquipFlag.Neck | EquipFlag.NeckStain,
EquipSlot.Wrists => EquipFlag.Wrist | EquipFlag.WristStain,
EquipSlot.RFinger => EquipFlag.RFinger | EquipFlag.RFingerStain,
EquipSlot.LFinger => EquipFlag.LFinger | EquipFlag.LFingerStain,
_ => 0,
};
}

View file

@ -1,29 +0,0 @@
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;
public readonly string Abbreviation;
public readonly ClassJob Base;
public uint Id
=> Base.RowId;
public JobFlag Flag
=> (JobFlag)(1ul << (int)Base.RowId);
public Job(ClassJob job)
{
Base = job;
Name = job.Name.ToDalamudString().ToString();
Abbreviation = job.Abbreviation.ToDalamudString().ToString();
}
public override string ToString()
=> Name;
}

View file

@ -1,61 +0,0 @@
using System;
using System.Diagnostics;
using Lumina.Excel;
using Lumina.Excel.GeneratedSheets;
namespace Glamourer.Structs;
[Flags]
public enum JobFlag : ulong
{ }
// The game specifies different job groups that can contain specific jobs or not.
public readonly struct JobGroup
{
public readonly string Name;
public readonly int Count;
public readonly uint Id;
private readonly JobFlag _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;
_flags = 0ul;
Id = group.RowId;
Name = group.Name.ToString();
Debug.Assert(jobs.RowCount < 64, $"Number of Jobs exceeded 63 ({jobs.RowCount}).");
foreach (var job in jobs)
{
var abbr = job.Abbreviation.ToString();
if (abbr.Length == 0)
continue;
var prop = group.GetType().GetProperty(abbr);
Debug.Assert(prop != null, $"Could not get job abbreviation {abbr} property.");
if (!(bool)prop.GetValue(group)!)
continue;
++Count;
_flags |= (JobFlag)(1ul << (int)job.RowId);
}
}
// Check if a job is contained inside this group.
public bool Fits(Job job)
=> _flags.HasFlag(job.Flag);
// Check if any of the jobs in the given flags fit this group.
public bool Fits(JobFlag flag)
=> (_flags & flag) != 0;
// Check if a job is contained inside this group.
public bool Fits(uint jobId)
{
var flag = (JobFlag)(1ul << (int)jobId);
return _flags.HasFlag(flag);
}
}

View file

@ -9,8 +9,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
repo.json = repo.json
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glamourer.GameData", "Glamourer.GameData\Glamourer.GameData.csproj", "{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glamourer", "Glamourer\Glamourer.csproj", "{01EB903D-871F-4285-A8CF-6486561D5B5B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.Api", "Penumbra.Api\Penumbra.Api.csproj", "{29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}"
@ -27,10 +25,6 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Release|Any CPU.Build.0 = Release|Any CPU
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Release|Any CPU.ActiveCfg = Release|Any CPU

View file

@ -1,11 +1,7 @@
using System.Buffers.Text;
using System.Collections.Generic;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Structs;
using Penumbra.Api.Helpers;
using Penumbra.GameData.Actors;

View file

@ -1,10 +1,10 @@
using System;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Interop.Structs;
using Glamourer.State;
using Glamourer.Structs;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Automation;
@ -74,7 +74,7 @@ public class AutoDesign
var ret = new JObject
{
["Gearset"] = GearsetIndex,
["JobGroup"] = Jobs.Id,
["JobGroup"] = Jobs.Id.Id,
};
return ret;

View file

@ -4,14 +4,12 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Interop;
using Glamourer.Interop.Structs;
using Glamourer.Services;
using Glamourer.State;
using Glamourer.Structs;
using Glamourer.Unlocks;
using OtterGui.Classes;
using Penumbra.GameData.Actors;
@ -209,7 +207,7 @@ public class AutoDesignApplier : IDisposable
if (!GetPlayerSet(id, out var set))
{
if (_state.TryGetValue(id, out var s))
s.LastJob = (byte)newJob.Id;
s.LastJob = newJob.Id;
return;
}

View file

@ -10,7 +10,6 @@ using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Interop;
using Glamourer.Services;
using Glamourer.Structs;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui;
@ -516,7 +515,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
var jobs = conditions["JobGroup"]?.ToObject<int>() ?? -1;
if (jobs >= 0)
{
if (!_jobs.JobGroups.TryGetValue((ushort)jobs, out var jobGroup))
if (!_jobs.JobGroups.TryGetValue((JobGroupId)jobs, out var jobGroup))
{
Glamourer.Messager.NotificationMessage(
$"Error parsing automatically applied design for set {setName}: The job condition {jobs} does not exist.",

View file

@ -3,37 +3,30 @@ using System.Linq;
using Dalamud.Interface.Internal.Notifications;
using Glamourer.Designs;
using Glamourer.Interop;
using Glamourer.Services;
using Glamourer.Structs;
using Newtonsoft.Json.Linq;
using OtterGui.Classes;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Structs;
using Penumbra.String;
namespace Glamourer.Automation;
public class FixedDesignMigrator
public class FixedDesignMigrator(JobService jobs)
{
private readonly JobService _jobs;
private List<(string Name, List<(string, JobGroup, bool)> Data)>? _migratedData;
public FixedDesignMigrator(JobService jobs)
=> _jobs = jobs;
private List<(string Name, List<(string, JobGroup, bool)> Data)>? _migratedData;
public void ConsumeMigratedData(ActorManager actors, DesignFileSystem designFileSystem, AutoDesignManager autoManager)
{
if (_migratedData == null)
return;
foreach (var data in _migratedData)
foreach (var (name, data) in _migratedData)
{
var allEnabled = true;
var name = data.Name;
if (autoManager.Any(d => name == d.Name))
continue;
var id = ActorIdentifier.Invalid;
if (ByteString.FromString(data.Name, out var byteString, false))
if (ByteString.FromString(name, out var byteString))
{
id = actors.CreatePlayer(byteString, ushort.MaxValue);
if (!id.IsValid)
@ -46,16 +39,15 @@ public class FixedDesignMigrator
id = actors.CreatePlayer(byteString, actors.Data.Worlds.First().Key);
if (!id.IsValid)
{
Glamourer.Messager.NotificationMessage($"Could not migrate fixed design {data.Name}.", NotificationType.Error);
allEnabled = false;
Glamourer.Messager.NotificationMessage($"Could not migrate fixed design {name}.", NotificationType.Error);
continue;
}
}
autoManager.AddDesignSet(name, id);
autoManager.SetState(autoManager.Count - 1, allEnabled);
autoManager.SetState(autoManager.Count - 1, true);
var set = autoManager[^1];
foreach (var design in data.Data.AsEnumerable().Reverse())
foreach (var design in data.AsEnumerable().Reverse())
{
if (!designFileSystem.Find(design.Item1, out var child) || child is not DesignFileSystem.Leaf leaf)
{
@ -96,7 +88,7 @@ public class FixedDesignMigrator
}
var job = obj["JobGroups"]?.ToObject<int>() ?? -1;
if (job < 0 || !_jobs.JobGroups.TryGetValue((ushort)job, out var group))
if (job < 0 || !jobs.JobGroups.TryGetValue((JobGroupId)job, out var group))
{
Glamourer.Messager.NotificationMessage("Could not semi-migrate fixed design: Invalid job group specified.",
NotificationType.Warning);

View file

@ -1,7 +1,6 @@
using Dalamud.Interface.Internal.Notifications;
using Glamourer.Customization;
using Glamourer.GameData;
using Glamourer.Services;
using Glamourer.Structs;
using Newtonsoft.Json.Linq;
using OtterGui.Classes;
using Penumbra.GameData.Enums;
@ -85,12 +84,12 @@ public class DesignBase
internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant;
private DesignFlags _designFlags = DesignFlags.ApplyHatVisible | DesignFlags.ApplyVisorState | DesignFlags.ApplyWeaponVisible;
public bool SetCustomize(CustomizationService customizationService, Customize customize)
public bool SetCustomize(CustomizationService customizationService, CustomizeArray customize)
{
if (customize.Equals(_designData.Customize))
return false;
_designData.Customize.Load(customize);
_designData.Customize = customize;
CustomizationSet = customizationService.Service.GetList(customize.Clan, customize.Gender);
return true;
}
@ -443,7 +442,7 @@ public class DesignBase
{
design._designData.ModelId = 0;
design._designData.IsHuman = true;
design.SetCustomize(customizations, Customize.Default);
design.SetCustomize(customizations, CustomizeArray.Default);
Glamourer.Messager.NotificationMessage("The loaded design does not contain any customization data, reset to default.",
NotificationType.Warning);
return;

View file

@ -1,7 +1,5 @@
using System;
using Glamourer.Customization;
using Glamourer.Services;
using Glamourer.Structs;
using OtterGui;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
@ -62,7 +60,7 @@ public class DesignBase64Migration
data.SetHatVisible((bytes[90] & 0x01) == 0);
data.SetVisor((bytes[90] & 0x10) != 0);
data.SetWeaponVisible((bytes[90] & 0x02) == 0);
data.ModelId = (uint)bytes[91] | ((uint)bytes[92] << 8) | ((uint)bytes[93] << 16) | ((uint)bytes[94] << 24);
data.ModelId = bytes[91] | ((uint)bytes[92] << 8) | ((uint)bytes[93] << 16) | ((uint)bytes[94] << 24);
break;
}
case 5:
@ -73,7 +71,7 @@ public class DesignBase64Migration
data.SetHatVisible((bytes[90] & 0x01) == 0);
data.SetVisor((bytes[90] & 0x10) != 0);
data.SetWeaponVisible((bytes[90] & 0x02) == 0);
data.ModelId = (uint)bytes[91] | ((uint)bytes[92] << 8) | ((uint)bytes[93] << 16) | ((uint)bytes[94] << 24);
data.ModelId = bytes[91] | ((uint)bytes[92] << 8) | ((uint)bytes[93] << 16) | ((uint)bytes[94] << 24);
break;
default: throw new Exception($"Can not parse Base64 string into design for migration:\n\tInvalid Version {bytes[0]}.");
}
@ -102,11 +100,11 @@ public class DesignBase64Migration
if (!humans.IsHuman(data.ModelId))
{
data.LoadNonHuman(data.ModelId, *(Customize*)(ptr + 4), (nint)eq);
data.LoadNonHuman(data.ModelId, *(CustomizeArray*)(ptr + 4), (nint)eq);
return data;
}
data.Customize.Load(*(Customize*)(ptr + 4));
data.Customize = *(CustomizeArray*)(ptr + 4);
foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex())
{
var mdl = eq[idx];
@ -187,7 +185,7 @@ public class DesignBase64Migration
| (equipFlags.HasFlag(EquipFlag.Wrist) ? 0x02 : 0)
| (equipFlags.HasFlag(EquipFlag.RFinger) ? 0x04 : 0)
| (equipFlags.HasFlag(EquipFlag.LFinger) ? 0x08 : 0));
save.Customize.Write((nint)data + 4);
save.Customize.Write(data + 4);
((CharacterWeapon*)(data + 30))[0] = save.Item(EquipSlot.MainHand).Weapon(save.Stain(EquipSlot.MainHand));
((CharacterWeapon*)(data + 30))[1] = save.Item(EquipSlot.OffHand).Weapon(save.Stain(EquipSlot.OffHand));
foreach (var slot in EquipSlotExtensions.EqdpSlots)

View file

@ -2,10 +2,8 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Glamourer.Customization;
using Glamourer.Services;
using Glamourer.State;
using Glamourer.Structs;
using Glamourer.Utility;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@ -165,7 +163,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
yield return (slot, item, armor.Stain);
}
var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant, FullEquipType.Unknown);
var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant);
if (!mh.Valid)
{
Glamourer.Log.Warning($"Appearance data {mainhand} for mainhand weapon invalid, item could not be identified.");

View file

@ -1,8 +1,6 @@
using System;
using System.Runtime.CompilerServices;
using Glamourer.Customization;
using Glamourer.Services;
using Glamourer.Structs;
using OtterGui.Classes;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -12,30 +10,30 @@ namespace Glamourer.Designs;
public unsafe struct DesignData
{
private string _nameHead = string.Empty;
private string _nameBody = string.Empty;
private string _nameHands = string.Empty;
private string _nameLegs = string.Empty;
private string _nameFeet = string.Empty;
private string _nameEars = string.Empty;
private string _nameNeck = string.Empty;
private string _nameWrists = string.Empty;
private string _nameRFinger = string.Empty;
private string _nameLFinger = string.Empty;
private string _nameMainhand = string.Empty;
private string _nameOffhand = string.Empty;
private fixed uint _itemIds[12];
private fixed ushort _iconIds[12];
private fixed byte _equipmentBytes[48];
public Customize Customize = Customize.Default;
public uint ModelId;
public CrestFlag CrestVisibility;
private SecondaryId _secondaryMainhand;
private SecondaryId _secondaryOffhand;
private FullEquipType _typeMainhand;
private FullEquipType _typeOffhand;
private byte _states;
public bool IsHuman = true;
private string _nameHead = string.Empty;
private string _nameBody = string.Empty;
private string _nameHands = string.Empty;
private string _nameLegs = string.Empty;
private string _nameFeet = string.Empty;
private string _nameEars = string.Empty;
private string _nameNeck = string.Empty;
private string _nameWrists = string.Empty;
private string _nameRFinger = string.Empty;
private string _nameLFinger = string.Empty;
private string _nameMainhand = string.Empty;
private string _nameOffhand = string.Empty;
private fixed uint _itemIds[12];
private fixed ushort _iconIds[12];
private fixed byte _equipmentBytes[48];
public CustomizeArray Customize = CustomizeArray.Default;
public uint ModelId;
public CrestFlag CrestVisibility;
private SecondaryId _secondaryMainhand;
private SecondaryId _secondaryOffhand;
private FullEquipType _typeMainhand;
private FullEquipType _typeOffhand;
private byte _states;
public bool IsHuman = true;
public DesignData()
{ }
@ -255,11 +253,11 @@ public unsafe struct DesignData
}
public bool LoadNonHuman(uint modelId, Customize customize, nint equipData)
public bool LoadNonHuman(uint modelId, CustomizeArray customize, nint equipData)
{
ModelId = modelId;
IsHuman = false;
Customize.Load(customize);
Customize.Read(customize.Data);
fixed (byte* ptr = _equipmentBytes)
{
MemoryUtility.MemCpyUnchecked(ptr, (byte*)equipData, 40);
@ -294,7 +292,7 @@ public unsafe struct DesignData
public readonly byte[] GetCustomizeBytes()
{
var ret = new byte[CustomizeArray.Size];
fixed (byte* retPtr = ret, inPtr = Customize.Data.Data)
fixed (byte* retPtr = ret, inPtr = Customize.Data)
{
MemoryUtility.MemCpyUnchecked(retPtr, inPtr, ret.Length);
}

View file

@ -3,12 +3,10 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using Dalamud.Utility;
using Glamourer.Customization;
using Glamourer.Events;
using Glamourer.Interop.Penumbra;
using Glamourer.Services;
using Glamourer.State;
using Glamourer.Structs;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using OtterGui;
@ -25,8 +23,8 @@ public class DesignManager
private readonly HumanModelList _humans;
private readonly SaveService _saveService;
private readonly DesignChanged _event;
private readonly List<Design> _designs = new();
private readonly Dictionary<Guid, DesignData> _undoStore = new();
private readonly List<Design> _designs = [];
private readonly Dictionary<Guid, DesignData> _undoStore = [];
public IReadOnlyList<Design> Designs
=> _designs;
@ -298,7 +296,7 @@ public class DesignManager
return;
case CustomizeIndex.Clan:
{
var customize = new Customize(design.DesignData.Customize.Data.Clone());
var customize = design.DesignData.Customize;
if (_customizations.ChangeClan(ref customize, (SubRace)value.Value) == 0)
return;
if (!design.SetCustomize(_customizations, customize))
@ -308,7 +306,7 @@ public class DesignManager
}
case CustomizeIndex.Gender:
{
var customize = new Customize(design.DesignData.Customize.Data.Clone());
var customize = design.DesignData.Customize;
if (_customizations.ChangeGender(ref customize, (Gender)(value.Value + 1)) == 0)
return;
if (!design.SetCustomize(_customizations, customize))

View file

@ -2,9 +2,11 @@
using Lumina.Excel;
using Lumina.Excel.GeneratedSheets;
namespace Glamourer.Customization;
namespace Glamourer.GameData;
// A custom version of CharaMakeParams that is easier to parse.
/// <summary>
/// A custom version of CharaMakeParams that is easier to parse.
/// </summary>
[Sheet("CharaMakeParams")]
public class CharaMakeParams : ExcelRow
{
@ -64,7 +66,7 @@ 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;
int currentOffset;
for (var i = 0; i < NumMenus; ++i)
{
currentOffset = 3 + i;

View file

@ -0,0 +1,50 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Dalamud.Plugin.Services;
using Penumbra.String.Functions;
namespace Glamourer.GameData;
public class ColorParameters : IReadOnlyList<uint>
{
private readonly uint[] _rgbaColors;
public ReadOnlySpan<uint> GetSlice(int offset, int count)
=> _rgbaColors.AsSpan(offset, count);
public unsafe ColorParameters(IDataManager gameData, IPluginLog log)
{
try
{
var file = gameData.GetFile("chara/xls/charamake/human.cmp")!;
_rgbaColors = new uint[file.Data.Length >> 2];
fixed (byte* ptr1 = file.Data)
{
fixed (uint* ptr2 = _rgbaColors)
{
MemoryUtility.MemCpyUnchecked(ptr2, ptr1, file.Data.Length);
}
}
}
catch (Exception e)
{
log.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);
_rgbaColors = Array.Empty<uint>();
}
}
public IEnumerator<uint> GetEnumerator()
=> (IEnumerator<uint>)_rgbaColors.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public int Count
=> _rgbaColors.Length;
public uint this[int index]
=> _rgbaColors[index];
}

View file

@ -1,6 +1,6 @@
namespace Glamourer.Customization;
namespace Glamourer.GameData;
// Localization from the game files directly.
/// <summary> For localization from the game files directly. </summary>
public enum CustomName
{
MidlanderM,

View file

@ -3,7 +3,7 @@ using Dalamud.Interface.Internal;
using Dalamud.Plugin.Services;
using Penumbra.GameData.Enums;
namespace Glamourer.Customization;
namespace Glamourer.GameData;
public class CustomizationManager : ICustomizationManager
{

View file

@ -1,12 +1,14 @@
using Penumbra.GameData.Enums;
using System.Collections.Generic;
using System.Linq;
using Penumbra.GameData.Structs;
namespace Glamourer.Customization;
namespace Glamourer.GameData;
public static class CustomizationNpcOptions
{
public static Dictionary<(SubRace, Gender), IReadOnlyList<(CustomizeIndex, CustomizeValue)>> CreateNpcData(CustomizationSet[] sets, NpcCustomizeSet npcCustomizeSet)
public static Dictionary<(SubRace, Gender), IReadOnlyList<(CustomizeIndex, CustomizeValue)>> CreateNpcData(CustomizationSet[] sets,
NpcCustomizeSet npcCustomizeSet)
{
var dict = new Dictionary<(SubRace, Gender), HashSet<(CustomizeIndex, CustomizeValue)>>();
var customizeIndices = new[]

View file

@ -10,9 +10,10 @@ using Lumina.Excel;
using Lumina.Excel.GeneratedSheets;
using OtterGui.Classes;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Race = Penumbra.GameData.Enums.Race;
namespace Glamourer.Customization;
namespace Glamourer.GameData;
// Generate everything about customization per tribe and gender.
public partial class CustomizationOptions
@ -66,7 +67,7 @@ public partial class CustomizationOptions
{
var tmp = new TemporaryData(gameData, this, log);
_icons = new IconStorage(textures, gameData);
SetNames(gameData, tmp);
SetNames(gameData);
foreach (var race in Clans)
{
foreach (var gender in Genders)
@ -79,7 +80,7 @@ public partial class CustomizationOptions
// 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(IDataManager gameData, TemporaryData tmp)
private void SetNames(IDataManager gameData)
{
var subRace = gameData.GetExcelSheet<Tribe>()!;
@ -122,9 +123,6 @@ public partial class CustomizationOptions
private class TemporaryData
{
public bool Valid
=> _cmpFile.Valid;
public CustomizationSet GetSet(SubRace race, Gender gender)
{
var (skin, hair) = GetColors(race, gender);
@ -177,10 +175,8 @@ public partial class CustomizationOptions
public TemporaryData(IDataManager gameData, CustomizationOptions options, IPluginLog log)
{
_options = options;
_cmpFile = new CmpFile(gameData, log);
_cmpFile = new ColorParameters(gameData, log);
_customizeSheet = gameData.GetExcelSheet<CharaMakeCustomize>(ClientLanguage.English)!;
_bnpcCustomize = gameData.GetExcelSheet<BNpcCustomize>(ClientLanguage.English)!;
_enpcBase = gameData.GetExcelSheet<ENpcBase>(ClientLanguage.English)!;
Lobby = gameData.GetExcelSheet<Lobby>(ClientLanguage.English)!;
var tmp = gameData.Excel.GetType().GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)?
.MakeGenericMethod(typeof(CharaMakeParams)).Invoke(gameData.Excel, new object?[]
@ -204,10 +200,8 @@ public partial class CustomizationOptions
private readonly ExcelSheet<CharaMakeCustomize> _customizeSheet;
private readonly ExcelSheet<CharaMakeParams> _listSheet;
private readonly ExcelSheet<HairMakeType> _hairSheet;
private readonly ExcelSheet<BNpcCustomize> _bnpcCustomize;
private readonly ExcelSheet<ENpcBase> _enpcBase;
public readonly ExcelSheet<Lobby> Lobby;
private readonly CmpFile _cmpFile;
private readonly ColorParameters _cmpFile;
// Those values are shared between all races.
private readonly CustomizeData[] _highlightPicker;
@ -222,9 +216,17 @@ public partial class CustomizationOptions
private CustomizeData[] CreateColorPicker(CustomizeIndex index, int offset, int num, bool light = false)
=> _cmpFile.GetSlice(offset, num)
.Select((c, i) => new CustomizeData(index, (CustomizeValue)(light ? 128 + i : 0 + i), c, (ushort)(offset + i)))
.ToArray();
{
var ret = new CustomizeData[num];
var idx = 0;
foreach (var value in _cmpFile.GetSlice(offset, num))
{
ret[idx] = new CustomizeData(index, (CustomizeValue)(light ? 128 + idx : idx), value, (ushort)(offset + idx));
++idx;
}
return ret;
}
private void SetHairByFace(CustomizationSet set)
@ -299,15 +301,9 @@ public partial class CustomizationOptions
// Set customizations available if they have any options.
private static void SetAvailability(CustomizationSet set, CharaMakeParams row)
{
if (set.Race == Race.Hrothgar && set.Gender == Gender.Female)
if (set is { Race: Race.Hrothgar, Gender: Gender.Female })
return;
void Set(bool available, CustomizeIndex flag)
{
if (available)
set.SetAvailable(flag);
}
Set(true, CustomizeIndex.Height);
Set(set.Faces.Count > 0, CustomizeIndex.Face);
Set(true, CustomizeIndex.Hairstyle);
@ -340,6 +336,13 @@ public partial class CustomizationOptions
Set(true, CustomizeIndex.SmallIris);
Set(set.Race != Race.Hrothgar, CustomizeIndex.Lipstick);
Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaintReversed);
return;
void Set(bool available, CustomizeIndex flag)
{
if (available)
set.SetAvailable(flag);
}
}
// Create a list of lists of facial features and the legacy tattoo.
@ -348,9 +351,6 @@ public partial class CustomizationOptions
var count = set.Faces.Count;
set.FacialFeature1 = new List<(CustomizeData, CustomizeData)>(count);
static (CustomizeData, CustomizeData) Create(CustomizeIndex i, uint data)
=> (new CustomizeData(i, CustomizeValue.Zero, data, 0), new CustomizeData(i, CustomizeValue.Max, data, 1));
set.LegacyTattoo = Create(CustomizeIndex.LegacyTattoo, 137905);
var tmp = Enumerable.Repeat(0, 7).Select(_ => new (CustomizeData, CustomizeData)[count + 1]).ToArray();
@ -373,6 +373,10 @@ public partial class CustomizationOptions
set.FacialFeature5 = tmp[4];
set.FacialFeature6 = tmp[5];
set.FacialFeature7 = tmp[6];
return;
static (CustomizeData, CustomizeData) Create(CustomizeIndex i, uint data)
=> (new CustomizeData(i, CustomizeValue.Zero, data), new CustomizeData(i, CustomizeValue.Max, data, 1));
}
// Set the names for the given set of parameters.
@ -414,7 +418,7 @@ public partial class CustomizationOptions
nameArray[(int)CustomizeIndex.SmallIris] = CustomizeIndex.SmallIris.ToDefaultName();
nameArray[(int)CustomizeIndex.Lipstick] = CustomizeIndex.Lipstick.ToDefaultName();
nameArray[(int)CustomizeIndex.FacePaintReversed] = CustomizeIndex.FacePaintReversed.ToDefaultName();
set.OptionName = nameArray;
set.OptionName = nameArray;
}
// Obtain available skin and hair colors for the given subrace and gender.

View file

@ -4,8 +4,9 @@ using System.Linq;
using System.Runtime.CompilerServices;
using OtterGui;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Customization;
namespace Glamourer.GameData;
// Each Subrace and Gender combo has a customization set.
// This describes the available customizations, their types and their names.
@ -300,3 +301,10 @@ public class CustomizationSet
private CustomizeValue HrothgarFaceHack(CustomizeValue value)
=> Race == Race.Hrothgar && value.Value is > 4 and < 9 ? value - 4 : value;
}
public static class CustomizationSetExtensions
{
/// <summary> Return only the available customizations in this set and Clan or Gender. </summary>
public static CustomizeFlag FixApplication(this CustomizeFlag flag, CustomizationSet set)
=> flag & (set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender);
}

View file

@ -1,7 +1,9 @@
using System;
using System.Runtime.InteropServices;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Customization;
namespace Glamourer.GameData;
// 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.

View file

@ -2,7 +2,7 @@
using Dalamud.Interface.Internal;
using Penumbra.GameData.Enums;
namespace Glamourer.Customization;
namespace Glamourer.GameData;
public interface ICustomizationManager
{

View file

@ -11,7 +11,7 @@ using OtterGui.Services;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Structs;
namespace Glamourer.Customization;
namespace Glamourer.GameData;
public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
{
@ -187,83 +187,83 @@ public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
data.VisorToggled = row.Visor;
}
private static (bool, Customize) FromBnpcCustomize(BNpcCustomize bnpcCustomize)
private static (bool, CustomizeArray) FromBnpcCustomize(BNpcCustomize bnpcCustomize)
{
var customize = new Customize();
customize.Data.Set(0, (byte)bnpcCustomize.Race.Row);
customize.Data.Set(1, bnpcCustomize.Gender);
customize.Data.Set(2, bnpcCustomize.BodyType);
customize.Data.Set(3, bnpcCustomize.Height);
customize.Data.Set(4, (byte)bnpcCustomize.Tribe.Row);
customize.Data.Set(5, bnpcCustomize.Face);
customize.Data.Set(6, bnpcCustomize.HairStyle);
customize.Data.Set(7, bnpcCustomize.HairHighlight);
customize.Data.Set(8, bnpcCustomize.SkinColor);
customize.Data.Set(9, bnpcCustomize.EyeHeterochromia);
customize.Data.Set(10, bnpcCustomize.HairColor);
customize.Data.Set(11, bnpcCustomize.HairHighlightColor);
customize.Data.Set(12, bnpcCustomize.FacialFeature);
customize.Data.Set(13, bnpcCustomize.FacialFeatureColor);
customize.Data.Set(14, bnpcCustomize.Eyebrows);
customize.Data.Set(15, bnpcCustomize.EyeColor);
customize.Data.Set(16, bnpcCustomize.EyeShape);
customize.Data.Set(17, bnpcCustomize.Nose);
customize.Data.Set(18, bnpcCustomize.Jaw);
customize.Data.Set(19, bnpcCustomize.Mouth);
customize.Data.Set(20, bnpcCustomize.LipColor);
customize.Data.Set(21, bnpcCustomize.BustOrTone1);
customize.Data.Set(22, bnpcCustomize.ExtraFeature1);
customize.Data.Set(23, bnpcCustomize.ExtraFeature2OrBust);
customize.Data.Set(24, bnpcCustomize.FacePaint);
customize.Data.Set(25, bnpcCustomize.FacePaintColor);
var customize = new CustomizeArray();
customize.SetByIndex(0, (CustomizeValue) (byte)bnpcCustomize.Race.Row);
customize.SetByIndex(1, (CustomizeValue) bnpcCustomize.Gender);
customize.SetByIndex(2, (CustomizeValue) bnpcCustomize.BodyType);
customize.SetByIndex(3, (CustomizeValue) bnpcCustomize.Height);
customize.SetByIndex(4, (CustomizeValue) (byte)bnpcCustomize.Tribe.Row);
customize.SetByIndex(5, (CustomizeValue) bnpcCustomize.Face);
customize.SetByIndex(6, (CustomizeValue) bnpcCustomize.HairStyle);
customize.SetByIndex(7, (CustomizeValue) bnpcCustomize.HairHighlight);
customize.SetByIndex(8, (CustomizeValue) bnpcCustomize.SkinColor);
customize.SetByIndex(9, (CustomizeValue) bnpcCustomize.EyeHeterochromia);
customize.SetByIndex(10, (CustomizeValue) bnpcCustomize.HairColor);
customize.SetByIndex(11, (CustomizeValue) bnpcCustomize.HairHighlightColor);
customize.SetByIndex(12, (CustomizeValue) bnpcCustomize.FacialFeature);
customize.SetByIndex(13, (CustomizeValue) bnpcCustomize.FacialFeatureColor);
customize.SetByIndex(14, (CustomizeValue) bnpcCustomize.Eyebrows);
customize.SetByIndex(15, (CustomizeValue) bnpcCustomize.EyeColor);
customize.SetByIndex(16, (CustomizeValue) bnpcCustomize.EyeShape);
customize.SetByIndex(17, (CustomizeValue) bnpcCustomize.Nose);
customize.SetByIndex(18, (CustomizeValue) bnpcCustomize.Jaw);
customize.SetByIndex(19, (CustomizeValue) bnpcCustomize.Mouth);
customize.SetByIndex(20, (CustomizeValue) bnpcCustomize.LipColor);
customize.SetByIndex(21, (CustomizeValue) bnpcCustomize.BustOrTone1);
customize.SetByIndex(22, (CustomizeValue) bnpcCustomize.ExtraFeature1);
customize.SetByIndex(23, (CustomizeValue) bnpcCustomize.ExtraFeature2OrBust);
customize.SetByIndex(24, (CustomizeValue) bnpcCustomize.FacePaint);
customize.SetByIndex(25, (CustomizeValue) bnpcCustomize.FacePaintColor);
if (customize.BodyType.Value != 1
|| !CustomizationOptions.Races.Contains(customize.Race)
|| !CustomizationOptions.Clans.Contains(customize.Clan)
|| !CustomizationOptions.Genders.Contains(customize.Gender))
return (false, Customize.Default);
return (false, CustomizeArray.Default);
return (true, customize);
}
private static (bool, Customize) FromEnpcBase(ENpcBase enpcBase)
private static (bool, CustomizeArray) FromEnpcBase(ENpcBase enpcBase)
{
if (enpcBase.ModelChara.Value?.Type != 1)
return (false, Customize.Default);
return (false, CustomizeArray.Default);
var customize = new Customize();
customize.Data.Set(0, (byte)enpcBase.Race.Row);
customize.Data.Set(1, enpcBase.Gender);
customize.Data.Set(2, enpcBase.BodyType);
customize.Data.Set(3, enpcBase.Height);
customize.Data.Set(4, (byte)enpcBase.Tribe.Row);
customize.Data.Set(5, enpcBase.Face);
customize.Data.Set(6, enpcBase.HairStyle);
customize.Data.Set(7, enpcBase.HairHighlight);
customize.Data.Set(8, enpcBase.SkinColor);
customize.Data.Set(9, enpcBase.EyeHeterochromia);
customize.Data.Set(10, enpcBase.HairColor);
customize.Data.Set(11, enpcBase.HairHighlightColor);
customize.Data.Set(12, enpcBase.FacialFeature);
customize.Data.Set(13, enpcBase.FacialFeatureColor);
customize.Data.Set(14, enpcBase.Eyebrows);
customize.Data.Set(15, enpcBase.EyeColor);
customize.Data.Set(16, enpcBase.EyeShape);
customize.Data.Set(17, enpcBase.Nose);
customize.Data.Set(18, enpcBase.Jaw);
customize.Data.Set(19, enpcBase.Mouth);
customize.Data.Set(20, enpcBase.LipColor);
customize.Data.Set(21, enpcBase.BustOrTone1);
customize.Data.Set(22, enpcBase.ExtraFeature1);
customize.Data.Set(23, enpcBase.ExtraFeature2OrBust);
customize.Data.Set(24, enpcBase.FacePaint);
customize.Data.Set(25, enpcBase.FacePaintColor);
var customize = new CustomizeArray();
customize.SetByIndex(0, (CustomizeValue) (byte)enpcBase.Race.Row);
customize.SetByIndex(1, (CustomizeValue) enpcBase.Gender);
customize.SetByIndex(2, (CustomizeValue) enpcBase.BodyType);
customize.SetByIndex(3, (CustomizeValue) enpcBase.Height);
customize.SetByIndex(4, (CustomizeValue) (byte)enpcBase.Tribe.Row);
customize.SetByIndex(5, (CustomizeValue) enpcBase.Face);
customize.SetByIndex(6, (CustomizeValue) enpcBase.HairStyle);
customize.SetByIndex(7, (CustomizeValue) enpcBase.HairHighlight);
customize.SetByIndex(8, (CustomizeValue) enpcBase.SkinColor);
customize.SetByIndex(9, (CustomizeValue) enpcBase.EyeHeterochromia);
customize.SetByIndex(10, (CustomizeValue) enpcBase.HairColor);
customize.SetByIndex(11, (CustomizeValue) enpcBase.HairHighlightColor);
customize.SetByIndex(12, (CustomizeValue) enpcBase.FacialFeature);
customize.SetByIndex(13, (CustomizeValue) enpcBase.FacialFeatureColor);
customize.SetByIndex(14, (CustomizeValue) enpcBase.Eyebrows);
customize.SetByIndex(15, (CustomizeValue) enpcBase.EyeColor);
customize.SetByIndex(16, (CustomizeValue) enpcBase.EyeShape);
customize.SetByIndex(17, (CustomizeValue) enpcBase.Nose);
customize.SetByIndex(18, (CustomizeValue) enpcBase.Jaw);
customize.SetByIndex(19, (CustomizeValue) enpcBase.Mouth);
customize.SetByIndex(20, (CustomizeValue) enpcBase.LipColor);
customize.SetByIndex(21, (CustomizeValue) enpcBase.BustOrTone1);
customize.SetByIndex(22, (CustomizeValue) enpcBase.ExtraFeature1);
customize.SetByIndex(23, (CustomizeValue) enpcBase.ExtraFeature2OrBust);
customize.SetByIndex(24, (CustomizeValue) enpcBase.FacePaint);
customize.SetByIndex(25, (CustomizeValue) enpcBase.FacePaintColor);
if (customize.BodyType.Value != 1
|| !CustomizationOptions.Races.Contains(customize.Race)
|| !CustomizationOptions.Clans.Contains(customize.Clan)
|| !CustomizationOptions.Genders.Contains(customize.Gender))
return (false, Customize.Default);
return (false, CustomizeArray.Default);
return (true, customize);
}

View file

@ -3,12 +3,12 @@ using System.Text;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Penumbra.GameData.Structs;
namespace Glamourer.Customization;
namespace Glamourer.GameData;
public unsafe struct NpcData
{
public string Name;
public Customize Customize;
public CustomizeArray Customize;
private fixed byte _equip[40];
public CharacterWeapon Mainhand;
public CharacterWeapon Offhand;

View file

@ -84,7 +84,6 @@
<ItemGroup>
<ProjectReference Include="..\OtterGui\OtterGui.csproj" />
<ProjectReference Include="..\Glamourer.GameData\Glamourer.GameData.csproj" />
<ProjectReference Include="..\Penumbra.Api\Penumbra.Api.csproj" />
<ProjectReference Include="..\Penumbra.String\Penumbra.string.csproj" />
<ProjectReference Include="..\Penumbra.GameData\Penumbra.GameData.csproj" />
@ -109,8 +108,8 @@
<Target Name="GetGitHash" BeforeTargets="GetAssemblyVersion" Returns="InformationalVersion">
<Exec Command="git rev-parse --short HEAD" ConsoleToMSBuild="true" StandardOutputImportance="low" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="GitCommitHashSuccess"/>
<Output TaskParameter="ConsoleOutput" PropertyName="GitCommitHash" Condition="$(GitCommitHashSuccess) == 0"/>
<Output TaskParameter="ExitCode" PropertyName="GitCommitHashSuccess" />
<Output TaskParameter="ConsoleOutput" PropertyName="GitCommitHash" Condition="$(GitCommitHashSuccess) == 0" />
</Exec>
<PropertyGroup>

View file

@ -1,11 +1,11 @@
using System.Numerics;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Glamourer.Customization;
using Glamourer.GameData;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using Penumbra.GameData;
using Penumbra.GameData.Enums;
namespace Glamourer.Gui.Customization;
@ -15,12 +15,12 @@ public partial class CustomizationDrawer
private void DrawColorPicker(CustomizeIndex index)
{
using var _ = SetId(index);
using var id = SetId(index);
var (current, custom) = GetCurrentCustomization(index);
var color = ImGui.ColorConvertU32ToFloat4(current < 0 ? ImGui.GetColorU32(ImGuiCol.FrameBg) : custom.Color);
using (var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, current < 0))
using (_ = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, current < 0))
{
if (ImGui.ColorButton($"{_customize[index].Value}##color", color, ImGuiColorEditFlags.None, _framedIconSize))
ImGui.OpenPopup(ColorPickerPopupName);
@ -39,7 +39,7 @@ public partial class CustomizationDrawer
ImGui.SameLine();
using (var group = ImRaii.Group())
using (_ = ImRaii.Group())
{
DataInputInt(current, npc);
if (_withApply)
@ -89,7 +89,7 @@ public partial class CustomizationDrawer
{
var current = _set.DataByValue(index, _customize[index], out var custom, _customize.Face);
if (_set.IsAvailable(index) && current < 0)
return (current, new CustomizeData(index, _customize[index], 0, 0));
return (current, new CustomizeData(index, _customize[index]));
return (current, custom!.Value);
}

View file

@ -1,7 +1,7 @@
using System;
using System.Linq;
using Dalamud.Interface;
using Glamourer.Customization;
using Glamourer.GameData;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;

View file

@ -1,9 +1,11 @@
using System;
using System.Numerics;
using Glamourer.Customization;
using Glamourer.GameData;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Customization;
@ -13,7 +15,7 @@ public partial class CustomizationDrawer
private void DrawIconSelector(CustomizeIndex index)
{
using var _ = SetId(index);
using var id = SetId(index);
using var bigGroup = ImRaii.Group();
var label = _currentOption;
@ -28,7 +30,7 @@ public partial class CustomizationDrawer
}
var icon = _service.Service.GetIcon(custom!.Value.IconId);
using (var disabled = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw))
using (_ = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw))
{
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
ImGui.OpenPopup(IconSelectorPopup);
@ -37,7 +39,7 @@ public partial class CustomizationDrawer
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
ImGui.SameLine();
using (var group = ImRaii.Group())
using (_ = ImRaii.Group())
{
DataInputInt(current, npc);
if (_lockedRedraw && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
@ -118,16 +120,16 @@ public partial class CustomizationDrawer
ImGui.Dummy(new Vector2(ImGui.GetFrameHeight()));
}
var oldValue = _customize.Data.At(_currentIndex.ToByteAndMask().ByteIdx);
var tmp = (int)oldValue;
var oldValue = _customize.AtIndex(_currentIndex.ToByteAndMask().ByteIdx);
var tmp = (int)oldValue.Value;
ImGui.SetNextItemWidth(_inputIntSize);
if (ImGui.InputInt("##text", ref tmp, 1, 1))
{
tmp = Math.Clamp(tmp, 0, byte.MaxValue);
if (tmp != oldValue)
if (tmp != oldValue.Value)
{
_customize.Data.Set(_currentIndex.ToByteAndMask().ByteIdx, (byte)tmp);
var changes = (byte)tmp ^ oldValue;
_customize.SetByIndex(_currentIndex.ToByteAndMask().ByteIdx, (CustomizeValue)tmp);
var changes = (byte)tmp ^ oldValue.Value;
Changed |= ((changes & 0x01) == 0x01 ? CustomizeFlag.FacialFeature1 : 0)
| ((changes & 0x02) == 0x02 ? CustomizeFlag.FacialFeature2 : 0)
| ((changes & 0x04) == 0x04 ? CustomizeFlag.FacialFeature3 : 0)

View file

@ -1,10 +1,10 @@
using System;
using System.Numerics;
using Glamourer.Customization;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Customization;
@ -91,10 +91,10 @@ public partial class CustomizationDrawer
private void DrawListSelector(CustomizeIndex index, bool indexedBy1)
{
using var _ = SetId(index);
using var id = SetId(index);
using var bigGroup = ImRaii.Group();
using (var disabled = ImRaii.Disabled(_locked))
using (_ = ImRaii.Disabled(_locked))
{
if (indexedBy1)
{
@ -210,7 +210,7 @@ public partial class CustomizationDrawer
}
else
{
using (var disabled = ImRaii.Disabled(_locked))
using (_ = ImRaii.Disabled(_locked))
{
if (ImGui.Checkbox("##toggle", ref tmp))
{

View file

@ -4,7 +4,7 @@ using System.Reflection;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Utility;
using Dalamud.Plugin;
using Glamourer.Customization;
using Glamourer.GameData;
using Glamourer.Services;
using ImGuiNET;
using OtterGui;
@ -22,13 +22,12 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio
private Exception? _terminate;
private Customize _customize = Customize.Default;
private CustomizeArray _customize = CustomizeArray.Default;
private CustomizationSet _set = null!;
public Customize Customize
public CustomizeArray Customize
=> _customize;
public CustomizeFlag CurrentFlag { get; private set; }
public CustomizeFlag Changed { get; private set; }
public CustomizeFlag ChangeApply { get; private set; }
@ -47,18 +46,16 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio
public void Dispose()
=> _legacyTattoo?.Dispose();
public bool Draw(Customize current, bool locked, bool lockedRedraw)
public bool Draw(CustomizeArray current, bool locked, bool lockedRedraw)
{
CurrentFlag = CustomizeFlagExtensions.All;
_withApply = false;
Init(current, locked, lockedRedraw);
return DrawInternal();
}
public bool Draw(Customize current, CustomizeFlag apply, bool locked, bool lockedRedraw)
public bool Draw(CustomizeArray current, CustomizeFlag apply, bool locked, bool lockedRedraw)
{
CurrentFlag = CustomizeFlagExtensions.All;
ChangeApply = apply;
_initialApply = apply;
_withApply = !_config.HideApplyCheckmarks;
@ -66,12 +63,12 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio
return DrawInternal();
}
private void Init(Customize current, bool locked, bool lockedRedraw)
private void Init(CustomizeArray current, bool locked, bool lockedRedraw)
{
UpdateSizes();
_terminate = null;
Changed = 0;
_customize.Load(current);
_terminate = null;
Changed = 0;
_customize = current;
_locked = locked;
_lockedRedraw = lockedRedraw;
}
@ -156,20 +153,20 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio
for (var i = 0; i < CustomizeArray.Size; ++i)
{
using var id = ImRaii.PushId(i);
int value = _customize.Data.Data[i];
int value = _customize.Data[i];
ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale);
if (ImGui.InputInt(string.Empty, ref value, 0, 0))
{
var newValue = (byte)Math.Clamp(value, 0, byte.MaxValue);
if (newValue != _customize.Data.Data[i])
if (newValue != _customize.Data[i])
foreach (var flag in Enum.GetValues<CustomizeIndex>())
{
var (j, mask) = flag.ToByteAndMask();
var (j, _) = flag.ToByteAndMask();
if (j == i)
Changed |= flag.ToFlag();
}
_customize.Data.Data[i] = newValue;
_customize.Data[i] = newValue;
}
}

View file

@ -4,7 +4,7 @@ using System.Linq;
using Dalamud.Interface.Utility;
using Dalamud.Interface.Utility.Raii;
using Glamourer.Automation;
using Glamourer.Customization;
using Glamourer.GameData;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Services;
@ -13,6 +13,7 @@ using OtterGui;
using OtterGui.Classes;
using OtterGui.Log;
using OtterGui.Widgets;
using Penumbra.GameData.Enums;
namespace Glamourer.Gui;

View file

@ -7,7 +7,6 @@ using Glamourer.Interop;
using Glamourer.Interop.Penumbra;
using Glamourer.Services;
using Glamourer.State;
using Glamourer.Structs;
using ImGuiNET;
using OtterGui.Raii;
using Penumbra.Api.Enums;
@ -76,7 +75,7 @@ public class PenumbraChangedItemTooltip : IDisposable
case EquipSlot.OffHand when !CanApplyWeapon(EquipSlot.OffHand, item):
break;
case EquipSlot.RFinger:
using (var tt = !openTooltip ? null : ImRaii.Tooltip())
using (_ = !openTooltip ? null : ImRaii.Tooltip())
{
ImGui.TextUnformatted($"{prefix}Right-Click to apply to current actor (Right Finger).");
ImGui.TextUnformatted($"{prefix}Shift + Right-Click to apply to current actor (Left Finger).");
@ -92,7 +91,7 @@ public class PenumbraChangedItemTooltip : IDisposable
break;
default:
using (var tt = !openTooltip ? null : ImRaii.Tooltip())
using (_ = !openTooltip ? null : ImRaii.Tooltip())
{
ImGui.TextUnformatted($"{prefix}Right-Click to apply to current actor.");
if (last.Valid)

View file

@ -6,7 +6,6 @@ using Dalamud.Interface.Internal.Notifications;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
using Glamourer.Automation;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Gui.Customization;
@ -14,7 +13,6 @@ using Glamourer.Gui.Equipment;
using Glamourer.Interop;
using Glamourer.Interop.Structs;
using Glamourer.State;
using Glamourer.Structs;
using ImGuiNET;
using OtterGui;
using OtterGui.Classes;
@ -173,21 +171,21 @@ public class ActorPanel(
private void DrawEquipmentMetaToggles()
{
using (var _ = ImRaii.Group())
using (_ = ImRaii.Group())
{
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.HatState, _stateManager, _state!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Head, _stateManager, _state!));
}
ImGui.SameLine();
using (var _ = ImRaii.Group())
using (_ = ImRaii.Group())
{
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.VisorState, _stateManager, _state!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Body, _stateManager, _state!));
}
ImGui.SameLine();
using (var _ = ImRaii.Group())
using (_ = ImRaii.Group())
{
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.WeaponState, _stateManager, _state!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.OffHand, _stateManager, _state!));
@ -199,7 +197,7 @@ public class ActorPanel(
var names = _modelChara[_state!.ModelData.ModelId];
var turnHuman = ImGui.Button("Turn Human");
ImGui.Separator();
using (var box = ImRaii.ListBox("##MonsterList",
using (_ = ImRaii.ListBox("##MonsterList",
new Vector2(ImGui.GetContentRegionAvail().X, 10 * ImGui.GetTextLineHeightWithSpacing())))
{
if (names.Count == 0)
@ -211,14 +209,14 @@ public class ActorPanel(
ImGui.Separator();
ImGui.TextUnformatted("Customization Data");
using (var font = ImRaii.PushFont(UiBuilder.MonoFont))
using (_ = ImRaii.PushFont(UiBuilder.MonoFont))
{
foreach (var b in _state.ModelData.Customize.Data)
foreach (var b in _state.ModelData.Customize)
{
using (var g = ImRaii.Group())
using (_ = ImRaii.Group())
{
ImGui.TextUnformatted($" {b:X2}");
ImGui.TextUnformatted($"{b,3}");
ImGui.TextUnformatted($" {b.Value:X2}");
ImGui.TextUnformatted($"{b.Value,3}");
}
ImGui.SameLine();
@ -232,11 +230,11 @@ public class ActorPanel(
ImGui.Separator();
ImGui.TextUnformatted("Equipment Data");
using (var font = ImRaii.PushFont(UiBuilder.MonoFont))
using (_ = ImRaii.PushFont(UiBuilder.MonoFont))
{
foreach (var b in _state.ModelData.GetEquipmentBytes())
{
using (var g = ImRaii.Group())
using (_ = ImRaii.Group())
{
ImGui.TextUnformatted($" {b:X2}");
ImGui.TextUnformatted($"{b,3}");
@ -298,8 +296,8 @@ public class ActorPanel(
BorderColor = ColorId.ActorUnavailable.Value(),
};
private string _newName = string.Empty;
private DesignBase? _newDesign = null;
private string _newName = string.Empty;
private DesignBase? _newDesign;
private void SaveDesignOpen()
{

View file

@ -6,10 +6,8 @@ using System.Text;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Glamourer.Automation;
using Glamourer.Customization;
using Glamourer.Interop;
using Glamourer.Services;
using Glamourer.Structs;
using Glamourer.Unlocks;
using ImGuiNET;
using OtterGui;
@ -17,44 +15,29 @@ using OtterGui.Log;
using OtterGui.Raii;
using OtterGui.Widgets;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Action = System.Action;
using CustomizeIndex = Glamourer.Customization.CustomizeIndex;
namespace Glamourer.Gui.Tabs.AutomationTab;
public class SetPanel
public class SetPanel(
SetSelector _selector,
AutoDesignManager _manager,
JobService _jobs,
ItemUnlockManager _itemUnlocks,
RevertDesignCombo _designCombo,
CustomizeUnlockManager _customizeUnlocks,
CustomizationService _customizations,
IdentifierDrawer _identifierDrawer,
Configuration _config)
{
private readonly AutoDesignManager _manager;
private readonly SetSelector _selector;
private readonly ItemUnlockManager _itemUnlocks;
private readonly CustomizeUnlockManager _customizeUnlocks;
private readonly CustomizationService _customizations;
private readonly Configuration _config;
private readonly RevertDesignCombo _designCombo;
private readonly JobGroupCombo _jobGroupCombo;
private readonly IdentifierDrawer _identifierDrawer;
private readonly JobGroupCombo _jobGroupCombo = new(_manager, _jobs, Glamourer.Log);
private string? _tempName;
private int _dragIndex = -1;
private Action? _endAction;
public SetPanel(SetSelector selector, AutoDesignManager manager, JobService jobs, ItemUnlockManager itemUnlocks,
RevertDesignCombo designCombo,
CustomizeUnlockManager customizeUnlocks, CustomizationService customizations, IdentifierDrawer identifierDrawer, Configuration config)
{
_selector = selector;
_manager = manager;
_itemUnlocks = itemUnlocks;
_customizeUnlocks = customizeUnlocks;
_customizations = customizations;
_identifierDrawer = identifierDrawer;
_config = config;
_designCombo = designCombo;
_jobGroupCombo = new JobGroupCombo(manager, jobs, Glamourer.Log);
}
private AutoDesignSet Selection
=> _selector.Selection!;
@ -77,7 +60,7 @@ public class SetPanel
var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y };
using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
{
var enabled = Selection.Enabled;
if (ImGui.Checkbox("##Enabled", ref enabled))
@ -87,7 +70,7 @@ public class SetPanel
}
ImGui.SameLine();
using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
{
var useGame = _selector.Selection!.BaseState is AutoDesignSet.Base.Game;
if (ImGui.Checkbox("##gameState", ref useGame))
@ -98,7 +81,7 @@ public class SetPanel
}
ImGui.SameLine();
using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
{
var editing = _config.ShowAutomationSetEditing;
if (ImGui.Checkbox("##Show Editing", ref editing))
@ -230,7 +213,7 @@ public class SetPanel
if (_config.ShowUnlockedItemWarnings)
{
ImGui.TableNextColumn();
DrawWarnings(design, idx);
DrawWarnings(design);
}
}
@ -278,7 +261,7 @@ public class SetPanel
}
}
private void DrawWarnings(AutoDesign design, int idx)
private void DrawWarnings(AutoDesign design)
{
if (design.Revert)
return;
@ -301,27 +284,6 @@ public class SetPanel
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(2 * ImGuiHelpers.GlobalScale, 0));
static void DrawWarning(StringBuilder sb, uint color, Vector2 size, string suffix, string good)
{
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
if (sb.Length > 0)
{
sb.Append(suffix);
using (var font = ImRaii.PushFont(UiBuilder.IconFont))
{
ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, color);
}
ImGuiUtil.HoverTooltip(sb.ToString());
}
else
{
ImGuiUtil.DrawTextButton(string.Empty, size, 0);
ImGuiUtil.HoverTooltip(good);
}
}
var tt = _config.UnlockedItemMode
? "\nThese items will be skipped when applied automatically.\n\nTo change this, disable the Obtained Item Mode setting."
: string.Empty;
@ -355,6 +317,27 @@ public class SetPanel
: string.Empty;
DrawWarning(sb2, _config.UnlockedItemMode ? 0xA03030F0 : 0x0, size, tt, "All customizations to be applied are unlocked.");
ImGui.SameLine();
return;
static void DrawWarning(StringBuilder sb, uint color, Vector2 size, string suffix, string good)
{
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
if (sb.Length > 0)
{
sb.Append(suffix);
using (_ = ImRaii.PushFont(UiBuilder.IconFont))
{
ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, color);
}
ImGuiUtil.HoverTooltip(sb.ToString());
}
else
{
ImGuiUtil.DrawTextButton(string.Empty, size, 0);
ImGuiUtil.HoverTooltip(good);
}
}
}
private void DrawDragDrop(AutoDesignSet set, int index)
@ -394,7 +377,7 @@ public class SetPanel
var newType = design.ApplicationType;
var newTypeInt = (uint)newType;
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
using (var c = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value()))
using (_ = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value()))
{
if (ImGui.CheckboxFlags("##all", ref newTypeInt, (uint)AutoDesign.Type.All))
newType = (AutoDesign.Type)newTypeInt;

View file

@ -2,7 +2,7 @@
using System.Linq;
using System.Numerics;
using Dalamud.Interface;
using Glamourer.Customization;
using Glamourer.GameData;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Interop;

View file

@ -1,9 +1,10 @@
using System;
using Glamourer.Customization;
using Glamourer.GameData;
using Glamourer.Services;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using Penumbra.GameData.Enums;
namespace Glamourer.Gui.Tabs.DebugTab;

View file

@ -1,10 +1,10 @@
using System;
using System.Numerics;
using Glamourer.Customization;
using Glamourer.Unlocks;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using Penumbra.GameData.Enums;
namespace Glamourer.Gui.Tabs.DebugTab;

View file

@ -1,10 +1,9 @@
using System.IO;
using System.Numerics;
using Glamourer.Customization;
using Glamourer.Interop;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using Penumbra.GameData.Files;
namespace Glamourer.Gui.Tabs.DebugTab;
@ -35,7 +34,7 @@ public class DatFilePanel(ImportService _importService) : IDebugTabTree
ImGui.TextUnformatted(_datFile.Value.Version.ToString());
ImGui.TextUnformatted(_datFile.Value.Time.LocalDateTime.ToString("g"));
ImGui.TextUnformatted(_datFile.Value.Voice.ToString());
ImGui.TextUnformatted(_datFile.Value.Customize.Data.ToString());
ImGui.TextUnformatted(_datFile.Value.Customize.ToString());
ImGui.TextUnformatted(_datFile.Value.Description);
}
}

View file

@ -1,9 +1,7 @@
using System;
using System.Linq;
using Dalamud.Interface;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Structs;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;

View file

@ -1,10 +1,8 @@
using System;
using System.Linq;
using Dalamud.Interface;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Services;
using Glamourer.Structs;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
@ -85,7 +83,7 @@ public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDe
DrawDesignData(_parse64);
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
ImGui.TextUnformatted(_base64);
using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing with { X = 0 }))
using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing with { X = 0 }))
{
foreach (var (c1, c2) in _restore.Zip(_base64))
{
@ -99,7 +97,7 @@ public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDe
foreach (var ((b1, b2), idx) in _base64Bytes.Zip(_restoreBytes).WithIndex())
{
using (var group = ImRaii.Group())
using (_ = ImRaii.Group())
{
ImGui.TextUnformatted(idx.ToString("D2"));
ImGui.TextUnformatted(b1.ToString("X2"));
@ -121,7 +119,7 @@ public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDe
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
foreach (var (b, idx) in _base64Bytes.WithIndex())
{
using (var group = ImRaii.Group())
using (_ = ImRaii.Group())
{
ImGui.TextUnformatted(idx.ToString("D2"));
ImGui.TextUnformatted(b.ToString("X2"));

View file

@ -32,7 +32,7 @@ public class JobPanel(JobService _jobs) : IDebugTabTree
foreach (var (id, job) in _jobs.Jobs)
{
ImGuiUtil.DrawTableColumn(id.ToString("D3"));
ImGuiUtil.DrawTableColumn(id.Id.ToString("D3"));
ImGuiUtil.DrawTableColumn(job.Name);
ImGuiUtil.DrawTableColumn(job.Abbreviation);
}
@ -68,7 +68,7 @@ public class JobPanel(JobService _jobs) : IDebugTabTree
foreach (var (id, group) in _jobs.JobGroups)
{
ImGuiUtil.DrawTableColumn(id.ToString("D3"));
ImGuiUtil.DrawTableColumn(id.Id.ToString("D3"));
ImGuiUtil.DrawTableColumn(group.Name);
ImGuiUtil.DrawTableColumn(group.Count.ToString());
}

View file

@ -1,10 +1,8 @@
using System;
using System.Numerics;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Glamourer.Customization;
using Glamourer.Interop;
using Glamourer.Interop.Structs;
using Glamourer.Structs;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
@ -126,15 +124,14 @@ public unsafe class ModelEvaluationPanel(
model.AsHuman->Head.Value == 0 ? actor.GetArmor(EquipSlot.Head) : CharacterArmor.Empty);
}
private void DrawWeaponState(Actor actor, Model model)
private static void DrawWeaponState(Actor actor, Model model)
{
using var id = ImRaii.PushId("WeaponState");
ImGuiUtil.DrawTableColumn("Weapon State");
ImGuiUtil.DrawTableColumn(actor.IsCharacter
? actor.AsCharacter->DrawData.IsWeaponHidden ? "Hidden" : "Visible"
: "No Character");
var text = string.Empty;
string text;
if (!model.IsHuman)
{
text = "No Model";
@ -146,19 +143,14 @@ public unsafe class ModelEvaluationPanel(
else
{
var weapon = (DrawObject*)model.AsDrawObject->Object.ChildObject;
if ((weapon->Flags & 0x09) == 0x09)
text = "Visible";
else
text = "Hidden";
text = (weapon->Flags & 0x09) == 0x09 ? "Visible" : "Hidden";
}
ImGuiUtil.DrawTableColumn(text);
ImGui.TableNextColumn();
if (!model.IsHuman)
return;
}
private void DrawWetness(Actor actor, Model model)
private static void DrawWetness(Actor actor, Model model)
{
using var id = ImRaii.PushId("Wetness");
ImGuiUtil.DrawTableColumn("Wetness");
@ -212,12 +204,12 @@ public unsafe class ModelEvaluationPanel(
private void DrawCustomize(Actor actor, Model model)
{
using var id = ImRaii.PushId("Customize");
var actorCustomize = new Customize(actor.IsCharacter
var actorCustomize = actor.IsCharacter
? *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData
: new CustomizeArray());
var modelCustomize = new Customize(model.IsHuman
: new CustomizeArray();
var modelCustomize = model.IsHuman
? *(CustomizeArray*)model.AsHuman->Customize.Data
: new CustomizeArray());
: new CustomizeArray();
foreach (var type in Enum.GetValues<CustomizeIndex>())
{
using var id2 = ImRaii.PushId((int)type);
@ -235,7 +227,7 @@ public unsafe class ModelEvaluationPanel(
var shift = BitOperations.TrailingZeroCount(mask);
var newValue = value + (1 << shift);
modelCustomize.Set(type, (CustomizeValue)newValue);
_changeCustomizeService.UpdateCustomize(model, modelCustomize.Data);
_changeCustomizeService.UpdateCustomize(model, modelCustomize);
}
ImGui.SameLine();
@ -246,14 +238,14 @@ public unsafe class ModelEvaluationPanel(
var shift = BitOperations.TrailingZeroCount(mask);
var newValue = value - (1 << shift);
modelCustomize.Set(type, (CustomizeValue)newValue);
_changeCustomizeService.UpdateCustomize(model, modelCustomize.Data);
_changeCustomizeService.UpdateCustomize(model, modelCustomize);
}
ImGui.SameLine();
if (ImGui.SmallButton("Reset"))
{
modelCustomize.Set(type, actorCustomize[type]);
_changeCustomizeService.UpdateCustomize(model, modelCustomize.Data);
_changeCustomizeService.UpdateCustomize(model, modelCustomize);
}
}
}

View file

@ -3,14 +3,15 @@ using System.Numerics;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Interop;
using Glamourer.State;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using Penumbra.GameData.Enums;
using ImGuiClip = OtterGui.ImGuiClip;
namespace Glamourer.Gui.Tabs.DebugTab;
@ -24,7 +25,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM
=> false;
private string _npcFilter = string.Empty;
private bool _customizeOrGear = false;
private bool _customizeOrGear;
public void Draw()
{
@ -48,19 +49,17 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM
ImGui.TableNextRow();
var idx = 0;
var remainder = ImGuiClip.FilteredClippedDraw(_npcCombo.Items, skips,
d => d.Name.Contains(_npcFilter, StringComparison.OrdinalIgnoreCase), Draw);
d => d.Name.Contains(_npcFilter, StringComparison.OrdinalIgnoreCase), DrawData);
ImGui.TableNextColumn();
ImGuiClip.DrawEndDummy(remainder, ImGui.GetFrameHeightWithSpacing());
return;
void Draw(NpcData data)
void DrawData(NpcData data)
{
using var id = ImRaii.PushId(idx++);
var disabled = !_state.GetOrCreate(_objectManager.Player, out var state);
ImGui.TableNextColumn();
if (ImGuiUtil.DrawDisabledButton("Apply", Vector2.Zero, string.Empty, disabled, false))
if (ImGuiUtil.DrawDisabledButton("Apply", Vector2.Zero, string.Empty, disabled))
{
foreach (var (slot, item, stain) in _designConverter.FromDrawData(data.Equip.ToArray(), data.Mainhand, data.Offhand))
_state.ChangeEquip(state!, slot, item, stain, StateChanged.Source.Manual);
@ -76,7 +75,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(data.Kind is ObjectKind.BattleNpc ? "B" : "E");
using (var icon = ImRaii.PushFont(UiBuilder.IconFont))
using (_ = ImRaii.PushFont(UiBuilder.IconFont))
{
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
@ -86,7 +85,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM
using var mono = ImRaii.PushFont(UiBuilder.MonoFont);
ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(_customizeOrGear ? data.Customize.Data.ToString() : data.WriteGear());
ImGui.TextUnformatted(_customizeOrGear ? data.Customize.ToString() : data.WriteGear());
}
}
}

View file

@ -7,14 +7,12 @@ using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.Internal.Notifications;
using FFXIVClientStructs.FFXIV.Client.System.Framework;
using Glamourer.Automation;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment;
using Glamourer.Interop;
using Glamourer.State;
using Glamourer.Structs;
using ImGuiNET;
using OtterGui;
using OtterGui.Classes;

View file

@ -1,4 +1,4 @@
using Glamourer.Customization;
using Glamourer.GameData;
using OtterGui.Widgets;
namespace Glamourer.Gui.Tabs;

View file

@ -3,7 +3,7 @@ using System.Linq;
using System.Numerics;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Interface.Utility;
using Glamourer.Customization;
using Glamourer.GameData;
using Glamourer.Interop;
using Glamourer.Services;
using Glamourer.Unlocks;

View file

@ -8,7 +8,6 @@ using Dalamud.Interface.Utility;
using Glamourer.Events;
using Glamourer.Interop;
using Glamourer.Services;
using Glamourer.Structs;
using Glamourer.Unlocks;
using ImGuiNET;
using OtterGui;

View file

@ -2,7 +2,6 @@
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.State;
using Glamourer.Structs;
using Penumbra.GameData.Enums;
namespace Glamourer.Gui;

View file

@ -1,9 +1,7 @@
using System.Numerics;
using Dalamud.Interface;
using Dalamud.Interface.Utility;
using Glamourer.Customization;
using Glamourer.Services;
using Glamourer.Structs;
using Glamourer.Unlocks;
using ImGuiNET;
using Lumina.Misc;
@ -61,7 +59,7 @@ public static class UiHelpers
{
var flags = (sbyte)(currentApply ? currentValue ? 1 : -1 : 0);
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing);
using (var disabled = ImRaii.Disabled(locked))
using (_ = ImRaii.Disabled(locked))
{
if (new TristateCheckbox(ColorId.TriStateCross.Value(), ColorId.TriStateCheck.Value(), ColorId.TriStateNeutral.Value()).Draw(
"##" + label, flags, out flags))

View file

@ -2,7 +2,6 @@
using Dalamud.Hooking;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Glamourer.Customization;
using Glamourer.Events;
using Glamourer.Interop.Structs;
using OtterGui.Classes;
@ -15,7 +14,7 @@ namespace Glamourer.Interop;
/// Changes in Race, body type or Gender are probably ignored.
/// This operates on draw objects, not game objects.
/// </summary>
public unsafe class ChangeCustomizeService : EventWrapper<Action<Model, Ref<Customize>>, ChangeCustomizeService.Priority>
public unsafe class ChangeCustomizeService : EventWrapper<Action<Model, Ref<CustomizeArray>>, ChangeCustomizeService.Priority>
{
private readonly PenumbraReloaded _penumbraReloaded;
private readonly IGameInteropProvider _interop;
@ -81,10 +80,11 @@ public unsafe class ChangeCustomizeService : EventWrapper<Action<Model, Ref<Cust
{
if (!InUpdate.InMethod)
{
var customize = new Ref<Customize>(new Customize(*(CustomizeArray*)data));
var customize = new Ref<CustomizeArray>(*(CustomizeArray*)data);
Invoke(this, (Model)human, customize);
((Customize*)data)->Load(customize.Value);
*(CustomizeArray*)data = customize.Value;
}
return _changeCustomizeHook.Original(human, data, skipEquipment);
}
}

View file

@ -1,8 +1,6 @@
using System;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Services;
using Glamourer.Structs;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -70,7 +68,7 @@ public sealed class CharaFile
return;
data.SetItem(slot, item);
data.SetStain(slot, (StainId)dye);
data.SetStain(slot, dye);
flags |= slot.ToFlag();
flags |= slot.ToStainFlag();
}
@ -94,7 +92,7 @@ public sealed class CharaFile
flags |= slot.ToStainFlag();
}
private static CustomizeFlag ParseCustomize(JObject jObj, ref Customize customize)
private static CustomizeFlag ParseCustomize(JObject jObj, ref CustomizeArray customize)
{
CustomizeFlag ret = 0;
customize.Race = ParseRace(jObj, ref ret);
@ -149,7 +147,7 @@ public sealed class CharaFile
return id;
}
private static void ParseFacial(JObject jObj, ref Customize customize, ref CustomizeFlag application)
private static void ParseFacial(JObject jObj, ref CustomizeArray customize, ref CustomizeFlag application)
{
var jTok = jObj["FacialFeatures"];
if (jTok == null)
@ -186,7 +184,7 @@ public sealed class CharaFile
customize[CustomizeIndex.LegacyTattoo] = CustomizeValue.Max;
}
private static void ParseHighlights(JObject jObj, ref Customize customize, ref CustomizeFlag application)
private static void ParseHighlights(JObject jObj, ref CustomizeArray customize, ref CustomizeFlag application)
{
var jTok = jObj["EnableHighlights"];
if (jTok == null)
@ -242,15 +240,15 @@ public sealed class CharaFile
throw new Exception($"Age {age} != Normal is not supported.");
}
private static unsafe void ParseByte(JObject jObj, string property, CustomizeIndex idx, ref Customize customize,
private static unsafe void ParseByte(JObject jObj, string property, CustomizeIndex idx, ref CustomizeArray customize,
ref CustomizeFlag application)
{
var jTok = jObj[property];
if (jTok == null)
return;
customize.Data.Data[idx.ToByteAndMask().ByteIdx] = jTok.ToObject<byte>();
application |= idx.ToFlag();
customize.Data[idx.ToByteAndMask().ByteIdx] = jTok.ToObject<byte>();
application |= idx.ToFlag();
}
private static SubRace ParseTribe(JObject jObj, ref CustomizeFlag application)

View file

@ -42,7 +42,7 @@ public sealed class CmaFile
var byteData = Convert.FromHexString(bytes);
fixed (byte* ptr = byteData)
{
data.Customize.Data.Read(ptr);
data.Customize.Read(ptr);
}
}
@ -64,7 +64,7 @@ public sealed class CmaFile
data.SetStain(slot, armor.Stain);
}
data.Customize.Data.Read(ptr);
data.Customize.Read(ptr);
}
}

View file

@ -1,12 +1,10 @@
using System;
using System.Linq;
using Dalamud.Hooking;
using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Glamourer.Interop.Structs;
using Glamourer.Structs;
using OtterGui.Classes;
using Penumbra.GameData.Enums;

View file

@ -4,13 +4,14 @@ using System.IO;
using System.Linq;
using Dalamud.Interface.DragDrop;
using Dalamud.Interface.Internal.Notifications;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Interop.CharaFile;
using Glamourer.Services;
using Glamourer.Structs;
using ImGuiNET;
using OtterGui.Classes;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Files;
using Penumbra.GameData.Structs;
namespace Glamourer.Interop;
@ -139,7 +140,7 @@ public class ImportService(CustomizationService _customizations, IDragDropManage
return true;
}
public bool SaveDesignAsDat(string path, in Customize input, string description)
public bool SaveDesignAsDat(string path, in CustomizeArray input, string description)
{
if (!Verify(input, out var voice))
return false;
@ -168,7 +169,7 @@ public class ImportService(CustomizationService _customizations, IDragDropManage
}
}
public bool Verify(in Customize input, out byte voice, byte? inputVoice = null)
public bool Verify(in CustomizeArray input, out byte voice, byte? inputVoice = null)
{
voice = 0;
if (_customizations.ValidateClan(input.Clan, input.Race, out _, out _).Length > 0)

View file

@ -6,7 +6,9 @@ using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using Glamourer.Interop.Structs;
using Glamourer.Structs;
using Penumbra.GameData;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Structs;
namespace Glamourer.Interop;
@ -14,19 +16,20 @@ public class JobService : IDisposable
{
private readonly nint _characterDataOffset;
public readonly IReadOnlyDictionary<byte, Job> Jobs;
public readonly IReadOnlyDictionary<ushort, JobGroup> JobGroups;
public readonly IReadOnlyList<JobGroup> AllJobGroups;
public readonly DictJob Jobs;
public readonly DictJobGroup JobGroups;
public IReadOnlyList<JobGroup> AllJobGroups
=> JobGroups.AllJobGroups;
public event Action<Actor, Job, Job>? JobChanged;
public JobService(IDataManager gameData, IGameInteropProvider interop)
public JobService(DictJob jobs, DictJobGroup jobGroups, IDataManager gameData, IGameInteropProvider interop)
{
interop.InitializeFromAttributes(this);
_characterDataOffset = Marshal.OffsetOf<Character>(nameof(Character.CharacterData));
Jobs = GameData.Jobs(gameData);
AllJobGroups = GameData.AllJobGroups(gameData);
JobGroups = GameData.JobGroups(gameData);
Jobs = jobs;
JobGroups = jobGroups;
_changeJobHook.Enable();
}

View file

@ -2,8 +2,6 @@
using System;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Glamourer.Customization;
using Glamourer.Structs;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Penumbra.String;
@ -116,8 +114,8 @@ public readonly unsafe struct Actor : IEquatable<Actor>
public CharacterWeapon GetOffhand()
=> new(AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.OffHand).ModelId.Value);
public Customize GetCustomize()
=> *(Customize*)&AsCharacter->DrawData.CustomizeData;
public CustomizeArray GetCustomize()
=> *(CustomizeArray*)&AsCharacter->DrawData.CustomizeData;
// TODO remove this when available in ClientStructs
internal ref CrestFlag CrestBitfield

View file

@ -1,7 +1,6 @@
using System;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Glamourer.Customization;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Object = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.Object;
@ -91,8 +90,8 @@ public readonly unsafe struct Model : IEquatable<Model>
public CharacterArmor GetArmor(EquipSlot slot)
=> ((CharacterArmor*)&AsHuman->Head)[slot.ToIndex()];
public Customize GetCustomize()
=> *(Customize*)&AsHuman->Customize;
public CustomizeArray GetCustomize()
=> *(CustomizeArray*)&AsHuman->Customize;
public (Model Address, CharacterWeapon Data) GetMainhand()
{

View file

@ -4,6 +4,7 @@ using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures;
using Glamourer.Events;
using Glamourer.Interop.Structs;
using Penumbra.GameData;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;

View file

@ -5,7 +5,6 @@ using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using Glamourer.Events;
using Glamourer.Interop.Structs;
using Glamourer.Structs;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -14,18 +13,15 @@ namespace Glamourer.Interop;
public unsafe class WeaponService : IDisposable
{
private readonly WeaponLoading _event;
private readonly CrestService _crestService;
private readonly ThreadLocal<bool> _inUpdate = new(() => false);
private readonly delegate* unmanaged[Stdcall]<DrawDataContainer*, uint, ulong, byte, byte, byte, byte, void>
_original;
public WeaponService(WeaponLoading @event, IGameInteropProvider interop, CrestService crestService)
public WeaponService(WeaponLoading @event, IGameInteropProvider interop)
{
_event = @event;
_crestService = crestService;
_event = @event;
_loadWeaponHook =
interop.HookFromAddress<LoadWeaponDelegate>((nint)DrawDataContainer.MemberFunctionPointers.LoadWeapon, LoadWeaponDetour);
_original =

View file

@ -5,13 +5,11 @@ using Dalamud.Game.Command;
using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Plugin.Services;
using Glamourer.Automation;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Gui;
using Glamourer.Interop;
using Glamourer.State;
using Glamourer.Structs;
using ImGuiNET;
using OtterGui;
using OtterGui.Classes;

View file

@ -2,10 +2,11 @@ using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Dalamud.Plugin.Services;
using Glamourer.Customization;
using Glamourer.GameData;
using OtterGui.Services;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Services;
@ -30,13 +31,12 @@ public sealed class CustomizationService(
public Task Awaiter
=> _task;
public (Customize NewValue, CustomizeFlag Applied, CustomizeFlag Changed) Combine(Customize oldValues, Customize newValues,
public (CustomizeArray NewValue, CustomizeFlag Applied, CustomizeFlag Changed) Combine(CustomizeArray oldValues, CustomizeArray newValues,
CustomizeFlag applyWhich, bool allowUnknown)
{
CustomizeFlag applied = 0;
CustomizeFlag changed = 0;
Customize ret = default;
ret.Load(oldValues);
var ret = oldValues;
if (applyWhich.HasFlag(CustomizeFlag.Clan))
{
changed |= ChangeClan(ref ret, newValues.Clan);
@ -247,7 +247,7 @@ public sealed class CustomizationService(
}
/// <summary> Change a clan while keeping all other customizations valid. </summary>
public CustomizeFlag ChangeClan(ref Customize customize, SubRace newClan)
public CustomizeFlag ChangeClan(ref CustomizeArray customize, SubRace newClan)
{
if (customize.Clan == newClan)
return 0;
@ -271,7 +271,7 @@ public sealed class CustomizationService(
}
/// <summary> Change a gender while keeping all other customizations valid. </summary>
public CustomizeFlag ChangeGender(ref Customize customize, Gender newGender)
public CustomizeFlag ChangeGender(ref CustomizeArray customize, Gender newGender)
{
if (customize.Gender == newGender)
return 0;
@ -288,7 +288,7 @@ public sealed class CustomizationService(
return FixValues(set, ref customize) | CustomizeFlag.Gender;
}
private static CustomizeFlag FixValues(CustomizationSet set, ref Customize customize)
private static CustomizeFlag FixValues(CustomizationSet set, ref CustomizeArray customize)
{
CustomizeFlag flags = 0;
foreach (var idx in CustomizationExtensions.AllBasic)

View file

@ -15,7 +15,6 @@ using Glamourer.Gui.Tabs.UnlocksTab;
using Glamourer.Interop;
using Glamourer.Interop.Penumbra;
using Glamourer.State;
using Glamourer.Structs;
using Glamourer.Unlocks;
using Microsoft.Extensions.DependencyInjection;
using OtterGui.Classes;
@ -24,6 +23,7 @@ using OtterGui.Services;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Data;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Services;

View file

@ -1,13 +1,11 @@
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Structs;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
using System.Linq;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin.Services;
using CustomizeIndex = Glamourer.Customization.CustomizeIndex;
using Penumbra.GameData.Structs;
namespace Glamourer.State;
@ -34,7 +32,7 @@ public class ActorState
public DesignData ModelData;
/// <summary> The last seen job. </summary>
public byte LastJob;
public JobId LastJob;
/// <summary> The Lock-Key locking this state. </summary>
public uint Combination;

View file

@ -2,7 +2,6 @@
using System.Linq;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Interface.Internal.Notifications;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Gui;
using Glamourer.Interop;
@ -12,7 +11,7 @@ using ImGuiNET;
using OtterGui.Classes;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using CustomizeIndex = Glamourer.Customization.CustomizeIndex;
using CustomizeIndex = Penumbra.GameData.Enums.CustomizeIndex;
namespace Glamourer.State;
@ -107,7 +106,7 @@ public unsafe class FunModule : IDisposable
}
}
public void ApplyFun(Actor actor, Span<CharacterArmor> armor, ref Customize customize)
public void ApplyFun(Actor actor, Span<CharacterArmor> armor, ref CustomizeArray customize)
{
if (!ValidFunTarget(actor))
return;
@ -181,7 +180,7 @@ public unsafe class FunModule : IDisposable
}
}
public void ApplyOops(ref Customize customize)
public void ApplyOops(ref CustomizeArray customize)
{
if (_codes.EnabledOops == Race.Unknown)
return;
@ -193,7 +192,7 @@ public unsafe class FunModule : IDisposable
_customizations.ChangeClan(ref customize, targetClan);
}
public void ApplyIndividual(ref Customize customize)
public void ApplyIndividual(ref CustomizeArray customize)
{
if (!_codes.EnabledIndividual)
return;
@ -209,7 +208,7 @@ public unsafe class FunModule : IDisposable
}
}
public void Apply63(ref Customize customize)
public void Apply63(ref CustomizeArray customize)
{
if (!_codes.Enabled63 || customize.Race is Race.Hrothgar) // TODO Female Hrothgar
return;
@ -217,7 +216,7 @@ public unsafe class FunModule : IDisposable
_customizations.ChangeGender(ref customize, customize.Gender is Gender.Male ? Gender.Female : Gender.Male);
}
public void ApplySizing(Actor actor, ref Customize customize)
public void ApplySizing(Actor actor, ref CustomizeArray customize)
{
if (_codes.EnabledSizing == CodeService.Sizing.None)
return;

View file

@ -1,11 +1,9 @@
using System.Linq;
using Glamourer.Customization;
using Glamourer.Events;
using Glamourer.Interop;
using Glamourer.Interop.Penumbra;
using Glamourer.Interop.Structs;
using Glamourer.Services;
using Glamourer.Structs;
using Penumbra.Api.Enums;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -40,7 +38,7 @@ public class StateApplier(UpdateSlotService _updateSlot, VisorService _visor, We
/// Change the customization values of actors either by applying them via update or redrawing,
/// this depends on whether the changes include changes to Race, Gender, Body Type or Face.
/// </summary>
public unsafe void ChangeCustomize(ActorData data, in Customize customize, ActorState? state = null)
public unsafe void ChangeCustomize(ActorData data, in CustomizeArray customize, ActorState? _ = null)
{
foreach (var actor in data.Objects)
{
@ -48,15 +46,15 @@ public class StateApplier(UpdateSlotService _updateSlot, VisorService _visor, We
if (!mdl.IsCharacterBase)
continue;
var flags = Customize.Compare(mdl.GetCustomize(), customize);
var flags = CustomizeArray.Compare(mdl.GetCustomize(), customize);
if (!flags.RequiresRedraw() || !mdl.IsHuman)
{
_changeCustomize.UpdateCustomize(mdl, customize.Data);
_changeCustomize.UpdateCustomize(mdl, customize);
}
else if (data.Objects.Count > 1 && _objects.IsInGPose && !actor.IsGPoseOrCutscene)
{
var mdlCustomize = (Customize*)&mdl.AsHuman->Customize;
mdlCustomize->Load(customize);
var mdlCustomize = (CustomizeArray*)&mdl.AsHuman->Customize;
*mdlCustomize = customize;
_penumbra.RedrawObject(actor, RedrawType.AfterGPose);
}
else
@ -66,7 +64,7 @@ public class StateApplier(UpdateSlotService _updateSlot, VisorService _visor, We
}
}
/// <inheritdoc cref="ChangeCustomize(ActorData, in Customize, ActorState?)"/>
/// <inheritdoc cref="ChangeCustomize(ActorData, in CustomizeArray, ActorState?)"/>
public ActorData ChangeCustomize(ActorState state, bool apply)
{
var data = GetData(state);

View file

@ -1,10 +1,8 @@
using System;
using System.Linq;
using Dalamud.Plugin.Services;
using Glamourer.Customization;
using Glamourer.Events;
using Glamourer.Services;
using Glamourer.Structs;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
@ -30,7 +28,7 @@ public class StateEditor
/// <summary> Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. </summary>
/// <remarks> We currently only allow changing things to humans, not humans to monsters. </remarks>
public bool ChangeModelId(ActorState state, uint modelId, in Customize customize, nint equipData, StateChanged.Source source,
public bool ChangeModelId(ActorState state, uint modelId, in CustomizeArray customize, nint equipData, StateChanged.Source source,
out uint oldModelId, uint key = 0)
{
oldModelId = state.ModelData.ModelId;
@ -57,7 +55,7 @@ public class StateEditor
return false;
// Fix up everything else to make sure the result is a valid human.
state.ModelData.Customize = Customize.Default;
state.ModelData.Customize = CustomizeArray.Default;
state.ModelData.SetDefaultEquipment(_items);
state.ModelData.SetHatVisible(true);
state.ModelData.SetWeaponVisible(true);
@ -104,8 +102,8 @@ public class StateEditor
}
/// <summary> Change an entire customization array according to flags. </summary>
public bool ChangeHumanCustomize(ActorState state, in Customize customizeInput, CustomizeFlag applyWhich, StateChanged.Source source,
out Customize old, out CustomizeFlag changed, uint key = 0)
public bool ChangeHumanCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag applyWhich, StateChanged.Source source,
out CustomizeArray old, out CustomizeFlag changed, uint key = 0)
{
old = state.ModelData.Customize;
changed = 0;

View file

@ -1,5 +1,4 @@
using Glamourer.Automation;
using Glamourer.Customization;
using Glamourer.Events;
using Glamourer.Interop;
using Glamourer.Interop.Penumbra;
@ -12,7 +11,6 @@ using Penumbra.GameData.Structs;
using System;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Plugin.Services;
using Glamourer.Structs;
using Penumbra.GameData.DataContainers;
namespace Glamourer.State;
@ -114,7 +112,7 @@ public class StateListener : IDisposable
_creatingIdentifier = actor.GetIdentifier(_actors);
ref var modelId = ref *(uint*)modelPtr;
ref var customize = ref *(Customize*)customizePtr;
ref var customize = ref *(CustomizeArray*)customizePtr;
if (_autoDesignApplier.Reduce(actor, _creatingIdentifier, out _creatingState))
{
switch (UpdateBaseData(actor, _creatingState, modelId, customizePtr, equipDataPtr))
@ -140,7 +138,7 @@ public class StateListener : IDisposable
ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender);
}
private unsafe void OnCustomizeChange(Model model, Ref<Customize> customize)
private unsafe void OnCustomizeChange(Model model, Ref<CustomizeArray> customize)
{
if (!model.IsHuman)
return;
@ -156,7 +154,7 @@ public class StateListener : IDisposable
UpdateCustomize(actor, state, ref customize.Value, false);
}
private void UpdateCustomize(Actor actor, ActorState state, ref Customize customize, bool checkTransform)
private void UpdateCustomize(Actor actor, ActorState state, ref CustomizeArray customize, bool checkTransform)
{
switch (UpdateBaseData(actor, state, customize, checkTransform))
{
@ -515,7 +513,7 @@ public class StateListener : IDisposable
if (isHuman)
state.BaseData = _manager.FromActor(actor, false, false);
else
state.BaseData.LoadNonHuman(modelId, *(Customize*)customizeData, equipData);
state.BaseData.LoadNonHuman(modelId, *(CustomizeArray*)customizeData, equipData);
return UpdateState.Change;
}
@ -526,7 +524,7 @@ public class StateListener : IDisposable
/// only if we kept track of state of someone who went to the aesthetician,
/// or if they used other tools to change things.
/// </summary>
private UpdateState UpdateBaseData(Actor actor, ActorState state, Customize customize, bool checkTransform)
private UpdateState UpdateBaseData(Actor actor, ActorState state, CustomizeArray customize, bool checkTransform)
{
// Customize array does not agree between game object and draw object => transformation.
if (checkTransform && !actor.GetCustomize().Equals(customize))
@ -537,7 +535,7 @@ public class StateListener : IDisposable
return UpdateState.NoChange; // TODO: handle wrong base data.
// Update customize base state.
state.BaseData.Customize.Load(customize);
state.BaseData.Customize = customize;
return UpdateState.Change;
}

View file

@ -4,13 +4,11 @@ using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Dalamud.Plugin.Services;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Interop;
using Glamourer.Interop.Structs;
using Glamourer.Services;
using Glamourer.Structs;
using Penumbra.GameData.Actors;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums;
@ -18,8 +16,15 @@ using Penumbra.GameData.Structs;
namespace Glamourer.State;
public class StateManager(ActorManager _actors, ItemManager _items, StateChanged _event, StateApplier _applier, StateEditor _editor,
HumanModelList _humans, ICondition _condition, IClientState _clientState)
public class StateManager(
ActorManager _actors,
ItemManager _items,
StateChanged _event,
StateApplier _applier,
StateEditor _editor,
HumanModelList _humans,
ICondition _condition,
IClientState _clientState)
: IReadOnlyDictionary<ActorIdentifier, ActorState>
{
private readonly Dictionary<ActorIdentifier, ActorState> _states = [];
@ -102,7 +107,7 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged
// TODO reverse search model data to get model id from model.
if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId))
{
ret.LoadNonHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId, *(Customize*)&actor.AsCharacter->DrawData.CustomizeData,
ret.LoadNonHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId, *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData,
(nint)(&actor.AsCharacter->DrawData.Head));
return ret;
}
@ -194,9 +199,9 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged
return;
var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Skeleton, (Variant)offhand.Weapon.Id);
offhand.Skeleton = (PrimaryId)(mainhand.Skeleton.Id + 50);
offhand.Variant = mainhand.Variant;
offhand.Weapon = mainhand.Weapon;
offhand.Skeleton = (PrimaryId)(mainhand.Skeleton.Id + 50);
offhand.Variant = mainhand.Variant;
offhand.Weapon = mainhand.Weapon;
ret.SetItem(EquipSlot.Hands, gauntlets);
ret.SetStain(EquipSlot.Hands, mainhand.Stain);
}
@ -205,10 +210,10 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged
/// <summary> Turn an actor human. </summary>
public void TurnHuman(ActorState state, StateChanged.Source source, uint key = 0)
=> ChangeModelId(state, 0, Customize.Default, nint.Zero, source, key);
=> ChangeModelId(state, 0, CustomizeArray.Default, nint.Zero, source, key);
/// <summary> Turn an actor to. </summary>
public void ChangeModelId(ActorState state, uint modelId, Customize customize, nint equipData, StateChanged.Source source,
public void ChangeModelId(ActorState state, uint modelId, CustomizeArray customize, nint equipData, StateChanged.Source source,
uint key = 0)
{
if (!_editor.ChangeModelId(state, modelId, customize, equipData, source, out var old, key))
@ -233,7 +238,8 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged
}
/// <summary> Change an entire customization array according to flags. </summary>
public void ChangeCustomize(ActorState state, in Customize customizeInput, CustomizeFlag apply, StateChanged.Source source, uint key = 0)
public void ChangeCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag apply, StateChanged.Source source,
uint key = 0)
{
if (!_editor.ChangeHumanCustomize(state, customizeInput, apply, source, out var old, out var applied, key))
return;
@ -447,7 +453,7 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged
var redraw = state.ModelData.ModelId != state.BaseData.ModelId
|| !state.ModelData.IsHuman
|| Customize.Compare(state.ModelData.Customize, state.BaseData.Customize).RequiresRedraw();
|| CustomizeArray.Compare(state.ModelData.Customize, state.BaseData.Customize).RequiresRedraw();
state.ModelData = state.BaseData;
state.ModelData.SetIsWet(false);
foreach (var index in Enum.GetValues<CustomizeIndex>())
@ -470,7 +476,7 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged
actors = ApplyAll(state, redraw, true);
Glamourer.Log.Verbose(
$"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]");
_event.Invoke(StateChanged.Type.Reset, StateChanged.Source.Manual, state, actors, null);
_event.Invoke(StateChanged.Type.Reset, StateChanged.Source.Manual, state, actors);
}
public void ResetStateFixed(ActorState state, uint key = 0)
@ -538,7 +544,7 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged
if (!GetOrCreate(actor, out var state))
return;
ApplyAll(state, !actor.Model.IsHuman || Customize.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(),
ApplyAll(state, !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(),
false);
}

View file

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using Glamourer.Interop.Structs;
using Glamourer.Structs;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;

View file

@ -8,10 +8,11 @@ using Dalamud.Plugin.Services;
using Dalamud.Utility;
using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game.UI;
using Glamourer.Customization;
using Glamourer.GameData;
using Glamourer.Events;
using Glamourer.Services;
using Lumina.Excel.GeneratedSheets;
using Penumbra.GameData.Enums;
namespace Glamourer.Unlocks;
@ -172,7 +173,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable
=> UnlockDictionaryHelpers.Load(ToFilename(_saveService.FileNames), _unlocked, id => Unlockable.Any(c => c.Value.Data == id),
"customization");
/// <summary> Create a list of all unlockable hairstyles and facepaints. </summary>
/// <summary> Create a list of all unlockable hairstyles and face paints. </summary>
private static Dictionary<CustomizeData, (uint Data, string Name)> CreateUnlockableCustomizations(CustomizationService customizations,
IDataManager gameData)
{

@ -1 +1 @@
Subproject commit 3787e82d1b84d2542b6e4238060d75383a4b12a1
Subproject commit d9a962748364b267df1b186cdaebb9ed9f3fceb6