Merge branch 'main' into Limiana/main

This commit is contained in:
Ottermandias 2023-12-31 13:33:24 +01:00
commit 5f28644b56
138 changed files with 3314 additions and 4006 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,38 +0,0 @@
namespace Glamourer.Customization;
// Localization from the game files directly.
public enum CustomName
{
MidlanderM,
HighlanderM,
WildwoodM,
DuskwightM,
PlainsfolkM,
DunesfolkM,
SeekerOfTheSunM,
KeeperOfTheMoonM,
SeawolfM,
HellsguardM,
RaenM,
XaelaM,
HelionM,
LostM,
RavaM,
VeenaM,
MidlanderF,
HighlanderF,
WildwoodF,
DuskwightF,
PlainsfolkF,
DunesfolkF,
SeekerOfTheSunF,
KeeperOfTheMoonF,
SeawolfF,
HellsguardF,
RaenF,
XaelaF,
HelionF,
LostF,
RavaF,
VeenaF,
}

View file

@ -1,38 +0,0 @@
using System.Collections.Generic;
using Dalamud.Interface.Internal;
using Dalamud.Plugin.Services;
using Penumbra.GameData.Enums;
namespace Glamourer.Customization;
public class CustomizationManager : ICustomizationManager
{
private static CustomizationOptions? _options;
private CustomizationManager()
{ }
public static ICustomizationManager Create(ITextureProvider textures, IDataManager gameData, IPluginLog log)
{
_options ??= new CustomizationOptions(textures, gameData, log);
return new CustomizationManager();
}
public IReadOnlyList<Race> Races
=> CustomizationOptions.Races;
public IReadOnlyList<SubRace> Clans
=> CustomizationOptions.Clans;
public IReadOnlyList<Gender> Genders
=> CustomizationOptions.Genders;
public CustomizationSet GetList(SubRace clan, Gender gender)
=> _options!.GetList(clan, gender);
public IDalamudTextureWrap GetIcon(uint iconId)
=> _options!.GetIcon(iconId);
public string GetName(CustomName name)
=> _options!.GetName(name);
}

View file

@ -1,362 +0,0 @@
using System;
using Dalamud.Plugin.Services;
using Lumina.Excel;
using Lumina.Excel.GeneratedSheets;
using OtterGui;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Penumbra.GameData;
namespace Glamourer.Customization;
public static class CustomizationNpcOptions
{
public unsafe struct NpcData
{
public string Name;
public Customize Customize;
private fixed byte _equip[40];
public CharacterWeapon Mainhand;
public CharacterWeapon Offhand;
public uint Id;
public bool VisorToggled;
public ObjectKind Kind;
public ReadOnlySpan<CharacterArmor> Equip
{
get
{
fixed (byte* ptr = _equip)
{
return new ReadOnlySpan<CharacterArmor>((CharacterArmor*)ptr, 10);
}
}
}
public string WriteGear()
{
var sb = new StringBuilder(128);
var span = Equip;
for (var i = 0; i < 10; ++i)
{
sb.Append(span[i].Set.Id.ToString("D4"));
sb.Append('-');
sb.Append(span[i].Variant.Id.ToString("D3"));
sb.Append('-');
sb.Append(span[i].Stain.Id.ToString("D3"));
sb.Append(", ");
}
sb.Append(Mainhand.Set.Id.ToString("D4"));
sb.Append('-');
sb.Append(Mainhand.Type.Id.ToString("D4"));
sb.Append('-');
sb.Append(Mainhand.Variant.Id.ToString("D3"));
sb.Append('-');
sb.Append(Mainhand.Stain.Id.ToString("D4"));
sb.Append(", ");
sb.Append(Offhand.Set.Id.ToString("D4"));
sb.Append('-');
sb.Append(Offhand.Type.Id.ToString("D4"));
sb.Append('-');
sb.Append(Offhand.Variant.Id.ToString("D3"));
sb.Append('-');
sb.Append(Offhand.Stain.Id.ToString("D3"));
return sb.ToString();
}
internal void Set(int idx, uint value)
{
fixed (byte* ptr = _equip)
{
((uint*)ptr)[idx] = value;
}
}
public bool DataEquals(in NpcData other)
{
if (VisorToggled != other.VisorToggled)
return false;
if (!Customize.Equals(other.Customize))
return false;
if (!Mainhand.Equals(other.Mainhand))
return false;
if (!Offhand.Equals(other.Offhand))
return false;
fixed (byte* ptr1 = _equip, ptr2 = other._equip)
{
return new ReadOnlySpan<byte>(ptr1, 40).SequenceEqual(new ReadOnlySpan<byte>(ptr2, 40));
}
}
}
private static void ApplyNpcEquip(ref NpcData data, NpcEquip row)
{
data.Set(0, row.ModelHead | (row.DyeHead.Row << 24));
data.Set(1, row.ModelBody | (row.DyeBody.Row << 24));
data.Set(2, row.ModelHands | (row.DyeHands.Row << 24));
data.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24));
data.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24));
data.Set(5, row.ModelEars | (row.DyeEars.Row << 24));
data.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24));
data.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24));
data.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24));
data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24));
data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48));
data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48));
data.VisorToggled = row.Visor;
}
public static unsafe IReadOnlyList<NpcData> CreateNpcData(IReadOnlyDictionary<uint, string> eNpcs,
IReadOnlyDictionary<uint, string> bnpcNames, IObjectIdentifier identifier, IDataManager data)
{
var enpcSheet = data.GetExcelSheet<ENpcBase>()!;
var bnpcSheet = data.GetExcelSheet<BNpcBase>()!;
var list = new List<NpcData>(eNpcs.Count + (int)bnpcSheet.RowCount);
foreach (var (id, name) in eNpcs)
{
var row = enpcSheet.GetRow(id);
if (row == null || name.IsNullOrWhitespace())
continue;
var (valid, customize) = FromEnpcBase(row);
if (!valid)
continue;
var ret = new NpcData
{
Name = name,
Customize = customize,
Id = id,
Kind = ObjectKind.EventNpc,
};
if (row.NpcEquip.Row != 0 && row.NpcEquip.Value is { } equip)
{
ApplyNpcEquip(ref ret, equip);
}
else
{
ret.Set(0, row.ModelHead | (row.DyeHead.Row << 24));
ret.Set(1, row.ModelBody | (row.DyeBody.Row << 24));
ret.Set(2, row.ModelHands | (row.DyeHands.Row << 24));
ret.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24));
ret.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24));
ret.Set(5, row.ModelEars | (row.DyeEars.Row << 24));
ret.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24));
ret.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24));
ret.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24));
ret.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24));
ret.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48));
ret.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48));
ret.VisorToggled = row.Visor;
}
list.Add(ret);
}
foreach (var baseRow in bnpcSheet)
{
if (baseRow.ModelChara.Value!.Type != 1)
continue;
var bnpcNameIds = identifier.GetBnpcNames(baseRow.RowId);
if (bnpcNameIds.Count == 0)
continue;
var (valid, customize) = FromBnpcCustomize(baseRow.BNpcCustomize.Value!);
if (!valid)
continue;
var equip = baseRow.NpcEquip.Value!;
var ret = new NpcData
{
Customize = customize,
Id = baseRow.RowId,
Kind = ObjectKind.BattleNpc,
};
ApplyNpcEquip(ref ret, equip);
foreach (var bnpcNameId in bnpcNameIds)
{
if (bnpcNames.TryGetValue(bnpcNameId.Id, out var name) && !name.IsNullOrWhitespace())
list.Add(ret with { Name = name });
}
}
var groups = list.GroupBy(d => d.Name).ToDictionary(g => g.Key, g => g.ToList());
list.Clear();
foreach (var (name, duplicates) in groups.OrderBy(kvp => kvp.Key))
{
for (var i = 0; i < duplicates.Count; ++i)
{
var current = duplicates[i];
var add = true;
for (var j = 0; j < i; ++j)
{
if (current.DataEquals(duplicates[j]))
{
duplicates.RemoveAt(i--);
break;
}
}
}
if (duplicates.Count == 1)
list.Add(duplicates[0]);
else
list.AddRange(duplicates
.Select(duplicate => duplicate with { Name = $"{name} ({(duplicate.Kind is ObjectKind.BattleNpc ? 'B' : 'E')}{duplicate.Id})" }));
}
var lastWeird = list.FindIndex(d => char.IsAsciiLetterOrDigit(d.Name[0]));
if (lastWeird != -1)
{
list.AddRange(list.Take(lastWeird));
list.RemoveRange(0, lastWeird);
}
return list;
}
public static Dictionary<(SubRace, Gender), IReadOnlyList<(CustomizeIndex, CustomizeValue)>> CreateNpcData(CustomizationSet[] sets,
ExcelSheet<BNpcCustomize> bNpc, ExcelSheet<ENpcBase> eNpc)
{
var customizes = bNpc.SelectWhere(FromBnpcCustomize)
.Concat(eNpc.SelectWhere(FromEnpcBase)).ToList();
var dict = new Dictionary<(SubRace, Gender), HashSet<(CustomizeIndex, CustomizeValue)>>();
var customizeIndices = new[]
{
CustomizeIndex.Face,
CustomizeIndex.Hairstyle,
CustomizeIndex.LipColor,
CustomizeIndex.SkinColor,
CustomizeIndex.FacePaintColor,
CustomizeIndex.HighlightsColor,
CustomizeIndex.HairColor,
CustomizeIndex.FacePaint,
CustomizeIndex.TattooColor,
CustomizeIndex.EyeColorLeft,
CustomizeIndex.EyeColorRight,
};
foreach (var customize in customizes)
{
var set = sets[CustomizationOptions.ToIndex(customize.Clan, customize.Gender)];
foreach (var customizeIndex in customizeIndices)
{
var value = customize[customizeIndex];
if (value == CustomizeValue.Zero)
continue;
if (set.DataByValue(customizeIndex, value, out _, customize.Face) >= 0)
continue;
if (!dict.TryGetValue((set.Clan, set.Gender), out var npcSet))
{
npcSet = new HashSet<(CustomizeIndex, CustomizeValue)> { (customizeIndex, value) };
dict.Add((set.Clan, set.Gender), npcSet);
}
else
{
npcSet.Add((customizeIndex, value));
}
}
}
return dict.ToDictionary(kvp => kvp.Key,
kvp => (IReadOnlyList<(CustomizeIndex, CustomizeValue)>)kvp.Value.OrderBy(p => p.Item1).ThenBy(p => p.Item2.Value).ToArray());
}
private static (bool, Customize) 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);
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 (true, customize);
}
private static (bool, Customize) FromEnpcBase(ENpcBase enpcBase)
{
if (enpcBase.ModelChara.Value?.Type != 1)
return (false, Customize.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);
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 (true, customize);
}
}

View file

@ -1,526 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Dalamud;
using Dalamud.Interface.Internal;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using Lumina.Excel;
using Lumina.Excel.GeneratedSheets;
using OtterGui.Classes;
using Penumbra.GameData.Enums;
using Race = Penumbra.GameData.Enums.Race;
namespace Glamourer.Customization;
// Generate everything about customization per tribe and gender.
public partial class CustomizationOptions
{
// All races except for Unknown
internal static readonly Race[] Races = ((Race[])Enum.GetValues(typeof(Race))).Skip(1).ToArray();
// All tribes except for Unknown
internal static readonly SubRace[] Clans = ((SubRace[])Enum.GetValues(typeof(SubRace))).Skip(1).ToArray();
// Two genders.
internal static readonly Gender[] Genders =
{
Gender.Male,
Gender.Female,
};
// Every tribe and gender has a separate set of available customizations.
internal CustomizationSet GetList(SubRace race, Gender gender)
=> _customizationSets[ToIndex(race, gender)];
// Get specific icons.
internal IDalamudTextureWrap GetIcon(uint id)
=> _icons.LoadIcon(id)!;
private readonly IconStorage _icons;
private static readonly int ListSize = Clans.Length * Genders.Length;
private readonly CustomizationSet[] _customizationSets = new CustomizationSet[ListSize];
// Get the index for the given pair of tribe and gender.
internal static int ToIndex(SubRace race, Gender gender)
{
var idx = ((int)race - 1) * Genders.Length + (gender == Gender.Female ? 1 : 0);
if (idx < 0 || idx >= ListSize)
ThrowException(race, gender);
return idx;
}
private static void ThrowException(SubRace race, Gender gender)
=> throw new Exception($"Invalid customization requested for {race} {gender}.");
}
public partial class CustomizationOptions
{
public string GetName(CustomName name)
=> _names[(int)name];
internal CustomizationOptions(ITextureProvider textures, IDataManager gameData, IPluginLog log)
{
var tmp = new TemporaryData(gameData, this, log);
_icons = new IconStorage(textures, gameData);
SetNames(gameData, tmp);
foreach (var race in Clans)
{
foreach (var gender in Genders)
_customizationSets[ToIndex(race, gender)] = tmp.GetSet(race, gender);
}
tmp.SetNpcData(_customizationSets);
}
// 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)
{
var subRace = gameData.GetExcelSheet<Tribe>()!;
void Set(CustomName id, Lumina.Text.SeString? s, string def)
=> _names[(int)id] = s?.ToDalamudString().TextValue ?? def;
Set(CustomName.MidlanderM, subRace.GetRow((int)SubRace.Midlander)?.Masculine, SubRace.Midlander.ToName());
Set(CustomName.MidlanderF, subRace.GetRow((int)SubRace.Midlander)?.Feminine, SubRace.Midlander.ToName());
Set(CustomName.HighlanderM, subRace.GetRow((int)SubRace.Highlander)?.Masculine, SubRace.Highlander.ToName());
Set(CustomName.HighlanderF, subRace.GetRow((int)SubRace.Highlander)?.Feminine, SubRace.Highlander.ToName());
Set(CustomName.WildwoodM, subRace.GetRow((int)SubRace.Wildwood)?.Masculine, SubRace.Wildwood.ToName());
Set(CustomName.WildwoodF, subRace.GetRow((int)SubRace.Wildwood)?.Feminine, SubRace.Wildwood.ToName());
Set(CustomName.DuskwightM, subRace.GetRow((int)SubRace.Duskwight)?.Masculine, SubRace.Duskwight.ToName());
Set(CustomName.DuskwightF, subRace.GetRow((int)SubRace.Duskwight)?.Feminine, SubRace.Duskwight.ToName());
Set(CustomName.PlainsfolkM, subRace.GetRow((int)SubRace.Plainsfolk)?.Masculine, SubRace.Plainsfolk.ToName());
Set(CustomName.PlainsfolkF, subRace.GetRow((int)SubRace.Plainsfolk)?.Feminine, SubRace.Plainsfolk.ToName());
Set(CustomName.DunesfolkM, subRace.GetRow((int)SubRace.Dunesfolk)?.Masculine, SubRace.Dunesfolk.ToName());
Set(CustomName.DunesfolkF, subRace.GetRow((int)SubRace.Dunesfolk)?.Feminine, SubRace.Dunesfolk.ToName());
Set(CustomName.SeekerOfTheSunM, subRace.GetRow((int)SubRace.SeekerOfTheSun)?.Masculine, SubRace.SeekerOfTheSun.ToName());
Set(CustomName.SeekerOfTheSunF, subRace.GetRow((int)SubRace.SeekerOfTheSun)?.Feminine, SubRace.SeekerOfTheSun.ToName());
Set(CustomName.KeeperOfTheMoonM, subRace.GetRow((int)SubRace.KeeperOfTheMoon)?.Masculine, SubRace.KeeperOfTheMoon.ToName());
Set(CustomName.KeeperOfTheMoonF, subRace.GetRow((int)SubRace.KeeperOfTheMoon)?.Feminine, SubRace.KeeperOfTheMoon.ToName());
Set(CustomName.SeawolfM, subRace.GetRow((int)SubRace.Seawolf)?.Masculine, SubRace.Seawolf.ToName());
Set(CustomName.SeawolfF, subRace.GetRow((int)SubRace.Seawolf)?.Feminine, SubRace.Seawolf.ToName());
Set(CustomName.HellsguardM, subRace.GetRow((int)SubRace.Hellsguard)?.Masculine, SubRace.Hellsguard.ToName());
Set(CustomName.HellsguardF, subRace.GetRow((int)SubRace.Hellsguard)?.Feminine, SubRace.Hellsguard.ToName());
Set(CustomName.RaenM, subRace.GetRow((int)SubRace.Raen)?.Masculine, SubRace.Raen.ToName());
Set(CustomName.RaenF, subRace.GetRow((int)SubRace.Raen)?.Feminine, SubRace.Raen.ToName());
Set(CustomName.XaelaM, subRace.GetRow((int)SubRace.Xaela)?.Masculine, SubRace.Xaela.ToName());
Set(CustomName.XaelaF, subRace.GetRow((int)SubRace.Xaela)?.Feminine, SubRace.Xaela.ToName());
Set(CustomName.HelionM, subRace.GetRow((int)SubRace.Helion)?.Masculine, SubRace.Helion.ToName());
Set(CustomName.HelionF, subRace.GetRow((int)SubRace.Helion)?.Feminine, SubRace.Helion.ToName());
Set(CustomName.LostM, subRace.GetRow((int)SubRace.Lost)?.Masculine, SubRace.Lost.ToName());
Set(CustomName.LostF, subRace.GetRow((int)SubRace.Lost)?.Feminine, SubRace.Lost.ToName());
Set(CustomName.RavaM, subRace.GetRow((int)SubRace.Rava)?.Masculine, SubRace.Rava.ToName());
Set(CustomName.RavaF, subRace.GetRow((int)SubRace.Rava)?.Feminine, SubRace.Rava.ToName());
Set(CustomName.VeenaM, subRace.GetRow((int)SubRace.Veena)?.Masculine, SubRace.Veena.ToName());
Set(CustomName.VeenaF, subRace.GetRow((int)SubRace.Veena)?.Feminine, SubRace.Veena.ToName());
}
private class TemporaryData
{
public bool Valid
=> _cmpFile.Valid;
public CustomizationSet GetSet(SubRace race, Gender gender)
{
var (skin, hair) = GetColors(race, gender);
var row = _listSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
var hrothgar = race.ToRace() == Race.Hrothgar;
// Create the initial set with all the easily accessible parameters available for anyone.
var set = new CustomizationSet(race, gender)
{
Voices = row.Voices,
HairStyles = GetHairStyles(race, gender),
HairColors = hair,
SkinColors = skin,
EyeColors = _eyeColorPicker,
HighlightColors = _highlightPicker,
TattooColors = _tattooColorPicker,
LipColorsDark = hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark,
LipColorsLight = hrothgar ? Array.Empty<CustomizeData>() : _lipColorPickerLight,
FacePaintColorsDark = _facePaintColorPickerDark,
FacePaintColorsLight = _facePaintColorPickerLight,
Faces = GetFaces(row),
NumEyebrows = GetListSize(row, CustomizeIndex.Eyebrows),
NumEyeShapes = GetListSize(row, CustomizeIndex.EyeShape),
NumNoseShapes = GetListSize(row, CustomizeIndex.Nose),
NumJawShapes = GetListSize(row, CustomizeIndex.Jaw),
NumMouthShapes = GetListSize(row, CustomizeIndex.Mouth),
FacePaints = GetFacePaints(race, gender),
TailEarShapes = GetTailEarShapes(row),
};
SetAvailability(set, row);
SetFacialFeatures(set, row);
SetHairByFace(set);
SetMenuTypes(set, row);
SetNames(set, row);
return set;
}
public void SetNpcData(CustomizationSet[] sets)
{
var data = CustomizationNpcOptions.CreateNpcData(sets, _bnpcCustomize, _enpcBase);
foreach (var set in sets)
{
if (data.TryGetValue((set.Clan, set.Gender), out var npcData))
set.NpcOptions = npcData.ToArray();
}
}
public TemporaryData(IDataManager gameData, CustomizationOptions options, IPluginLog log)
{
_options = options;
_cmpFile = new CmpFile(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?[]
{
"charamaketype",
gameData.Language.ToLumina(),
null,
}) as ExcelSheet<CharaMakeParams>;
_listSheet = tmp!;
_hairSheet = gameData.GetExcelSheet<HairMakeType>()!;
_highlightPicker = CreateColorPicker(CustomizeIndex.HighlightsColor, 256, 192);
_lipColorPickerDark = CreateColorPicker(CustomizeIndex.LipColor, 512, 96);
_lipColorPickerLight = CreateColorPicker(CustomizeIndex.LipColor, 1024, 96, true);
_eyeColorPicker = CreateColorPicker(CustomizeIndex.EyeColorLeft, 0, 192);
_facePaintColorPickerDark = CreateColorPicker(CustomizeIndex.FacePaintColor, 640, 96);
_facePaintColorPickerLight = CreateColorPicker(CustomizeIndex.FacePaintColor, 1152, 96, true);
_tattooColorPicker = CreateColorPicker(CustomizeIndex.TattooColor, 0, 192);
}
// Required sheets.
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;
// Those values are shared between all races.
private readonly CustomizeData[] _highlightPicker;
private readonly CustomizeData[] _eyeColorPicker;
private readonly CustomizeData[] _facePaintColorPickerDark;
private readonly CustomizeData[] _facePaintColorPickerLight;
private readonly CustomizeData[] _lipColorPickerDark;
private readonly CustomizeData[] _lipColorPickerLight;
private readonly CustomizeData[] _tattooColorPicker;
private readonly CustomizationOptions _options;
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();
private void SetHairByFace(CustomizationSet set)
{
if (set.Race != Race.Hrothgar)
{
set.HairByFace = Enumerable.Repeat(set.HairStyles, set.Faces.Count + 1).ToArray();
return;
}
var tmp = new IReadOnlyList<CustomizeData>[set.Faces.Count + 1];
tmp[0] = set.HairStyles;
for (var i = 1; i <= set.Faces.Count; ++i)
{
bool Valid(CustomizeData c)
{
var data = _customizeSheet.GetRow(c.CustomizeId)?.Unknown6 ?? 0;
return data == 0 || data == i + set.Faces.Count;
}
tmp[i] = set.HairStyles.Where(Valid).ToArray();
}
set.HairByFace = tmp;
}
private static void SetMenuTypes(CustomizationSet set, CharaMakeParams row)
{
// Set up the menu types for all customizations.
set.Types = Enum.GetValues<CustomizeIndex>().Select(c =>
{
// Those types are not correctly given in the menu, so special case them to color pickers.
switch (c)
{
case CustomizeIndex.HighlightsColor:
case CustomizeIndex.EyeColorLeft:
case CustomizeIndex.EyeColorRight:
case CustomizeIndex.FacePaintColor:
return CharaMakeParams.MenuType.ColorPicker;
case CustomizeIndex.BodyType: return CharaMakeParams.MenuType.Nothing;
case CustomizeIndex.FacePaintReversed:
case CustomizeIndex.Highlights:
case CustomizeIndex.SmallIris:
case CustomizeIndex.Lipstick:
return CharaMakeParams.MenuType.Checkmark;
case CustomizeIndex.FacialFeature1:
case CustomizeIndex.FacialFeature2:
case CustomizeIndex.FacialFeature3:
case CustomizeIndex.FacialFeature4:
case CustomizeIndex.FacialFeature5:
case CustomizeIndex.FacialFeature6:
case CustomizeIndex.FacialFeature7:
case CustomizeIndex.LegacyTattoo:
return CharaMakeParams.MenuType.IconCheckmark;
}
var gameId = c.ToByteAndMask().ByteIdx;
// Otherwise find the first menu corresponding to the id.
// If there is none, assume a list.
var menu = row.Menus
.Cast<CharaMakeParams.Menu?>()
.FirstOrDefault(m => m!.Value.Customize == gameId);
var ret = menu?.Type ?? CharaMakeParams.MenuType.ListSelector;
if (c is CustomizeIndex.TailShape && ret is CharaMakeParams.MenuType.ListSelector)
ret = CharaMakeParams.MenuType.List1Selector;
return ret;
}).ToArray();
set.Order = CustomizationSet.ComputeOrder(set);
}
// 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)
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);
Set(true, CustomizeIndex.Highlights);
Set(true, CustomizeIndex.SkinColor);
Set(true, CustomizeIndex.EyeColorRight);
Set(true, CustomizeIndex.HairColor);
Set(true, CustomizeIndex.HighlightsColor);
Set(true, CustomizeIndex.TattooColor);
Set(set.NumEyebrows > 0, CustomizeIndex.Eyebrows);
Set(true, CustomizeIndex.EyeColorLeft);
Set(set.NumEyeShapes > 0, CustomizeIndex.EyeShape);
Set(set.NumNoseShapes > 0, CustomizeIndex.Nose);
Set(set.NumJawShapes > 0, CustomizeIndex.Jaw);
Set(set.NumMouthShapes > 0, CustomizeIndex.Mouth);
Set(set.LipColorsDark.Count > 0, CustomizeIndex.LipColor);
Set(GetListSize(row, CustomizeIndex.MuscleMass) > 0, CustomizeIndex.MuscleMass);
Set(set.TailEarShapes.Count > 0, CustomizeIndex.TailShape);
Set(GetListSize(row, CustomizeIndex.BustSize) > 0, CustomizeIndex.BustSize);
Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaint);
Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaintColor);
Set(true, CustomizeIndex.FacialFeature1);
Set(true, CustomizeIndex.FacialFeature2);
Set(true, CustomizeIndex.FacialFeature3);
Set(true, CustomizeIndex.FacialFeature4);
Set(true, CustomizeIndex.FacialFeature5);
Set(true, CustomizeIndex.FacialFeature6);
Set(true, CustomizeIndex.FacialFeature7);
Set(true, CustomizeIndex.LegacyTattoo);
Set(true, CustomizeIndex.SmallIris);
Set(set.Race != Race.Hrothgar, CustomizeIndex.Lipstick);
Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaintReversed);
}
// Create a list of lists of facial features and the legacy tattoo.
private static void SetFacialFeatures(CustomizationSet set, CharaMakeParams row)
{
var count = set.Faces.Count;
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();
for (var i = 0; i < count; ++i)
{
var data = row.FacialFeatureByFace[i].Icons;
tmp[0][i + 1] = Create(CustomizeIndex.FacialFeature1, data[0]);
tmp[1][i + 1] = Create(CustomizeIndex.FacialFeature2, data[1]);
tmp[2][i + 1] = Create(CustomizeIndex.FacialFeature3, data[2]);
tmp[3][i + 1] = Create(CustomizeIndex.FacialFeature4, data[3]);
tmp[4][i + 1] = Create(CustomizeIndex.FacialFeature5, data[4]);
tmp[5][i + 1] = Create(CustomizeIndex.FacialFeature6, data[5]);
tmp[6][i + 1] = Create(CustomizeIndex.FacialFeature7, data[6]);
}
set.FacialFeature1 = tmp[0];
set.FacialFeature2 = tmp[1];
set.FacialFeature3 = tmp[2];
set.FacialFeature4 = tmp[3];
set.FacialFeature5 = tmp[4];
set.FacialFeature6 = tmp[5];
set.FacialFeature7 = tmp[6];
}
// Set the names for the given set of parameters.
private void SetNames(CustomizationSet set, CharaMakeParams row)
{
var nameArray = Enum.GetValues<CustomizeIndex>().Select(c =>
{
// Find the first menu that corresponds to the Id.
var byteId = c.ToByteAndMask().ByteIdx;
var menu = row.Menus
.Cast<CharaMakeParams.Menu?>()
.FirstOrDefault(m => m!.Value.Customize == byteId);
if (menu == null)
{
// If none exists and the id corresponds to highlights, set the Highlights name.
if (c == CustomizeIndex.Highlights)
return Lobby.GetRow(237)?.Text.ToDalamudString().ToString() ?? "Highlights";
// Otherwise there is an error and we use the default name.
return c.ToDefaultName();
}
// Otherwise all is normal, get the menu name or if it does not work the default name.
var textRow = Lobby.GetRow(menu.Value.Id);
return textRow?.Text.ToDalamudString().ToString() ?? c.ToDefaultName();
}).ToArray();
// Add names for both eye colors.
nameArray[(int)CustomizeIndex.EyeColorLeft] = CustomizeIndex.EyeColorLeft.ToDefaultName();
nameArray[(int)CustomizeIndex.EyeColorRight] = CustomizeIndex.EyeColorRight.ToDefaultName();
nameArray[(int)CustomizeIndex.FacialFeature1] = CustomizeIndex.FacialFeature1.ToDefaultName();
nameArray[(int)CustomizeIndex.FacialFeature2] = CustomizeIndex.FacialFeature2.ToDefaultName();
nameArray[(int)CustomizeIndex.FacialFeature3] = CustomizeIndex.FacialFeature3.ToDefaultName();
nameArray[(int)CustomizeIndex.FacialFeature4] = CustomizeIndex.FacialFeature4.ToDefaultName();
nameArray[(int)CustomizeIndex.FacialFeature5] = CustomizeIndex.FacialFeature5.ToDefaultName();
nameArray[(int)CustomizeIndex.FacialFeature6] = CustomizeIndex.FacialFeature6.ToDefaultName();
nameArray[(int)CustomizeIndex.FacialFeature7] = CustomizeIndex.FacialFeature7.ToDefaultName();
nameArray[(int)CustomizeIndex.LegacyTattoo] = CustomizeIndex.LegacyTattoo.ToDefaultName();
nameArray[(int)CustomizeIndex.SmallIris] = CustomizeIndex.SmallIris.ToDefaultName();
nameArray[(int)CustomizeIndex.Lipstick] = CustomizeIndex.Lipstick.ToDefaultName();
nameArray[(int)CustomizeIndex.FacePaintReversed] = CustomizeIndex.FacePaintReversed.ToDefaultName();
set.OptionName = nameArray;
}
// Obtain available skin and hair colors for the given subrace and gender.
private (CustomizeData[], CustomizeData[]) GetColors(SubRace race, Gender gender)
{
if (race is > SubRace.Veena or SubRace.Unknown)
throw new ArgumentOutOfRangeException(nameof(race), race, null);
var gv = gender == Gender.Male ? 0 : 1;
var idx = ((int)race * 2 + gv) * 5 + 3;
return (CreateColorPicker(CustomizeIndex.SkinColor, idx << 8, 192),
CreateColorPicker(CustomizeIndex.HairColor, (idx + 1) << 8, 192));
}
// Obtain available hairstyles via reflection from the Hair sheet for the given subrace and gender.
private CustomizeData[] GetHairStyles(SubRace race, Gender gender)
{
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
// Unknown30 is the number of available hairstyles.
var hairList = new List<CustomizeData>(row.Unknown30);
// Hairstyles can be found starting at Unknown66.
for (var i = 0; i < row.Unknown30; ++i)
{
var name = $"Unknown{66 + i * 9}";
var customizeIdx = (uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
?? uint.MaxValue;
if (customizeIdx == uint.MaxValue)
continue;
// Hair Row from CustomizeSheet might not be set in case of unlockable hair.
var hairRow = _customizeSheet.GetRow(customizeIdx);
if (hairRow == null)
hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx));
else if (_options._icons.IconExists(hairRow.Icon))
hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon,
(ushort)hairRow.RowId));
}
return hairList.OrderBy(h => h.Value.Value).ToArray();
}
// Get Features.
private CustomizeData FromValueAndIndex(CustomizeIndex id, uint value, int index)
{
var row = _customizeSheet.GetRow(value);
return row == null
? new CustomizeData(id, (CustomizeValue)(index + 1), value)
: new CustomizeData(id, (CustomizeValue)row.FeatureID, row.Icon, (ushort)row.RowId);
}
// Get List sizes.
private static int GetListSize(CharaMakeParams row, CustomizeIndex index)
{
var gameId = index.ToByteAndMask().ByteIdx;
var menu = row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customize == gameId);
return menu?.Size ?? 0;
}
// Get face paints from the hair sheet via reflection.
private CustomizeData[] GetFacePaints(SubRace race, Gender gender)
{
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
var paintList = new List<CustomizeData>(row.Unknown37);
// Number of available face paints is at Unknown37.
for (var i = 0; i < row.Unknown37; ++i)
{
// Face paints start at Unknown73.
var name = $"Unknown{73 + i * 9}";
var customizeIdx =
(uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
?? uint.MaxValue;
if (customizeIdx == uint.MaxValue)
continue;
var paintRow = _customizeSheet.GetRow(customizeIdx);
// Facepaint Row from CustomizeSheet might not be set in case of unlockable facepaints.
if (paintRow != null)
paintList.Add(new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)paintRow.FeatureID, paintRow.Icon,
(ushort)paintRow.RowId));
else
paintList.Add(new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)i, customizeIdx));
}
return paintList.OrderBy(p => p.Value.Value).ToArray();
}
// Specific icons for tails or ears.
private CustomizeData[] GetTailEarShapes(CharaMakeParams row)
=> row.Menus.Cast<CharaMakeParams.Menu?>()
.FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.TailShape.ToByteAndMask().ByteIdx)?.Values
.Select((v, i) => FromValueAndIndex(CustomizeIndex.TailShape, v, i)).ToArray()
?? Array.Empty<CustomizeData>();
// Specific icons for faces.
private CustomizeData[] GetFaces(CharaMakeParams row)
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.Face.ToByteAndMask().ByteIdx)
?.Values
.Select((v, i) => FromValueAndIndex(CustomizeIndex.Face, v, i)).ToArray()
?? Array.Empty<CustomizeData>();
// Specific icons for Hrothgar patterns.
private CustomizeData[] HrothgarFurPattern(CharaMakeParams row)
=> row.Menus.Cast<CharaMakeParams.Menu?>()
.FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.LipColor.ToByteAndMask().ByteIdx)?.Values
.Select((v, i) => FromValueAndIndex(CustomizeIndex.LipColor, v, i)).ToArray()
?? Array.Empty<CustomizeData>();
}
}

View file

@ -1,123 +0,0 @@
using System;
using Penumbra.GameData.Enums;
namespace Glamourer.Customization;
public unsafe struct Customize
{
public Penumbra.GameData.Structs.CustomizeData Data;
public Customize(in Penumbra.GameData.Structs.CustomizeData 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.CustomizeData 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.CustomizeData 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,148 +0,0 @@
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using Dalamud.Memory;
namespace Glamourer.Customization;
[StructLayout(LayoutKind.Explicit, Size = Size)]
public unsafe struct DatCharacterFile
{
public const int Size = 4 + 4 + 4 + 4 + Penumbra.GameData.Structs.CustomizeData.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 Penumbra.GameData.Structs.CustomizeData _customize;
[FieldOffset(16 + Penumbra.GameData.Structs.CustomizeData.Size)]
private ushort _voice;
[FieldOffset(16 + Penumbra.GameData.Structs.CustomizeData.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,17 +0,0 @@
using System.Collections.Generic;
using Dalamud.Interface.Internal;
using Penumbra.GameData.Enums;
namespace Glamourer.Customization;
public interface ICustomizationManager
{
public IReadOnlyList<Race> Races { get; }
public IReadOnlyList<SubRace> Clans { get; }
public IReadOnlyList<Gender> Genders { get; }
public CustomizationSet GetList(SubRace race, Gender gender);
public IDalamudTextureWrap GetIcon(uint iconId);
public string GetName(CustomName name);
}

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 repo.json = repo.json
EndProjectSection EndProjectSection
EndProject 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}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glamourer", "Glamourer\Glamourer.csproj", "{01EB903D-871F-4285-A8CF-6486561D5B5B}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.Api", "Penumbra.Api\Penumbra.Api.csproj", "{29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}" 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 Release|Any CPU = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution 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.ActiveCfg = Debug|Any CPU
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.Build.0 = 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 {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 System.Linq;
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin; using Dalamud.Plugin;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Structs;
using Penumbra.Api.Helpers; using Penumbra.Api.Helpers;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;

View file

@ -0,0 +1,92 @@
using System.Linq;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin;
using Glamourer.Events;
using Glamourer.Services;
using Penumbra.Api.Helpers;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Api;
public partial class GlamourerIpc
{
public enum GlamourerErrorCode
{
Success,
ActorNotFound,
ActorNotHuman,
ItemInvalid,
}
public const string LabelSetItem = "Glamourer.SetItem";
public const string LabelSetItemByActorName = "Glamourer.SetItemByActorName";
private readonly FuncProvider<Character?, byte, ulong, byte, uint, int> _setItemProvider;
private readonly FuncProvider<string, byte, ulong, byte, uint, int> _setItemByActorNameProvider;
public static FuncSubscriber<Character?, byte, ulong, byte, uint, int> SetItemSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelSetItem);
public static FuncSubscriber<string, byte, ulong, byte, uint, int> SetItemByActorNameSubscriber(DalamudPluginInterface pi)
=> new(pi, LabelSetItemByActorName);
private GlamourerErrorCode SetItem(Character? character, EquipSlot slot, CustomItemId itemId, StainId stainId, uint key)
{
if (itemId.Id == 0)
itemId = ItemManager.NothingId(slot);
var item = _items.Resolve(slot, itemId);
if (!item.Valid)
return GlamourerErrorCode.ItemInvalid;
var identifier = _actors.FromObject(character, false, false, false);
if (!identifier.IsValid)
return GlamourerErrorCode.ActorNotFound;
if (!_stateManager.TryGetValue(identifier, out var state))
{
_objects.Update();
var data = _objects[identifier];
if (!data.Valid || !_stateManager.GetOrCreate(identifier, data.Objects[0], out state))
return GlamourerErrorCode.ActorNotFound;
}
if (!state.ModelData.IsHuman)
return GlamourerErrorCode.ActorNotHuman;
_stateManager.ChangeEquip(state, slot, item, stainId, StateChanged.Source.Ipc, key);
return GlamourerErrorCode.Success;
}
private GlamourerErrorCode SetItemByActorName(string name, EquipSlot slot, CustomItemId itemId, StainId stainId, uint key)
{
if (itemId.Id == 0)
itemId = ItemManager.NothingId(slot);
var item = _items.Resolve(slot, itemId);
if (!item.Valid)
return GlamourerErrorCode.ItemInvalid;
var found = false;
_objects.Update();
foreach (var identifier in FindActorsRevert(name).Distinct())
{
if (!_stateManager.TryGetValue(identifier, out var state))
{
var data = _objects[identifier];
if (!data.Valid || !_stateManager.GetOrCreate(identifier, data.Objects[0], out state))
continue;
}
if (!state.ModelData.IsHuman)
return GlamourerErrorCode.ActorNotHuman;
_stateManager.ChangeEquip(state, slot, item, stainId, StateChanged.Source.Ipc, key);
found = true;
}
return found ? GlamourerErrorCode.Success : GlamourerErrorCode.ActorNotFound;
}
}

View file

@ -11,31 +11,34 @@ using Glamourer.Services;
using Glamourer.State; using Glamourer.State;
using Penumbra.Api.Helpers; using Penumbra.Api.Helpers;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
using Penumbra.String; using Penumbra.String;
namespace Glamourer.Api; namespace Glamourer.Api;
public partial class GlamourerIpc : IDisposable public sealed partial class GlamourerIpc : IDisposable
{ {
public const int CurrentApiVersionMajor = 0; public const int CurrentApiVersionMajor = 0;
public const int CurrentApiVersionMinor = 4; public const int CurrentApiVersionMinor = 4;
private readonly StateManager _stateManager; private readonly StateManager _stateManager;
private readonly ObjectManager _objects; private readonly ObjectManager _objects;
private readonly ActorService _actors; private readonly ActorManager _actors;
private readonly DesignConverter _designConverter; private readonly DesignConverter _designConverter;
private readonly AutoDesignApplier _autoDesignApplier; private readonly AutoDesignApplier _autoDesignApplier;
private readonly DesignManager _designManager; private readonly DesignManager _designManager;
private readonly ItemManager _items;
public GlamourerIpc(DalamudPluginInterface pi, StateManager stateManager, ObjectManager objects, ActorService actors, public GlamourerIpc(DalamudPluginInterface pi, StateManager stateManager, ObjectManager objects, ActorManager actors,
DesignConverter designConverter, StateChanged stateChangedEvent, GPoseService gPose, AutoDesignApplier autoDesignApplier, DesignConverter designConverter, StateChanged stateChangedEvent, GPoseService gPose, AutoDesignApplier autoDesignApplier,
DesignManager designManager) DesignManager designManager, ItemManager items)
{ {
_stateManager = stateManager; _stateManager = stateManager;
_objects = objects; _objects = objects;
_actors = actors; _actors = actors;
_designConverter = designConverter; _designConverter = designConverter;
_autoDesignApplier = autoDesignApplier; _autoDesignApplier = autoDesignApplier;
_items = items;
_gPose = gPose; _gPose = gPose;
_stateChangedEvent = stateChangedEvent; _stateChangedEvent = stateChangedEvent;
_designManager = designManager; _designManager = designManager;
@ -82,6 +85,11 @@ public partial class GlamourerIpc : IDisposable
_stateChangedProvider = new EventProvider<StateChanged.Type, nint, Lazy<string>>(pi, LabelStateChanged); _stateChangedProvider = new EventProvider<StateChanged.Type, nint, Lazy<string>>(pi, LabelStateChanged);
_gPoseChangedProvider = new EventProvider<bool>(pi, LabelGPoseChanged); _gPoseChangedProvider = new EventProvider<bool>(pi, LabelGPoseChanged);
_setItemProvider = new FuncProvider<Character?, byte, ulong, byte, uint, int>(pi, LabelSetItem,
(idx, slot, item, stain, key) => (int)SetItem(idx, (EquipSlot)slot, item, stain, key));
_setItemByActorNameProvider = new FuncProvider<string, byte, ulong, byte, uint, int>(pi, LabelSetItemByActorName,
(name, slot, item, stain, key) => (int)SetItemByActorName(name, (EquipSlot)slot, item, stain, key));
_stateChangedEvent.Subscribe(OnStateChanged, StateChanged.Priority.GlamourerIpc); _stateChangedEvent.Subscribe(OnStateChanged, StateChanged.Priority.GlamourerIpc);
_gPose.Subscribe(OnGPoseChanged, GPoseService.Priority.GlamourerIpc); _gPose.Subscribe(OnGPoseChanged, GPoseService.Priority.GlamourerIpc);
@ -126,6 +134,9 @@ public partial class GlamourerIpc : IDisposable
_gPoseChangedProvider.Dispose(); _gPoseChangedProvider.Dispose();
_getDesignListProvider.Dispose(); _getDesignListProvider.Dispose();
_setItemProvider.Dispose();
_setItemByActorNameProvider.Dispose();
} }
private IEnumerable<ActorIdentifier> FindActors(string actorName) private IEnumerable<ActorIdentifier> FindActors(string actorName)
@ -154,7 +165,7 @@ public partial class GlamourerIpc : IDisposable
private IEnumerable<ActorIdentifier> FindActors(Character? character) private IEnumerable<ActorIdentifier> FindActors(Character? character)
{ {
var id = _actors.AwaitedService.FromObject(character, true, true, false); var id = _actors.FromObject(character, true, true, false);
if (!id.IsValid) if (!id.IsValid)
yield break; yield break;

View file

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

View file

@ -4,18 +4,16 @@ using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.UI.Misc; using FFXIVClientStructs.FFXIV.Client.UI.Misc;
using Glamourer.Customization;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.State; using Glamourer.State;
using Glamourer.Structs;
using Glamourer.Unlocks; using Glamourer.Unlocks;
using OtterGui.Classes; using OtterGui.Classes;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
@ -28,8 +26,8 @@ public class AutoDesignApplier : IDisposable
private readonly StateManager _state; private readonly StateManager _state;
private readonly JobService _jobs; private readonly JobService _jobs;
private readonly EquippedGearset _equippedGearset; private readonly EquippedGearset _equippedGearset;
private readonly ActorService _actors; private readonly ActorManager _actors;
private readonly CustomizationService _customizations; private readonly CustomizeService _customizations;
private readonly CustomizeUnlockManager _customizeUnlocks; private readonly CustomizeUnlockManager _customizeUnlocks;
private readonly ItemUnlockManager _itemUnlocks; private readonly ItemUnlockManager _itemUnlocks;
private readonly AutomationChanged _event; private readonly AutomationChanged _event;
@ -50,7 +48,7 @@ public class AutoDesignApplier : IDisposable
} }
public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs, public AutoDesignApplier(Configuration config, AutoDesignManager manager, StateManager state, JobService jobs,
CustomizationService customizations, ActorService actors, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks, CustomizeService customizations, ActorManager actors, ItemUnlockManager itemUnlocks, CustomizeUnlockManager customizeUnlocks,
AutomationChanged @event, ObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState, AutomationChanged @event, ObjectManager objects, WeaponLoading weapons, HumanModelList humans, IClientState clientState,
EquippedGearset equippedGearset) EquippedGearset equippedGearset)
{ {
@ -87,7 +85,7 @@ public class AutoDesignApplier : IDisposable
if (_jobChangeState == null || !_config.EnableAutoDesigns) if (_jobChangeState == null || !_config.EnableAutoDesigns)
return; return;
var id = actor.GetIdentifier(_actors.AwaitedService); var id = actor.GetIdentifier(_actors);
if (id == _jobChangeState.Identifier) if (id == _jobChangeState.Identifier)
{ {
var current = _jobChangeState.BaseData.Item(slot); var current = _jobChangeState.BaseData.Item(slot);
@ -161,7 +159,7 @@ public class AutoDesignApplier : IDisposable
{ {
foreach (var actor in data.Objects) foreach (var actor in data.Objects)
{ {
var specificId = actor.GetIdentifier(_actors.AwaitedService); var specificId = actor.GetIdentifier(_actors);
if (_state.GetOrCreate(specificId, actor, out var state)) if (_state.GetOrCreate(specificId, actor, out var state))
{ {
Reduce(actor, state, newSet, false, false); Reduce(actor, state, newSet, false, false);
@ -203,13 +201,13 @@ public class AutoDesignApplier : IDisposable
private void OnJobChange(Actor actor, Job oldJob, Job newJob) private void OnJobChange(Actor actor, Job oldJob, Job newJob)
{ {
if (!_config.EnableAutoDesigns || !actor.Identifier(_actors.AwaitedService, out var id)) if (!_config.EnableAutoDesigns || !actor.Identifier(_actors, out var id))
return; return;
if (!GetPlayerSet(id, out var set)) if (!GetPlayerSet(id, out var set))
{ {
if (_state.TryGetValue(id, out var s)) if (_state.TryGetValue(id, out var s))
s.LastJob = (byte)newJob.Id; s.LastJob = newJob.Id;
return; return;
} }
@ -312,13 +310,13 @@ public class AutoDesignApplier : IDisposable
if (_manager.EnabledSets.TryGetValue(identifier, out set)) if (_manager.EnabledSets.TryGetValue(identifier, out set))
return true; return true;
identifier = _actors.AwaitedService.CreatePlayer(identifier.PlayerName, ushort.MaxValue); identifier = _actors.CreatePlayer(identifier.PlayerName, ushort.MaxValue);
return _manager.EnabledSets.TryGetValue(identifier, out set); return _manager.EnabledSets.TryGetValue(identifier, out set);
case IdentifierType.Retainer: case IdentifierType.Retainer:
case IdentifierType.Npc: case IdentifierType.Npc:
return _manager.EnabledSets.TryGetValue(identifier, out set); return _manager.EnabledSets.TryGetValue(identifier, out set);
case IdentifierType.Owned: case IdentifierType.Owned:
identifier = _actors.AwaitedService.CreateNpc(identifier.Kind, identifier.DataId); identifier = _actors.CreateNpc(identifier.Kind, identifier.DataId);
return _manager.EnabledSets.TryGetValue(identifier, out set); return _manager.EnabledSets.TryGetValue(identifier, out set);
default: default:
set = null; set = null;
@ -470,7 +468,7 @@ public class AutoDesignApplier : IDisposable
totalCustomizeFlags |= CustomizeFlag.Face; totalCustomizeFlags |= CustomizeFlag.Face;
} }
var set = _customizations.AwaitedService.GetList(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender); var set = _customizations.Manager.GetSet(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender);
var face = state.ModelData.Customize.Face; var face = state.ModelData.Customize.Face;
foreach (var index in Enum.GetValues<CustomizeIndex>()) foreach (var index in Enum.GetValues<CustomizeIndex>())
{ {
@ -479,7 +477,7 @@ public class AutoDesignApplier : IDisposable
continue; continue;
var value = design.Customize[index]; var value = design.Customize[index];
if (CustomizationService.IsCustomizationValid(set, face, index, value, out var data)) if (CustomizeService.IsCustomizationValid(set, face, index, value, out var data))
{ {
if (data.HasValue && _config.UnlockedItemMode && !_customizeUnlocks.IsUnlocked(data.Value, out _)) if (data.HasValue && _config.UnlockedItemMode && !_customizeUnlocks.IsUnlocked(data.Value, out _))
continue; continue;

View file

@ -10,13 +10,14 @@ using Glamourer.Designs;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.Structs;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using OtterGui; using OtterGui;
using OtterGui.Classes; using OtterGui.Classes;
using OtterGui.Filesystem; using OtterGui.Filesystem;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Automation; namespace Glamourer.Automation;
@ -28,17 +29,17 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
private readonly JobService _jobs; private readonly JobService _jobs;
private readonly DesignManager _designs; private readonly DesignManager _designs;
private readonly ActorService _actors; private readonly ActorManager _actors;
private readonly AutomationChanged _event; private readonly AutomationChanged _event;
private readonly DesignChanged _designEvent; private readonly DesignChanged _designEvent;
private readonly List<AutoDesignSet> _data = new(); private readonly List<AutoDesignSet> _data = [];
private readonly Dictionary<ActorIdentifier, AutoDesignSet> _enabled = new(); private readonly Dictionary<ActorIdentifier, AutoDesignSet> _enabled = [];
public IReadOnlyDictionary<ActorIdentifier, AutoDesignSet> EnabledSets public IReadOnlyDictionary<ActorIdentifier, AutoDesignSet> EnabledSets
=> _enabled; => _enabled;
public AutoDesignManager(JobService jobs, ActorService actors, SaveService saveService, DesignManager designs, AutomationChanged @event, public AutoDesignManager(JobService jobs, ActorManager actors, SaveService saveService, DesignManager designs, AutomationChanged @event,
FixedDesignMigrator migrator, DesignFileSystem fileSystem, DesignChanged designEvent) FixedDesignMigrator migrator, DesignFileSystem fileSystem, DesignChanged designEvent)
{ {
_jobs = jobs; _jobs = jobs;
@ -419,7 +420,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
continue; continue;
} }
var id = _actors.AwaitedService.FromJson(obj["Identifier"] as JObject); var id = _actors.FromJson(obj["Identifier"] as JObject);
if (!IdentifierValid(id, out var group)) if (!IdentifierValid(id, out var group))
{ {
Glamourer.Messager.NotificationMessage("Skipped loading Automation Set: Invalid Identifier.", NotificationType.Warning); Glamourer.Messager.NotificationMessage("Skipped loading Automation Set: Invalid Identifier.", NotificationType.Warning);
@ -514,7 +515,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
var jobs = conditions["JobGroup"]?.ToObject<int>() ?? -1; var jobs = conditions["JobGroup"]?.ToObject<int>() ?? -1;
if (jobs >= 0) if (jobs >= 0)
{ {
if (!_jobs.JobGroups.TryGetValue((ushort)jobs, out var jobGroup)) if (!_jobs.JobGroups.TryGetValue((JobGroupId)jobs, out var jobGroup))
{ {
Glamourer.Messager.NotificationMessage( Glamourer.Messager.NotificationMessage(
$"Error parsing automatically applied design for set {setName}: The job condition {jobs} does not exist.", $"Error parsing automatically applied design for set {setName}: The job condition {jobs} does not exist.",
@ -562,9 +563,9 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
var name = manager.Data.ToName(identifier.Kind, identifier.DataId); var name = manager.Data.ToName(identifier.Kind, identifier.DataId);
var table = identifier.Kind switch var table = identifier.Kind switch
{ {
ObjectKind.BattleNpc => manager.Data.BNpcs, ObjectKind.BattleNpc => (IReadOnlyDictionary<NpcId, string>)manager.Data.BNpcs,
ObjectKind.EventNpc => manager.Data.ENpcs, ObjectKind.EventNpc => manager.Data.ENpcs,
_ => new Dictionary<uint, string>(), _ => new Dictionary<NpcId, string>(),
}; };
return table.Where(kvp => kvp.Value == name) return table.Where(kvp => kvp.Value == name)
.Select(kvp => manager.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, identifier.HomeWorld.Id, .Select(kvp => manager.CreateIndividualUnchecked(identifier.Type, identifier.PlayerName, identifier.HomeWorld.Id,
@ -580,12 +581,12 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
}, },
IdentifierType.Retainer => new[] IdentifierType.Retainer => new[]
{ {
_actors.AwaitedService.CreateRetainer(identifier.PlayerName, _actors.CreateRetainer(identifier.PlayerName,
identifier.Retainer == ActorIdentifier.RetainerType.Mannequin identifier.Retainer == ActorIdentifier.RetainerType.Mannequin
? ActorIdentifier.RetainerType.Mannequin ? ActorIdentifier.RetainerType.Mannequin
: ActorIdentifier.RetainerType.Bell).CreatePermanent(), : ActorIdentifier.RetainerType.Bell).CreatePermanent(),
}, },
IdentifierType.Npc => CreateNpcs(_actors.AwaitedService, identifier), IdentifierType.Npc => CreateNpcs(_actors, identifier),
_ => Array.Empty<ActorIdentifier>(), _ => Array.Empty<ActorIdentifier>(),
}; };
} }

View file

@ -3,59 +3,51 @@ using System.Linq;
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.Internal.Notifications;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.Services;
using Glamourer.Structs;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using OtterGui.Classes; using OtterGui.Classes;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
using Penumbra.GameData.Structs;
using Penumbra.String; using Penumbra.String;
namespace Glamourer.Automation; 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;
private List<(string Name, List<(string, JobGroup, bool)> Data)>? _migratedData;
public FixedDesignMigrator(JobService jobs) public void ConsumeMigratedData(ActorManager actors, DesignFileSystem designFileSystem, AutoDesignManager autoManager)
=> _jobs = jobs;
public void ConsumeMigratedData(ActorService actors, DesignFileSystem designFileSystem, AutoDesignManager autoManager)
{ {
if (_migratedData == null) if (_migratedData == null)
return; 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)) if (autoManager.Any(d => name == d.Name))
continue; continue;
var id = ActorIdentifier.Invalid; var id = ActorIdentifier.Invalid;
if (ByteString.FromString(data.Name, out var byteString, false)) if (ByteString.FromString(name, out var byteString))
{ {
id = actors.AwaitedService.CreatePlayer(byteString, ushort.MaxValue); id = actors.CreatePlayer(byteString, ushort.MaxValue);
if (!id.IsValid) if (!id.IsValid)
id = actors.AwaitedService.CreateRetainer(byteString, ActorIdentifier.RetainerType.Both); id = actors.CreateRetainer(byteString, ActorIdentifier.RetainerType.Both);
} }
if (!id.IsValid) if (!id.IsValid)
{ {
byteString = ByteString.FromSpanUnsafe("Mig Ration"u8, true, false, true); byteString = ByteString.FromSpanUnsafe("Mig Ration"u8, true, false, true);
id = actors.AwaitedService.CreatePlayer(byteString, actors.AwaitedService.Data.Worlds.First().Key); id = actors.CreatePlayer(byteString, actors.Data.Worlds.First().Key);
if (!id.IsValid) if (!id.IsValid)
{ {
Glamourer.Messager.NotificationMessage($"Could not migrate fixed design {data.Name}.", NotificationType.Error); Glamourer.Messager.NotificationMessage($"Could not migrate fixed design {name}.", NotificationType.Error);
allEnabled = false;
continue; continue;
} }
} }
autoManager.AddDesignSet(name, id); autoManager.AddDesignSet(name, id);
autoManager.SetState(autoManager.Count - 1, allEnabled); autoManager.SetState(autoManager.Count - 1, true);
var set = autoManager[^1]; 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) 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; 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.", Glamourer.Messager.NotificationMessage("Could not semi-migrate fixed design: Invalid job group specified.",
NotificationType.Warning); NotificationType.Warning);

View file

@ -15,7 +15,7 @@ public sealed class Design : DesignBase, ISavable
{ {
#region Data #region Data
internal Design(CustomizationService customize, ItemManager items) internal Design(CustomizeService customize, ItemManager items)
: base(customize, items) : base(customize, items)
{ } { }
@ -98,7 +98,7 @@ public sealed class Design : DesignBase, ISavable
#region Deserialization #region Deserialization
public static Design LoadDesign(CustomizationService customizations, ItemManager items, JObject json) public static Design LoadDesign(CustomizeService customizations, ItemManager items, JObject json)
{ {
var version = json["FileVersion"]?.ToObject<int>() ?? 0; var version = json["FileVersion"]?.ToObject<int>() ?? 0;
return version switch return version switch
@ -108,7 +108,7 @@ public sealed class Design : DesignBase, ISavable
}; };
} }
private static Design LoadDesignV1(CustomizationService customizations, ItemManager items, JObject json) private static Design LoadDesignV1(CustomizeService customizations, ItemManager items, JObject json)
{ {
static string[] ParseTags(JObject json) static string[] ParseTags(JObject json)
{ {

View file

@ -1,14 +1,13 @@
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.Internal.Notifications;
using Glamourer.Customization; using Glamourer.GameData;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.Structs;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using OtterGui.Classes; using OtterGui.Classes;
using Penumbra.GameData.Data;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using System; using System;
using System.Linq; using System.Linq;
using Penumbra.GameData.DataContainers;
namespace Glamourer.Designs; namespace Glamourer.Designs;
@ -26,35 +25,35 @@ public class DesignBase
public ref DesignData GetDesignDataRef() public ref DesignData GetDesignDataRef()
=> ref _designData; => ref _designData;
internal DesignBase(CustomizationService customize, ItemManager items) internal DesignBase(CustomizeService customize, ItemManager items)
{ {
_designData.SetDefaultEquipment(items); _designData.SetDefaultEquipment(items);
CustomizationSet = SetCustomizationSet(customize); CustomizeSet = SetCustomizationSet(customize);
} }
internal DesignBase(CustomizationService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags) internal DesignBase(CustomizeService customize, in DesignData designData, EquipFlag equipFlags, CustomizeFlag customizeFlags)
{ {
_designData = designData; _designData = designData;
ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant; ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant;
ApplyEquip = equipFlags & EquipFlagExtensions.All; ApplyEquip = equipFlags & EquipFlagExtensions.All;
_designFlags = 0; _designFlags = 0;
CustomizationSet = SetCustomizationSet(customize); CustomizeSet = SetCustomizationSet(customize);
} }
internal DesignBase(DesignBase clone) internal DesignBase(DesignBase clone)
{ {
_designData = clone._designData; _designData = clone._designData;
CustomizationSet = clone.CustomizationSet; CustomizeSet = clone.CustomizeSet;
ApplyCustomize = clone.ApplyCustomizeRaw; ApplyCustomize = clone.ApplyCustomizeRaw;
ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All; ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All;
_designFlags = clone._designFlags & (DesignFlags)0x0F; _designFlags = clone._designFlags & (DesignFlags)0x0F;
} }
/// <summary> Ensure that the customization set is updated when the design data changes. </summary> /// <summary> Ensure that the customization set is updated when the design data changes. </summary>
internal void SetDesignData(CustomizationService customize, in DesignData other) internal void SetDesignData(CustomizeService customize, in DesignData other)
{ {
_designData = other; _designData = other;
CustomizationSet = SetCustomizationSet(customize); CustomizeSet = SetCustomizationSet(customize);
} }
#region Application Data #region Application Data
@ -69,15 +68,18 @@ public class DesignBase
WriteProtected = 0x10, WriteProtected = 0x10,
} }
private CustomizeFlag _applyCustomize = CustomizeFlagExtensions.AllRelevant; private CustomizeFlag _applyCustomize = CustomizeFlagExtensions.AllRelevant;
public CustomizationSet CustomizationSet { get; private set; } public CustomizeSet CustomizeSet { get; private set; }
internal CustomizeFlag ApplyCustomize internal CustomizeFlag ApplyCustomize
{ {
get => _applyCustomize.FixApplication(CustomizationSet); get => _applyCustomize.FixApplication(CustomizeSet);
set => _applyCustomize = value & CustomizeFlagExtensions.AllRelevant; set => _applyCustomize = (value & CustomizeFlagExtensions.AllRelevant) | CustomizeFlag.BodyType;
} }
internal CustomizeFlag ApplyCustomizeExcludingBodyType
=> _applyCustomize.FixApplication(CustomizeSet) & ~CustomizeFlag.BodyType;
internal CustomizeFlag ApplyCustomizeRaw internal CustomizeFlag ApplyCustomizeRaw
=> _applyCustomize; => _applyCustomize;
@ -85,13 +87,13 @@ public class DesignBase
internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant; internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant;
private DesignFlags _designFlags = DesignFlags.ApplyHatVisible | DesignFlags.ApplyVisorState | DesignFlags.ApplyWeaponVisible; private DesignFlags _designFlags = DesignFlags.ApplyHatVisible | DesignFlags.ApplyVisorState | DesignFlags.ApplyWeaponVisible;
public bool SetCustomize(CustomizationService customizationService, Customize customize) public bool SetCustomize(CustomizeService customizeService, CustomizeArray customize)
{ {
if (customize.Equals(_designData.Customize)) if (customize.Equals(_designData.Customize))
return false; return false;
_designData.Customize.Load(customize); _designData.Customize = customize;
CustomizationSet = customizationService.AwaitedService.GetList(customize.Clan, customize.Gender); CustomizeSet = customizeService.Manager.GetSet(customize.Clan, customize.Gender);
return true; return true;
} }
@ -241,10 +243,10 @@ public class DesignBase
} }
} }
private CustomizationSet SetCustomizationSet(CustomizationService customize) private CustomizeSet SetCustomizationSet(CustomizeService customize)
=> !_designData.IsHuman => !_designData.IsHuman
? customize.AwaitedService.GetList(SubRace.Midlander, Gender.Male) ? customize.Manager.GetSet(SubRace.Midlander, Gender.Male)
: customize.AwaitedService.GetList(_designData.Customize.Clan, _designData.Customize.Gender); : customize.Manager.GetSet(_designData.Customize.Clan, _designData.Customize.Gender);
#endregion #endregion
@ -331,7 +333,7 @@ public class DesignBase
#region Deserialization #region Deserialization
public static DesignBase LoadDesignBase(CustomizationService customizations, ItemManager items, JObject json) public static DesignBase LoadDesignBase(CustomizeService customizations, ItemManager items, JObject json)
{ {
var version = json["FileVersion"]?.ToObject<int>() ?? 0; var version = json["FileVersion"]?.ToObject<int>() ?? 0;
return version switch return version switch
@ -341,7 +343,7 @@ public class DesignBase
}; };
} }
private static DesignBase LoadDesignV1Base(CustomizationService customizations, ItemManager items, JObject json) private static DesignBase LoadDesignV1Base(CustomizeService customizations, ItemManager items, JObject json)
{ {
var ret = new DesignBase(customizations, items); var ret = new DesignBase(customizations, items);
LoadCustomize(customizations, json["Customize"], ret, "Temporary Design", false, true); LoadCustomize(customizations, json["Customize"], ret, "Temporary Design", false, true);
@ -436,14 +438,14 @@ public class DesignBase
design._designData.SetVisor(metaValue.ForcedValue); design._designData.SetVisor(metaValue.ForcedValue);
} }
protected static void LoadCustomize(CustomizationService customizations, JToken? json, DesignBase design, string name, bool forbidNonHuman, protected static void LoadCustomize(CustomizeService customizations, JToken? json, DesignBase design, string name, bool forbidNonHuman,
bool allowUnknown) bool allowUnknown)
{ {
if (json == null) if (json == null)
{ {
design._designData.ModelId = 0; design._designData.ModelId = 0;
design._designData.IsHuman = true; 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.", Glamourer.Messager.NotificationMessage("The loaded design does not contain any customization data, reset to default.",
NotificationType.Warning); NotificationType.Warning);
return; return;
@ -474,7 +476,7 @@ public class DesignBase
{ {
var arrayText = json["Array"]?.ToObject<string>() ?? string.Empty; var arrayText = json["Array"]?.ToObject<string>() ?? string.Empty;
design._designData.Customize.LoadBase64(arrayText); design._designData.Customize.LoadBase64(arrayText);
design.CustomizationSet = design.SetCustomizationSet(customizations); design.CustomizeSet = design.SetCustomizationSet(customizations);
return; return;
} }
@ -486,18 +488,18 @@ public class DesignBase
design._designData.Customize.Race = race; design._designData.Customize.Race = race;
design._designData.Customize.Clan = clan; design._designData.Customize.Clan = clan;
design._designData.Customize.Gender = gender; design._designData.Customize.Gender = gender;
design.CustomizationSet = design.SetCustomizationSet(customizations); design.CustomizeSet = design.SetCustomizationSet(customizations);
design.SetApplyCustomize(CustomizeIndex.Race, json[CustomizeIndex.Race.ToString()]?["Apply"]?.ToObject<bool>() ?? false); design.SetApplyCustomize(CustomizeIndex.Race, json[CustomizeIndex.Race.ToString()]?["Apply"]?.ToObject<bool>() ?? false);
design.SetApplyCustomize(CustomizeIndex.Clan, json[CustomizeIndex.Clan.ToString()]?["Apply"]?.ToObject<bool>() ?? false); design.SetApplyCustomize(CustomizeIndex.Clan, json[CustomizeIndex.Clan.ToString()]?["Apply"]?.ToObject<bool>() ?? false);
design.SetApplyCustomize(CustomizeIndex.Gender, json[CustomizeIndex.Gender.ToString()]?["Apply"]?.ToObject<bool>() ?? false); design.SetApplyCustomize(CustomizeIndex.Gender, json[CustomizeIndex.Gender.ToString()]?["Apply"]?.ToObject<bool>() ?? false);
var set = design.CustomizationSet; var set = design.CustomizeSet;
foreach (var idx in CustomizationExtensions.AllBasic) foreach (var idx in CustomizationExtensions.AllBasic)
{ {
var tok = json[idx.ToString()]; var tok = json[idx.ToString()];
var data = (CustomizeValue)(tok?["Value"]?.ToObject<byte>() ?? 0); var data = (CustomizeValue)(tok?["Value"]?.ToObject<byte>() ?? 0);
if (set.IsAvailable(idx)) if (set.IsAvailable(idx) && design._designData.Customize.BodyType == 1)
PrintWarning(CustomizationService.ValidateCustomizeValue(set, design._designData.Customize.Face, idx, data, out data, PrintWarning(CustomizeService.ValidateCustomizeValue(set, design._designData.Customize.Face, idx, data, out data,
allowUnknown)); allowUnknown));
var apply = tok?["Apply"]?.ToObject<bool>() ?? false; var apply = tok?["Apply"]?.ToObject<bool>() ?? false;
design._designData.Customize[idx] = data; design._designData.Customize[idx] = data;
@ -505,7 +507,7 @@ public class DesignBase
} }
} }
public void MigrateBase64(CustomizationService customize, ItemManager items, HumanModelList humans, string base64) public void MigrateBase64(CustomizeService customize, ItemManager items, HumanModelList humans, string base64)
{ {
try try
{ {
@ -519,7 +521,7 @@ public class DesignBase
SetApplyVisorToggle(applyVisor); SetApplyVisorToggle(applyVisor);
SetApplyWeaponVisible(applyWeapon); SetApplyWeaponVisible(applyWeapon);
SetApplyWetness(true); SetApplyWetness(true);
CustomizationSet = SetCustomizationSet(customize); CustomizeSet = SetCustomizationSet(customize);
} }
catch (Exception ex) catch (Exception ex)
{ {

View file

@ -1,9 +1,7 @@
using System; using System;
using Glamourer.Customization;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.Structs;
using OtterGui; using OtterGui;
using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
@ -62,7 +60,7 @@ public class DesignBase64Migration
data.SetHatVisible((bytes[90] & 0x01) == 0); data.SetHatVisible((bytes[90] & 0x01) == 0);
data.SetVisor((bytes[90] & 0x10) != 0); data.SetVisor((bytes[90] & 0x10) != 0);
data.SetWeaponVisible((bytes[90] & 0x02) == 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; break;
} }
case 5: case 5:
@ -73,7 +71,7 @@ public class DesignBase64Migration
data.SetHatVisible((bytes[90] & 0x01) == 0); data.SetHatVisible((bytes[90] & 0x01) == 0);
data.SetVisor((bytes[90] & 0x10) != 0); data.SetVisor((bytes[90] & 0x10) != 0);
data.SetWeaponVisible((bytes[90] & 0x02) == 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; break;
default: throw new Exception($"Can not parse Base64 string into design for migration:\n\tInvalid Version {bytes[0]}."); 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)) 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; return data;
} }
data.Customize.Load(*(Customize*)(ptr + 4)); data.Customize = *(CustomizeArray*)(ptr + 4);
foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex()) foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex())
{ {
var mdl = eq[idx]; var mdl = eq[idx];
@ -121,9 +119,9 @@ public class DesignBase64Migration
data.SetStain(slot, mdl.Stain); data.SetStain(slot, mdl.Stain);
} }
var main = cur[0].Set.Id == 0 var main = cur[0].Skeleton.Id == 0
? items.DefaultSword ? items.DefaultSword
: items.Identify(EquipSlot.MainHand, cur[0].Set, cur[0].Type, cur[0].Variant); : items.Identify(EquipSlot.MainHand, cur[0].Skeleton, cur[0].Weapon, cur[0].Variant);
if (!main.Valid) if (!main.Valid)
{ {
Glamourer.Log.Warning("Base64 string invalid, weapon could not be identified."); Glamourer.Log.Warning("Base64 string invalid, weapon could not be identified.");
@ -135,10 +133,10 @@ public class DesignBase64Migration
EquipItem off; EquipItem off;
// Fist weapon hack // Fist weapon hack
if (main.ModelId.Id is > 1600 and < 1651 && cur[1].Variant == 0) if (main.PrimaryId.Id is > 1600 and < 1651 && cur[1].Variant == 0)
{ {
off = items.Identify(EquipSlot.OffHand, (SetId)(main.ModelId.Id + 50), main.WeaponType, main.Variant, main.Type); off = items.Identify(EquipSlot.OffHand, (PrimaryId)(main.PrimaryId.Id + 50), main.SecondaryId, main.Variant, main.Type);
var gauntlet = items.Identify(EquipSlot.Hands, cur[1].Set, (Variant)cur[1].Type.Id); var gauntlet = items.Identify(EquipSlot.Hands, cur[1].Skeleton, (Variant)cur[1].Weapon.Id);
if (gauntlet.Valid) if (gauntlet.Valid)
{ {
data.SetItem(EquipSlot.Hands, gauntlet); data.SetItem(EquipSlot.Hands, gauntlet);
@ -147,9 +145,9 @@ public class DesignBase64Migration
} }
else else
{ {
off = cur[0].Set.Id == 0 off = cur[0].Skeleton.Id == 0
? ItemManager.NothingItem(FullEquipType.Shield) ? ItemManager.NothingItem(FullEquipType.Shield)
: items.Identify(EquipSlot.OffHand, cur[1].Set, cur[1].Type, cur[1].Variant, main.Type); : items.Identify(EquipSlot.OffHand, cur[1].Skeleton, cur[1].Weapon, cur[1].Variant, main.Type);
} }
if (main.Type.ValidOffhand() != FullEquipType.Unknown && !off.Valid) if (main.Type.ValidOffhand() != FullEquipType.Unknown && !off.Valid)
@ -187,7 +185,7 @@ public class DesignBase64Migration
| (equipFlags.HasFlag(EquipFlag.Wrist) ? 0x02 : 0) | (equipFlags.HasFlag(EquipFlag.Wrist) ? 0x02 : 0)
| (equipFlags.HasFlag(EquipFlag.RFinger) ? 0x04 : 0) | (equipFlags.HasFlag(EquipFlag.RFinger) ? 0x04 : 0)
| (equipFlags.HasFlag(EquipFlag.LFinger) ? 0x08 : 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))[0] = save.Item(EquipSlot.MainHand).Weapon(save.Stain(EquipSlot.MainHand));
((CharacterWeapon*)(data + 30))[1] = save.Item(EquipSlot.OffHand).Weapon(save.Stain(EquipSlot.OffHand)); ((CharacterWeapon*)(data + 30))[1] = save.Item(EquipSlot.OffHand).Weapon(save.Stain(EquipSlot.OffHand));
foreach (var slot in EquipSlotExtensions.EqdpSlots) foreach (var slot in EquipSlotExtensions.EqdpSlots)

View file

@ -26,9 +26,9 @@ public class DesignColorUi
public DesignColorUi(DesignColors colors, DesignManager designs, Configuration config) public DesignColorUi(DesignColors colors, DesignManager designs, Configuration config)
{ {
_colors = colors; _colors = colors;
_designs = designs; _designs = designs;
_config = config; _config = config;
} }
public void Draw() public void Draw()
@ -78,7 +78,7 @@ public class DesignColorUi
{ {
using var id = ImRaii.PushId(idx); using var id = ImRaii.PushId(idx);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, tt, disabled, true)) if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, tt, disabled, true))
{ {
changeString = name; changeString = name;
@ -119,7 +119,7 @@ public class DesignColorUi
changeString = _newName; changeString = _newName;
changeValue = 0xFFFFFFFF; changeValue = 0xFFFFFFFF;
} }
if (changeString.Length > 0) if (changeString.Length > 0)
{ {
@ -139,6 +139,7 @@ public class DesignColorUi
newColor = color; newColor = color;
return false; return false;
} }
ImGuiUtil.HoverTooltip(tooltip); ImGuiUtil.HoverTooltip(tooltip);
newColor = ImGui.ColorConvertFloat4ToU32(vec); newColor = ImGui.ColorConvertFloat4ToU32(vec);
@ -148,12 +149,12 @@ public class DesignColorUi
public class DesignColors : ISavable, IReadOnlyDictionary<string, uint> public class DesignColors : ISavable, IReadOnlyDictionary<string, uint>
{ {
public const string AutomaticName = "Automatic"; public const string AutomaticName = "Automatic";
public const string MissingColorName = "Missing Color"; public const string MissingColorName = "Missing Color";
public const uint MissingColorDefault = 0xFF0000D0; public const uint MissingColorDefault = 0xFF0000D0;
private readonly SaveService _saveService; private readonly SaveService _saveService;
private readonly Dictionary<string, uint> _colors = new(); private readonly Dictionary<string, uint> _colors = [];
public uint MissingColor { get; private set; } = MissingColorDefault; public uint MissingColor { get; private set; } = MissingColorDefault;
public event Action? ColorChanged; public event Action? ColorChanged;
@ -281,7 +282,7 @@ public class DesignColors : ISavable, IReadOnlyDictionary<string, uint>
public static uint AutoColor(DesignBase design) public static uint AutoColor(DesignBase design)
{ {
var customize = design.ApplyCustomize == 0; var customize = design.ApplyCustomizeExcludingBodyType == 0;
var equip = design.ApplyEquip == 0; var equip = design.ApplyEquip == 0;
return (customize, equip) switch return (customize, equip) switch
{ {

View file

@ -2,20 +2,18 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Text; using System.Text;
using Glamourer.Customization;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.State; using Glamourer.State;
using Glamourer.Structs;
using Glamourer.Utility; using Glamourer.Utility;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
namespace Glamourer.Designs; namespace Glamourer.Designs;
public class DesignConverter(ItemManager _items, DesignManager _designs, CustomizationService _customize, HumanModelList _humans) public class DesignConverter(ItemManager _items, DesignManager _designs, CustomizeService _customize, HumanModelList _humans)
{ {
public const byte Version = 6; public const byte Version = 6;
@ -41,12 +39,18 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
=> ShareBase64(state, EquipFlagExtensions.All, CustomizeFlagExtensions.All, CrestExtensions.All); => ShareBase64(state, EquipFlagExtensions.All, CustomizeFlagExtensions.All, CrestExtensions.All);
public string ShareBase64(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) public string ShareBase64(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags)
=> ShareBase64(state.ModelData, equipFlags, customizeFlags, crestFlags);
public string ShareBase64(in DesignData data, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags)
{ {
var design = Convert(state, equipFlags, customizeFlags, crestFlags); var design = Convert(data, equipFlags, customizeFlags, crestFlags);
return ShareBase64(ShareJObject(design)); return ShareBase64(ShareJObject(design));
} }
public DesignBase Convert(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags) public DesignBase Convert(ActorState state, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags)
=> Convert(state.ModelData, equipFlags, customizeFlags, crestFlags);
public DesignBase Convert(in DesignData data, EquipFlag equipFlags, CustomizeFlag customizeFlags, CrestFlag crestFlags)
{ {
var design = _designs.CreateTemporary(); var design = _designs.CreateTemporary();
design.ApplyEquip = equipFlags & EquipFlagExtensions.All; design.ApplyEquip = equipFlags & EquipFlagExtensions.All;
@ -56,7 +60,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
design.SetApplyVisorToggle(design.DoApplyEquip(EquipSlot.Head)); design.SetApplyVisorToggle(design.DoApplyEquip(EquipSlot.Head));
design.SetApplyWeaponVisible(design.DoApplyEquip(EquipSlot.MainHand) || design.DoApplyEquip(EquipSlot.OffHand)); design.SetApplyWeaponVisible(design.DoApplyEquip(EquipSlot.MainHand) || design.DoApplyEquip(EquipSlot.OffHand));
design.SetApplyWetness(true); design.SetApplyWetness(true);
design.SetDesignData(_customize, state.ModelData); design.SetDesignData(_customize, data);
return design; return design;
} }
@ -146,7 +150,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
} }
public IEnumerable<(EquipSlot Slot, EquipItem Item, StainId Stain)> FromDrawData(IReadOnlyList<CharacterArmor> armors, public IEnumerable<(EquipSlot Slot, EquipItem Item, StainId Stain)> FromDrawData(IReadOnlyList<CharacterArmor> armors,
CharacterWeapon mainhand, CharacterWeapon offhand) CharacterWeapon mainhand, CharacterWeapon offhand, bool skipWarnings)
{ {
if (armors.Count != 10) if (armors.Count != 10)
throw new ArgumentException("Invalid length of armor array."); throw new ArgumentException("Invalid length of armor array.");
@ -158,15 +162,16 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
var item = _items.Identify(slot, armor.Set, armor.Variant); var item = _items.Identify(slot, armor.Set, armor.Variant);
if (!item.Valid) if (!item.Valid)
{ {
Glamourer.Log.Warning($"Appearance data {armor} for slot {slot} invalid, item could not be identified."); if (!skipWarnings)
Glamourer.Log.Warning($"Appearance data {armor} for slot {slot} invalid, item could not be identified.");
item = ItemManager.NothingItem(slot); item = ItemManager.NothingItem(slot);
} }
yield return (slot, item, armor.Stain); yield return (slot, item, armor.Stain);
} }
var mh = _items.Identify(EquipSlot.MainHand, mainhand.Set, mainhand.Type, mainhand.Variant, FullEquipType.Unknown); var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant);
if (!mh.Valid) if (!skipWarnings && !mh.Valid)
{ {
Glamourer.Log.Warning($"Appearance data {mainhand} for mainhand weapon invalid, item could not be identified."); Glamourer.Log.Warning($"Appearance data {mainhand} for mainhand weapon invalid, item could not be identified.");
mh = _items.DefaultSword; mh = _items.DefaultSword;
@ -174,8 +179,8 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
yield return (EquipSlot.MainHand, mh, mainhand.Stain); yield return (EquipSlot.MainHand, mh, mainhand.Stain);
var oh = _items.Identify(EquipSlot.OffHand, offhand.Set, offhand.Type, offhand.Variant, mh.Type); var oh = _items.Identify(EquipSlot.OffHand, offhand.Skeleton, offhand.Weapon, offhand.Variant, mh.Type);
if (!oh.Valid) if (!skipWarnings && !oh.Valid)
{ {
Glamourer.Log.Warning($"Appearance data {offhand} for offhand weapon invalid, item could not be identified."); Glamourer.Log.Warning($"Appearance data {offhand} for offhand weapon invalid, item could not be identified.");
oh = _items.GetDefaultOffhand(mh); oh = _items.GetDefaultOffhand(mh);

View file

@ -1,42 +1,39 @@
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using Glamourer.Customization;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.Structs;
using OtterGui.Classes; using OtterGui.Classes;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using Penumbra.String.Functions; using Penumbra.String.Functions;
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
namespace Glamourer.Designs; namespace Glamourer.Designs;
public unsafe struct DesignData public unsafe struct DesignData
{ {
private string _nameHead = string.Empty; private string _nameHead = string.Empty;
private string _nameBody = string.Empty; private string _nameBody = string.Empty;
private string _nameHands = string.Empty; private string _nameHands = string.Empty;
private string _nameLegs = string.Empty; private string _nameLegs = string.Empty;
private string _nameFeet = string.Empty; private string _nameFeet = string.Empty;
private string _nameEars = string.Empty; private string _nameEars = string.Empty;
private string _nameNeck = string.Empty; private string _nameNeck = string.Empty;
private string _nameWrists = string.Empty; private string _nameWrists = string.Empty;
private string _nameRFinger = string.Empty; private string _nameRFinger = string.Empty;
private string _nameLFinger = string.Empty; private string _nameLFinger = string.Empty;
private string _nameMainhand = string.Empty; private string _nameMainhand = string.Empty;
private string _nameOffhand = string.Empty; private string _nameOffhand = string.Empty;
private fixed uint _itemIds[12]; private fixed uint _itemIds[12];
private fixed ushort _iconIds[12]; private fixed ushort _iconIds[12];
private fixed byte _equipmentBytes[48]; private fixed byte _equipmentBytes[48];
public Customize Customize = Customize.Default; public CustomizeArray Customize = CustomizeArray.Default;
public uint ModelId; public uint ModelId;
public CrestFlag CrestVisibility; public CrestFlag CrestVisibility;
private WeaponType _secondaryMainhand; private SecondaryId _secondaryMainhand;
private WeaponType _secondaryOffhand; private SecondaryId _secondaryOffhand;
private FullEquipType _typeMainhand; private FullEquipType _typeMainhand;
private FullEquipType _typeOffhand; private FullEquipType _typeOffhand;
private byte _states; private byte _states;
public bool IsHuman = true; public bool IsHuman = true;
public DesignData() public DesignData()
{ } { }
@ -75,18 +72,18 @@ public unsafe struct DesignData
=> slot.ToIndex() switch => slot.ToIndex() switch
{ {
// @formatter:off // @formatter:off
0 => EquipItem.FromIds(_itemIds[ 0], _iconIds[ 0], (SetId)(_equipmentBytes[ 0] | (_equipmentBytes[ 1] << 8)), (WeaponType)0, _equipmentBytes[ 2], FullEquipType.Head, name: _nameHead ), 0 => EquipItem.FromIds((ItemId)_itemIds[ 0], (IconId)_iconIds[ 0], (PrimaryId)(_equipmentBytes[ 0] | (_equipmentBytes[ 1] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[ 2], FullEquipType.Head, name: _nameHead ),
1 => EquipItem.FromIds(_itemIds[ 1], _iconIds[ 1], (SetId)(_equipmentBytes[ 4] | (_equipmentBytes[ 5] << 8)), (WeaponType)0, _equipmentBytes[ 6], FullEquipType.Body, name: _nameBody ), 1 => EquipItem.FromIds((ItemId)_itemIds[ 1], (IconId)_iconIds[ 1], (PrimaryId)(_equipmentBytes[ 4] | (_equipmentBytes[ 5] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[ 6], FullEquipType.Body, name: _nameBody ),
2 => EquipItem.FromIds(_itemIds[ 2], _iconIds[ 2], (SetId)(_equipmentBytes[ 8] | (_equipmentBytes[ 9] << 8)), (WeaponType)0, _equipmentBytes[10], FullEquipType.Hands, name: _nameHands ), 2 => EquipItem.FromIds((ItemId)_itemIds[ 2], (IconId)_iconIds[ 2], (PrimaryId)(_equipmentBytes[ 8] | (_equipmentBytes[ 9] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[10], FullEquipType.Hands, name: _nameHands ),
3 => EquipItem.FromIds(_itemIds[ 3], _iconIds[ 3], (SetId)(_equipmentBytes[12] | (_equipmentBytes[13] << 8)), (WeaponType)0, _equipmentBytes[14], FullEquipType.Legs, name: _nameLegs ), 3 => EquipItem.FromIds((ItemId)_itemIds[ 3], (IconId)_iconIds[ 3], (PrimaryId)(_equipmentBytes[12] | (_equipmentBytes[13] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[14], FullEquipType.Legs, name: _nameLegs ),
4 => EquipItem.FromIds(_itemIds[ 4], _iconIds[ 4], (SetId)(_equipmentBytes[16] | (_equipmentBytes[17] << 8)), (WeaponType)0, _equipmentBytes[18], FullEquipType.Feet, name: _nameFeet ), 4 => EquipItem.FromIds((ItemId)_itemIds[ 4], (IconId)_iconIds[ 4], (PrimaryId)(_equipmentBytes[16] | (_equipmentBytes[17] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[18], FullEquipType.Feet, name: _nameFeet ),
5 => EquipItem.FromIds(_itemIds[ 5], _iconIds[ 5], (SetId)(_equipmentBytes[20] | (_equipmentBytes[21] << 8)), (WeaponType)0, _equipmentBytes[22], FullEquipType.Ears, name: _nameEars ), 5 => EquipItem.FromIds((ItemId)_itemIds[ 5], (IconId)_iconIds[ 5], (PrimaryId)(_equipmentBytes[20] | (_equipmentBytes[21] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[22], FullEquipType.Ears, name: _nameEars ),
6 => EquipItem.FromIds(_itemIds[ 6], _iconIds[ 6], (SetId)(_equipmentBytes[24] | (_equipmentBytes[25] << 8)), (WeaponType)0, _equipmentBytes[26], FullEquipType.Neck, name: _nameNeck ), 6 => EquipItem.FromIds((ItemId)_itemIds[ 6], (IconId)_iconIds[ 6], (PrimaryId)(_equipmentBytes[24] | (_equipmentBytes[25] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[26], FullEquipType.Neck, name: _nameNeck ),
7 => EquipItem.FromIds(_itemIds[ 7], _iconIds[ 7], (SetId)(_equipmentBytes[28] | (_equipmentBytes[29] << 8)), (WeaponType)0, _equipmentBytes[30], FullEquipType.Wrists, name: _nameWrists ), 7 => EquipItem.FromIds((ItemId)_itemIds[ 7], (IconId)_iconIds[ 7], (PrimaryId)(_equipmentBytes[28] | (_equipmentBytes[29] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[30], FullEquipType.Wrists, name: _nameWrists ),
8 => EquipItem.FromIds(_itemIds[ 8], _iconIds[ 8], (SetId)(_equipmentBytes[32] | (_equipmentBytes[33] << 8)), (WeaponType)0, _equipmentBytes[34], FullEquipType.Finger, name: _nameRFinger ), 8 => EquipItem.FromIds((ItemId)_itemIds[ 8], (IconId)_iconIds[ 8], (PrimaryId)(_equipmentBytes[32] | (_equipmentBytes[33] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[34], FullEquipType.Finger, name: _nameRFinger ),
9 => EquipItem.FromIds(_itemIds[ 9], _iconIds[ 9], (SetId)(_equipmentBytes[36] | (_equipmentBytes[37] << 8)), (WeaponType)0, _equipmentBytes[38], FullEquipType.Finger, name: _nameLFinger ), 9 => EquipItem.FromIds((ItemId)_itemIds[ 9], (IconId)_iconIds[ 9], (PrimaryId)(_equipmentBytes[36] | (_equipmentBytes[37] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[38], FullEquipType.Finger, name: _nameLFinger ),
10 => EquipItem.FromIds(_itemIds[10], _iconIds[10], (SetId)(_equipmentBytes[40] | (_equipmentBytes[41] << 8)), _secondaryMainhand, _equipmentBytes[42], _typeMainhand, name: _nameMainhand), 10 => EquipItem.FromIds((ItemId)_itemIds[10], (IconId)_iconIds[10], (PrimaryId)(_equipmentBytes[40] | (_equipmentBytes[41] << 8)), _secondaryMainhand, (Variant)_equipmentBytes[42], _typeMainhand, name: _nameMainhand),
11 => EquipItem.FromIds(_itemIds[11], _iconIds[11], (SetId)(_equipmentBytes[44] | (_equipmentBytes[45] << 8)), _secondaryOffhand, _equipmentBytes[46], _typeOffhand, name: _nameOffhand ), 11 => EquipItem.FromIds((ItemId)_itemIds[11], (IconId)_iconIds[11], (PrimaryId)(_equipmentBytes[44] | (_equipmentBytes[45] << 8)), _secondaryOffhand, (Variant)_equipmentBytes[46], _typeOffhand, name: _nameOffhand ),
_ => new EquipItem(), _ => new EquipItem(),
// @formatter:on // @formatter:on
}; };
@ -129,8 +126,8 @@ public unsafe struct DesignData
_itemIds[index] = item.ItemId.Id; _itemIds[index] = item.ItemId.Id;
_iconIds[index] = item.IconId.Id; _iconIds[index] = item.IconId.Id;
_equipmentBytes[4 * index + 0] = (byte)item.ModelId.Id; _equipmentBytes[4 * index + 0] = (byte)item.PrimaryId.Id;
_equipmentBytes[4 * index + 1] = (byte)(item.ModelId.Id >> 8); _equipmentBytes[4 * index + 1] = (byte)(item.PrimaryId.Id >> 8);
_equipmentBytes[4 * index + 2] = item.Variant.Id; _equipmentBytes[4 * index + 2] = item.Variant.Id;
switch (index) switch (index)
{ {
@ -148,12 +145,12 @@ public unsafe struct DesignData
// @formatter:on // @formatter:on
case 10: case 10:
_nameMainhand = item.Name; _nameMainhand = item.Name;
_secondaryMainhand = item.WeaponType; _secondaryMainhand = item.SecondaryId;
_typeMainhand = item.Type; _typeMainhand = item.Type;
return true; return true;
case 11: case 11:
_nameOffhand = item.Name; _nameOffhand = item.Name;
_secondaryOffhand = item.WeaponType; _secondaryOffhand = item.SecondaryId;
_typeOffhand = item.Type; _typeOffhand = item.Type;
return true; return true;
} }
@ -256,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; ModelId = modelId;
IsHuman = false; IsHuman = false;
Customize.Load(customize); Customize.Read(customize.Data);
fixed (byte* ptr = _equipmentBytes) fixed (byte* ptr = _equipmentBytes)
{ {
MemoryUtility.MemCpyUnchecked(ptr, (byte*)equipData, 40); MemoryUtility.MemCpyUnchecked(ptr, (byte*)equipData, 40);
@ -294,8 +291,8 @@ public unsafe struct DesignData
public readonly byte[] GetCustomizeBytes() public readonly byte[] GetCustomizeBytes()
{ {
var ret = new byte[CustomizeData.Size]; 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); MemoryUtility.MemCpyUnchecked(retPtr, inPtr, ret.Length);
} }

View file

@ -1,18 +1,20 @@
using System; using System;
using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Utility; using Dalamud.Utility;
using Glamourer.Customization;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.State; using Glamourer.State;
using Glamourer.Structs;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using OtterGui; using OtterGui;
using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
@ -20,18 +22,18 @@ namespace Glamourer.Designs;
public class DesignManager public class DesignManager
{ {
private readonly CustomizationService _customizations; private readonly CustomizeService _customizations;
private readonly ItemManager _items; private readonly ItemManager _items;
private readonly HumanModelList _humans; private readonly HumanModelList _humans;
private readonly SaveService _saveService; private readonly SaveService _saveService;
private readonly DesignChanged _event; private readonly DesignChanged _event;
private readonly List<Design> _designs = new(); private readonly List<Design> _designs = [];
private readonly Dictionary<Guid, DesignData> _undoStore = new(); private readonly Dictionary<Guid, DesignData> _undoStore = [];
public IReadOnlyList<Design> Designs public IReadOnlyList<Design> Designs
=> _designs; => _designs;
public DesignManager(SaveService saveService, ItemManager items, CustomizationService customizations, public DesignManager(SaveService saveService, ItemManager items, CustomizeService customizations,
DesignChanged @event, HumanModelList humans) DesignChanged @event, HumanModelList humans)
{ {
_saveService = saveService; _saveService = saveService;
@ -50,29 +52,44 @@ public class DesignManager
/// </summary> /// </summary>
public void LoadDesigns() public void LoadDesigns()
{ {
_humans.Awaiter.Wait();
_customizations.Awaiter.Wait();
_items.ItemData.Awaiter.Wait();
var stopwatch = Stopwatch.StartNew();
_designs.Clear(); _designs.Clear();
List<(Design, string)> invalidNames = new(); var skipped = 0;
var skipped = 0; ThreadLocal<List<(Design, string)>> designs = new(() => [], true);
foreach (var file in _saveService.FileNames.Designs()) Parallel.ForEach(_saveService.FileNames.Designs(), (f, _) =>
{ {
try try
{ {
var text = File.ReadAllText(file.FullName); var text = File.ReadAllText(f.FullName);
var data = JObject.Parse(text); var data = JObject.Parse(text);
var design = Design.LoadDesign(_customizations, _items, data); var design = Design.LoadDesign(_customizations, _items, data);
if (design.Identifier.ToString() != Path.GetFileNameWithoutExtension(file.Name)) designs.Value!.Add((design, f.FullName));
invalidNames.Add((design, file.FullName));
if (_designs.Any(f => f.Identifier == design.Identifier))
throw new Exception($"Identifier {design.Identifier} was not unique.");
design.Index = _designs.Count;
_designs.Add(design);
} }
catch (Exception ex) catch (Exception ex)
{ {
Glamourer.Log.Error($"Could not load design, skipped:\n{ex}"); Glamourer.Log.Error($"Could not load design, skipped:\n{ex}");
++skipped; Interlocked.Increment(ref skipped);
} }
});
List<(Design, string)> invalidNames = [];
foreach (var (design, path) in designs.Values.SelectMany(v => v))
{
if (design.Identifier.ToString() != Path.GetFileNameWithoutExtension(path))
invalidNames.Add((design, path));
if (_designs.Any(d => d.Identifier == design.Identifier))
{
Glamourer.Log.Error($"Could not load design, skipped: Identifier {design.Identifier} was not unique.");
++skipped;
continue;
}
design.Index = _designs.Count;
_designs.Add(design);
} }
var failed = MoveInvalidNames(invalidNames); var failed = MoveInvalidNames(invalidNames);
@ -81,7 +98,7 @@ public class DesignManager
$"Moved {invalidNames.Count - failed} designs to correct names.{(failed > 0 ? $" Failed to move {failed} designs to correct names." : string.Empty)}"); $"Moved {invalidNames.Count - failed} designs to correct names.{(failed > 0 ? $" Failed to move {failed} designs to correct names." : string.Empty)}");
Glamourer.Log.Information( Glamourer.Log.Information(
$"Loaded {_designs.Count} designs.{(skipped > 0 ? $" Skipped loading {skipped} designs due to errors." : string.Empty)}"); $"Loaded {_designs.Count} designs in {stopwatch.ElapsedMilliseconds} ms.{(skipped > 0 ? $" Skipped loading {skipped} designs due to errors." : string.Empty)}");
_event.Invoke(DesignChanged.Type.ReloadedAll, null!); _event.Invoke(DesignChanged.Type.ReloadedAll, null!);
} }
@ -193,10 +210,10 @@ public class DesignManager
public void ChangeColor(Design design, string newColor) public void ChangeColor(Design design, string newColor)
{ {
var oldColor = design.Color; var oldColor = design.Color;
if (oldColor == newColor) if (oldColor == newColor)
return; return;
design.Color = newColor; design.Color = newColor;
design.LastEdit = DateTimeOffset.UtcNow; design.LastEdit = DateTimeOffset.UtcNow;
_saveService.QueueSave(design); _saveService.QueueSave(design);
Glamourer.Log.Debug($"Changed color of design {design.Identifier}."); Glamourer.Log.Debug($"Changed color of design {design.Identifier}.");
@ -298,7 +315,7 @@ public class DesignManager
return; return;
case CustomizeIndex.Clan: 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) if (_customizations.ChangeClan(ref customize, (SubRace)value.Value) == 0)
return; return;
if (!design.SetCustomize(_customizations, customize)) if (!design.SetCustomize(_customizations, customize))
@ -308,7 +325,7 @@ public class DesignManager
} }
case CustomizeIndex.Gender: 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) if (_customizations.ChangeGender(ref customize, (Gender)(value.Value + 1)) == 0)
return; return;
if (!design.SetCustomize(_customizations, customize)) if (!design.SetCustomize(_customizations, customize))

View file

@ -2,9 +2,9 @@
using Lumina.Excel; using Lumina.Excel;
using Lumina.Excel.GeneratedSheets; 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")] [Sheet("CharaMakeParams")]
public class CharaMakeParams : ExcelRow public class CharaMakeParams : ExcelRow
{ {
@ -64,7 +64,7 @@ public class CharaMakeParams : ExcelRow
Race = new LazyRow<Race>(gameData, parser.ReadColumn<uint>(0), language); Race = new LazyRow<Race>(gameData, parser.ReadColumn<uint>(0), language);
Tribe = new LazyRow<Tribe>(gameData, parser.ReadColumn<uint>(1), language); Tribe = new LazyRow<Tribe>(gameData, parser.ReadColumn<uint>(1), language);
Gender = parser.ReadColumn<sbyte>(2); Gender = parser.ReadColumn<sbyte>(2);
var currentOffset = 0; int currentOffset;
for (var i = 0; i < NumMenus; ++i) for (var i = 0; i < NumMenus; ++i)
{ {
currentOffset = 3 + i; currentOffset = 3 + i;

View file

@ -0,0 +1,57 @@
using System;
using System.Collections;
using System.Collections.Generic;
using Dalamud.Plugin.Services;
using Penumbra.String.Functions;
namespace Glamourer.GameData;
/// <summary> Parse the Human.cmp file as a list of 4-byte integer values to obtain colors. </summary>
public class ColorParameters : IReadOnlyList<uint>
{
private readonly uint[] _rgbaColors;
/// <summary> Get a slice of the colors starting at <paramref name="offset"/> and containing <paramref name="count"/> colors. </summary>
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")!;
// Just copy all the data into an uint array.
_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 = [];
}
}
/// <inheritdoc/>
public IEnumerator<uint> GetEnumerator()
=> (IEnumerator<uint>)_rgbaColors.GetEnumerator();
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
/// <inheritdoc/>
public int Count
=> _rgbaColors.Length;
/// <inheritdoc/>
public uint this[int index]
=> _rgbaColors[index];
}

View file

@ -1,28 +1,38 @@
using System; using System;
using System.Runtime.InteropServices; 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, /// <summary>
// a byte value, an optional value-id and an optional icon or color. /// 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.
/// </summary>
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit)]
public readonly struct CustomizeData : IEquatable<CustomizeData> public readonly struct CustomizeData : IEquatable<CustomizeData>
{ {
/// <summary> The index of the option this value is for. </summary>
[FieldOffset(0)] [FieldOffset(0)]
public readonly CustomizeIndex Index; public readonly CustomizeIndex Index;
/// <summary> The value for the option. </summary>
[FieldOffset(1)] [FieldOffset(1)]
public readonly CustomizeValue Value; public readonly CustomizeValue Value;
/// <summary> The internal ID for sheets. </summary>
[FieldOffset(2)] [FieldOffset(2)]
public readonly ushort CustomizeId; public readonly ushort CustomizeId;
/// <summary> An ID for an associated icon. </summary>
[FieldOffset(4)] [FieldOffset(4)]
public readonly uint IconId; public readonly uint IconId;
/// <summary> An ID for an associated color. </summary>
[FieldOffset(4)] [FieldOffset(4)]
public readonly uint Color; public readonly uint Color;
/// <summary> Construct a CustomizeData from single data values. </summary>
public CustomizeData(CustomizeIndex index, CustomizeValue value, uint data = 0, ushort customizeId = 0) public CustomizeData(CustomizeIndex index, CustomizeValue value, uint data = 0, ushort customizeId = 0)
{ {
Index = index; Index = index;
@ -32,14 +42,23 @@ public readonly struct CustomizeData : IEquatable<CustomizeData>
CustomizeId = customizeId; CustomizeId = customizeId;
} }
/// <inheritdoc/>
public bool Equals(CustomizeData other) public bool Equals(CustomizeData other)
=> Index == other.Index => Index == other.Index
&& Value.Value == other.Value.Value && Value.Value == other.Value.Value
&& CustomizeId == other.CustomizeId; && CustomizeId == other.CustomizeId;
/// <inheritdoc/>
public override bool Equals(object? obj) public override bool Equals(object? obj)
=> obj is CustomizeData other && Equals(other); => obj is CustomizeData other && Equals(other);
/// <inheritdoc/>
public override int GetHashCode() public override int GetHashCode()
=> HashCode.Combine((int)Index, Value.Value, CustomizeId); => HashCode.Combine((int)Index, Value.Value, CustomizeId);
public static bool operator ==(CustomizeData left, CustomizeData right)
=> left.Equals(right);
public static bool operator !=(CustomizeData left, CustomizeData right)
=> !(left == right);
} }

View file

@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Dalamud.Interface.Internal;
using Dalamud.Plugin.Services;
using OtterGui.Classes;
using OtterGui.Services;
using Penumbra.GameData.Enums;
using Race = Penumbra.GameData.Enums.Race;
namespace Glamourer.GameData;
/// <summary> Generate everything about customization per tribe and gender. </summary>
public class CustomizeManager : IAsyncDataContainer
{
/// <summary> All races except for Unknown </summary>
public static readonly IReadOnlyList<Race> Races = ((Race[])Enum.GetValues(typeof(Race))).Skip(1).ToArray();
/// <summary> All tribes except for Unknown </summary>
public static readonly IReadOnlyList<SubRace> Clans = ((SubRace[])Enum.GetValues(typeof(SubRace))).Skip(1).ToArray();
/// <summary> Two genders. </summary>
public static readonly IReadOnlyList<Gender> Genders =
[
Gender.Male,
Gender.Female,
];
/// <summary> Every tribe and gender has a separate set of available customizations. </summary>
public CustomizeSet GetSet(SubRace race, Gender gender)
{
if (!Finished)
Awaiter.Wait();
return _customizationSets[ToIndex(race, gender)];
}
/// <summary> Get specific icons. </summary>
public IDalamudTextureWrap GetIcon(uint id)
=> _icons.LoadIcon(id)!;
/// <summary> Iterate over all supported genders and clans. </summary>
public static IEnumerable<(SubRace Clan, Gender Gender)> AllSets()
{
foreach (var clan in Clans)
{
yield return (clan, Gender.Male);
yield return (clan, Gender.Female);
}
}
public CustomizeManager(ITextureProvider textures, IDataManager gameData, IPluginLog log, NpcCustomizeSet npcCustomizeSet)
{
_icons = new IconStorage(textures, gameData);
var stopwatch = new Stopwatch();
var tmpTask = Task.Run(() =>
{
stopwatch.Start();
return new CustomizeSetFactory(gameData, log, _icons, npcCustomizeSet);
});
var setTasks = AllSets().Select(p
=> tmpTask.ContinueWith(t => _customizationSets[ToIndex(p.Clan, p.Gender)] = t.Result.CreateSet(p.Clan, p.Gender)));
Awaiter = Task.WhenAll(setTasks).ContinueWith(_ =>
{
// This is far too hard to estimate sensibly.
TotalCount = 0;
Memory = 0;
Time = stopwatch.ElapsedMilliseconds;
});
}
/// <inheritdoc/>
public Task Awaiter { get; }
/// <inheritdoc/>
public bool Finished
=> Awaiter.IsCompletedSuccessfully;
private readonly IconStorage _icons;
private static readonly int ListSize = Clans.Count * Genders.Count;
private readonly CustomizeSet[] _customizationSets = new CustomizeSet[ListSize];
/// <summary> Get the index for the given pair of tribe and gender. </summary>
private static int ToIndex(SubRace race, Gender gender)
{
var idx = ((int)race - 1) * Genders.Count + (gender == Gender.Female ? 1 : 0);
if (idx < 0 || idx >= ListSize)
throw new Exception($"Invalid customization requested for {race} {gender}.");
return idx;
}
public long Time { get; private set; }
public long Memory { get; private set; }
public string Name
=> nameof(CustomizeManager);
public int TotalCount { get; private set; }
}

View file

@ -4,14 +4,17 @@ using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using OtterGui; using OtterGui;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Customization; namespace Glamourer.GameData;
// Each Subrace and Gender combo has a customization set. /// <summary>
// This describes the available customizations, their types and their names. /// Each SubRace and Gender combo has a customization set.
public class CustomizationSet /// This describes the available customizations, their types and their names.
/// </summary>
public class CustomizeSet
{ {
internal CustomizationSet(SubRace clan, Gender gender) internal CustomizeSet(SubRace clan, Gender gender)
{ {
Gender = gender; Gender = gender;
Clan = clan; Clan = clan;
@ -23,6 +26,8 @@ public class CustomizationSet
public SubRace Clan { get; } public SubRace Clan { get; }
public Race Race { get; } public Race Race { get; }
public string Name { get; internal init; } = string.Empty;
public CustomizeFlag SettingAvailable { get; internal set; } public CustomizeFlag SettingAvailable { get; internal set; }
internal void SetAvailable(CustomizeIndex index) internal void SetAvailable(CustomizeIndex index)
@ -32,7 +37,7 @@ public class CustomizationSet
=> SettingAvailable.HasFlag(index.ToFlag()); => SettingAvailable.HasFlag(index.ToFlag());
// Meta // Meta
public IReadOnlyList<string> OptionName { get; internal set; } = null!; public IReadOnlyList<string> OptionName { get; internal init; } = null!;
public string Option(CustomizeIndex index) public string Option(CustomizeIndex index)
=> OptionName[(int)index]; => OptionName[(int)index];
@ -94,68 +99,6 @@ public class CustomizationSet
{ {
var type = Types[(int)index]; var type = Types[(int)index];
int GetInteger0(out CustomizeData? custom)
{
if (value < Count(index))
{
custom = new CustomizeData(index, value, 0, value.Value);
return value.Value;
}
custom = null;
return -1;
}
int GetInteger1(out CustomizeData? custom)
{
if (value > 0 && value < Count(index) + 1)
{
custom = new CustomizeData(index, value, 0, (ushort)(value.Value - 1));
return value.Value;
}
custom = null;
return -1;
}
static int GetBool(CustomizeIndex index, CustomizeValue value, out CustomizeData? custom)
{
if (value == CustomizeValue.Zero)
{
custom = new CustomizeData(index, CustomizeValue.Zero, 0, 0);
return 0;
}
var (_, mask) = index.ToByteAndMask();
if (value.Value == mask)
{
custom = new CustomizeData(index, new CustomizeValue(mask), 0, 1);
return 1;
}
custom = null;
return -1;
}
static int Invalid(out CustomizeData? custom)
{
custom = null;
return -1;
}
int Get(IEnumerable<CustomizeData> list, CustomizeValue v, out CustomizeData? output)
{
var (val, idx) = list.Cast<CustomizeData?>().WithIndex().FirstOrDefault(p => p.Item1!.Value.Value == v);
if (val == null)
{
output = null;
return -1;
}
output = val;
return idx;
}
return type switch return type switch
{ {
CharaMakeParams.MenuType.ListSelector => GetInteger0(out custom), CharaMakeParams.MenuType.ListSelector => GetInteger0(out custom),
@ -193,6 +136,68 @@ public class CustomizationSet
CharaMakeParams.MenuType.Checkmark => GetBool(index, value, out custom), CharaMakeParams.MenuType.Checkmark => GetBool(index, value, out custom),
_ => Invalid(out custom), _ => Invalid(out custom),
}; };
int Get(IEnumerable<CustomizeData> list, CustomizeValue v, out CustomizeData? output)
{
var (val, idx) = list.Cast<CustomizeData?>().WithIndex().FirstOrDefault(p => p.Value!.Value.Value == v);
if (val == null)
{
output = null;
return -1;
}
output = val;
return idx;
}
static int Invalid(out CustomizeData? custom)
{
custom = null;
return -1;
}
static int GetBool(CustomizeIndex index, CustomizeValue value, out CustomizeData? custom)
{
if (value == CustomizeValue.Zero)
{
custom = new CustomizeData(index, CustomizeValue.Zero);
return 0;
}
var (_, mask) = index.ToByteAndMask();
if (value.Value == mask)
{
custom = new CustomizeData(index, new CustomizeValue(mask), 0, 1);
return 1;
}
custom = null;
return -1;
}
int GetInteger1(out CustomizeData? custom)
{
if (value > 0 && value < Count(index) + 1)
{
custom = new CustomizeData(index, value, 0, (ushort)(value.Value - 1));
return value.Value;
}
custom = null;
return -1;
}
int GetInteger0(out CustomizeData? custom)
{
if (value < Count(index))
{
custom = new CustomizeData(index, value, 0, value.Value);
return value.Value;
}
custom = null;
return -1;
}
} }
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
@ -243,19 +248,6 @@ public class CustomizationSet
public CharaMakeParams.MenuType Type(CustomizeIndex index) public CharaMakeParams.MenuType Type(CustomizeIndex index)
=> Types[(int)index]; => Types[(int)index];
internal static IReadOnlyDictionary<CharaMakeParams.MenuType, CustomizeIndex[]> ComputeOrder(CustomizationSet set)
{
var ret = Enum.GetValues<CustomizeIndex>().ToArray();
ret[(int)CustomizeIndex.TattooColor] = CustomizeIndex.EyeColorLeft;
ret[(int)CustomizeIndex.EyeColorLeft] = CustomizeIndex.EyeColorRight;
ret[(int)CustomizeIndex.EyeColorRight] = CustomizeIndex.TattooColor;
var dict = ret.Skip(2).Where(set.IsAvailable).GroupBy(set.Type).ToDictionary(k => k.Key, k => k.ToArray());
foreach (var type in Enum.GetValues<CharaMakeParams.MenuType>())
dict.TryAdd(type, Array.Empty<CustomizeIndex>());
return dict;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public int Count(CustomizeIndex index) public int Count(CustomizeIndex index)
=> Count(index, CustomizeValue.Zero); => Count(index, CustomizeValue.Zero);
@ -300,3 +292,10 @@ public class CustomizationSet
private CustomizeValue HrothgarFaceHack(CustomizeValue value) private CustomizeValue HrothgarFaceHack(CustomizeValue value)
=> Race == Race.Hrothgar && value.Value is > 4 and < 9 ? value - 4 : 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, CustomizeSet set)
=> flag & (set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.BodyType);
}

View file

@ -0,0 +1,473 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Dalamud;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
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.GameData;
internal class CustomizeSetFactory(
IDataManager _gameData,
IPluginLog _log,
IconStorage _icons,
NpcCustomizeSet _npcCustomizeSet,
ColorParameters _colors)
{
public CustomizeSetFactory(IDataManager gameData, IPluginLog log, IconStorage icons, NpcCustomizeSet npcCustomizeSet)
: this(gameData, log, icons, npcCustomizeSet, new ColorParameters(gameData, log))
{ }
/// <summary> Create the set of all available customization options for a given clan and gender. </summary>
public CustomizeSet CreateSet(SubRace race, Gender gender)
{
var (skin, hair) = GetSkinHairColors(race, gender);
var row = _charaMakeSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
var hrothgar = race.ToRace() == Race.Hrothgar;
// Create the initial set with all the easily accessible parameters available for anyone.
var set = new CustomizeSet(race, gender)
{
Name = GetName(race, gender),
Voices = row.Voices,
HairStyles = GetHairStyles(race, gender),
HairColors = hair,
SkinColors = skin,
EyeColors = _eyeColorPicker,
HighlightColors = _highlightPicker,
TattooColors = _tattooColorPicker,
LipColorsDark = hrothgar ? HrothgarFurPattern(row) : _lipColorPickerDark,
LipColorsLight = hrothgar ? [] : _lipColorPickerLight,
FacePaintColorsDark = _facePaintColorPickerDark,
FacePaintColorsLight = _facePaintColorPickerLight,
Faces = GetFaces(row),
NumEyebrows = GetListSize(row, CustomizeIndex.Eyebrows),
NumEyeShapes = GetListSize(row, CustomizeIndex.EyeShape),
NumNoseShapes = GetListSize(row, CustomizeIndex.Nose),
NumJawShapes = GetListSize(row, CustomizeIndex.Jaw),
NumMouthShapes = GetListSize(row, CustomizeIndex.Mouth),
FacePaints = GetFacePaints(race, gender),
TailEarShapes = GetTailEarShapes(row),
OptionName = GetOptionNames(row),
Types = GetMenuTypes(row),
};
SetPostProcessing(set, row);
return set;
}
/// <summary> Some data can not be set independently of the rest, so we need a post-processing step to finalize. </summary>
private void SetPostProcessing(CustomizeSet set, CharaMakeParams row)
{
SetAvailability(set, row);
SetFacialFeatures(set, row);
SetHairByFace(set);
SetNpcData(set, set.Clan, set.Gender);
SetOrder(set);
}
/// <summary> Given a customize set with filled data, find all customizations used by valid NPCs that are not regularly available. </summary>
private void SetNpcData(CustomizeSet set, SubRace race, Gender gender)
{
var customizeIndices = new[]
{
CustomizeIndex.Face,
CustomizeIndex.Hairstyle,
CustomizeIndex.LipColor,
CustomizeIndex.SkinColor,
CustomizeIndex.FacePaintColor,
CustomizeIndex.HighlightsColor,
CustomizeIndex.HairColor,
CustomizeIndex.FacePaint,
CustomizeIndex.TattooColor,
CustomizeIndex.EyeColorLeft,
CustomizeIndex.EyeColorRight,
};
var npcCustomizations = new HashSet<(CustomizeIndex, CustomizeValue)>();
_npcCustomizeSet.Awaiter.Wait();
foreach (var customize in _npcCustomizeSet.Select(s => s.Customize).Where(c => c.Clan == race && c.Gender == gender && c.BodyType.Value == 1))
{
foreach (var customizeIndex in customizeIndices)
{
var value = customize[customizeIndex];
if (value == CustomizeValue.Zero)
continue;
if (set.DataByValue(customizeIndex, value, out _, customize.Face) >= 0)
continue;
npcCustomizations.Add((customizeIndex, value));
}
}
set.NpcOptions = npcCustomizations.OrderBy(p => p.Item1).ThenBy(p => p.Item2.Value).ToArray();
}
private readonly ColorParameters _colorParameters = new(_gameData, _log);
private readonly ExcelSheet<CharaMakeCustomize> _customizeSheet = _gameData.GetExcelSheet<CharaMakeCustomize>(ClientLanguage.English)!;
private readonly ExcelSheet<Lobby> _lobbySheet = _gameData.GetExcelSheet<Lobby>(ClientLanguage.English)!;
private readonly ExcelSheet<HairMakeType> _hairSheet = _gameData.GetExcelSheet<HairMakeType>(ClientLanguage.English)!;
private readonly ExcelSheet<Tribe> _tribeSheet = _gameData.GetExcelSheet<Tribe>(ClientLanguage.English)!;
// Those color pickers are shared between all races.
private readonly CustomizeData[] _highlightPicker = CreateColors(_colors, CustomizeIndex.HighlightsColor, 256, 192);
private readonly CustomizeData[] _lipColorPickerDark = CreateColors(_colors, CustomizeIndex.LipColor, 512, 96);
private readonly CustomizeData[] _lipColorPickerLight = CreateColors(_colors, CustomizeIndex.LipColor, 1024, 96, true);
private readonly CustomizeData[] _eyeColorPicker = CreateColors(_colors, CustomizeIndex.EyeColorLeft, 0, 192);
private readonly CustomizeData[] _facePaintColorPickerDark = CreateColors(_colors, CustomizeIndex.FacePaintColor, 640, 96);
private readonly CustomizeData[] _facePaintColorPickerLight = CreateColors(_colors, CustomizeIndex.FacePaintColor, 1152, 96, true);
private readonly CustomizeData[] _tattooColorPicker = CreateColors(_colors, CustomizeIndex.TattooColor, 0, 192);
private readonly ExcelSheet<CharaMakeParams> _charaMakeSheet = _gameData.Excel
.GetType()
.GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)?
.MakeGenericMethod(typeof(CharaMakeParams))
.Invoke(_gameData.Excel, ["charamaketype", _gameData.Language.ToLumina(), null])! as ExcelSheet<CharaMakeParams>
?? null!;
/// <summary> Obtain available skin and hair colors for the given clan and gender. </summary>
private (CustomizeData[] Skin, CustomizeData[] Hair) GetSkinHairColors(SubRace race, Gender gender)
{
if (race is > SubRace.Veena or SubRace.Unknown)
throw new ArgumentOutOfRangeException(nameof(race), race, null);
var gv = gender == Gender.Male ? 0 : 1;
var idx = ((int)race * 2 + gv) * 5 + 3;
return (CreateColors(_colorParameters, CustomizeIndex.SkinColor, idx << 8, 192),
CreateColors(_colorParameters, CustomizeIndex.HairColor, (idx + 1) << 8, 192));
}
/// <summary> Obtain the gender-specific clan name. </summary>
private string GetName(SubRace race, Gender gender)
=> gender switch
{
Gender.Male => _tribeSheet.GetRow((uint)race)?.Masculine.ToDalamudString().TextValue ?? race.ToName(),
Gender.Female => _tribeSheet.GetRow((uint)race)?.Feminine.ToDalamudString().TextValue ?? race.ToName(),
_ => "Unknown",
};
/// <summary> Obtain available hairstyles via reflection from the Hair sheet for the given subrace and gender. </summary>
private CustomizeData[] GetHairStyles(SubRace race, Gender gender)
{
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
// Unknown30 is the number of available hairstyles.
var hairList = new List<CustomizeData>(row.Unknown30);
// Hairstyles can be found starting at Unknown66.
for (var i = 0; i < row.Unknown30; ++i)
{
var name = $"Unknown{66 + i * 9}";
var customizeIdx = (uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
?? uint.MaxValue;
if (customizeIdx == uint.MaxValue)
continue;
// Hair Row from CustomizeSheet might not be set in case of unlockable hair.
var hairRow = _customizeSheet.GetRow(customizeIdx);
if (hairRow == null)
hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)i, customizeIdx));
else if (_icons.IconExists(hairRow.Icon))
hairList.Add(new CustomizeData(CustomizeIndex.Hairstyle, (CustomizeValue)hairRow.FeatureID, hairRow.Icon,
(ushort)hairRow.RowId));
}
return [.. hairList.OrderBy(h => h.Value.Value)];
}
/// <summary> Specific icons for tails or ears. </summary>
private CustomizeData[] GetTailEarShapes(CharaMakeParams row)
=> row.Menus.Cast<CharaMakeParams.Menu?>()
.FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.TailShape.ToByteAndMask().ByteIdx)?.Values
.Select((v, i) => FromValueAndIndex(CustomizeIndex.TailShape, v, i)).ToArray()
?? [];
/// <summary> Specific icons for faces. </summary>
private CustomizeData[] GetFaces(CharaMakeParams row)
=> row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.Face.ToByteAndMask().ByteIdx)
?.Values
.Select((v, i) => FromValueAndIndex(CustomizeIndex.Face, v, i)).ToArray()
?? [];
/// <summary> Specific icons for Hrothgar patterns. </summary>
private CustomizeData[] HrothgarFurPattern(CharaMakeParams row)
=> row.Menus.Cast<CharaMakeParams.Menu?>()
.FirstOrDefault(m => m!.Value.Customize == CustomizeIndex.LipColor.ToByteAndMask().ByteIdx)?.Values
.Select((v, i) => FromValueAndIndex(CustomizeIndex.LipColor, v, i)).ToArray()
?? [];
/// <summary> Get face paints from the hair sheet via reflection since there are also unlockable face paints. </summary>
private CustomizeData[] GetFacePaints(SubRace race, Gender gender)
{
var row = _hairSheet.GetRow(((uint)race - 1) * 2 - 1 + (uint)gender)!;
var paintList = new List<CustomizeData>(row.Unknown37);
// Number of available face paints is at Unknown37.
for (var i = 0; i < row.Unknown37; ++i)
{
// Face paints start at Unknown73.
var name = $"Unknown{73 + i * 9}";
var customizeIdx =
(uint?)row.GetType().GetProperty(name, BindingFlags.Public | BindingFlags.Instance)?.GetValue(row)
?? uint.MaxValue;
if (customizeIdx == uint.MaxValue)
continue;
var paintRow = _customizeSheet.GetRow(customizeIdx);
// Face paint Row from CustomizeSheet might not be set in case of unlockable face paints.
if (paintRow != null)
paintList.Add(new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)paintRow.FeatureID, paintRow.Icon,
(ushort)paintRow.RowId));
else
paintList.Add(new CustomizeData(CustomizeIndex.FacePaint, (CustomizeValue)i, customizeIdx));
}
return [.. paintList.OrderBy(p => p.Value.Value)];
}
/// <summary> Get List sizes. </summary>
private static int GetListSize(CharaMakeParams row, CustomizeIndex index)
{
var gameId = index.ToByteAndMask().ByteIdx;
var menu = row.Menus.Cast<CharaMakeParams.Menu?>().FirstOrDefault(m => m!.Value.Customize == gameId);
return menu?.Size ?? 0;
}
/// <summary> Get generic Features. </summary>
private CustomizeData FromValueAndIndex(CustomizeIndex id, uint value, int index)
{
var row = _customizeSheet.GetRow(value);
return row == null
? new CustomizeData(id, (CustomizeValue)(index + 1), value)
: new CustomizeData(id, (CustomizeValue)row.FeatureID, row.Icon, (ushort)row.RowId);
}
/// <summary> Create generic color sets from the parameters. </summary>
private static CustomizeData[] CreateColors(ColorParameters colorParameters, CustomizeIndex index, int offset, int num,
bool light = false)
{
var ret = new CustomizeData[num];
var idx = 0;
foreach (var value in colorParameters.GetSlice(offset, num))
{
ret[idx] = new CustomizeData(index, (CustomizeValue)(light ? 128 + idx : idx), value, (ushort)(offset + idx));
++idx;
}
return ret;
}
/// <summary> Set the specific option names for the given set of parameters. </summary>
private string[] GetOptionNames(CharaMakeParams row)
{
var nameArray = Enum.GetValues<CustomizeIndex>().Select(c =>
{
// Find the first menu that corresponds to the Id.
var byteId = c.ToByteAndMask().ByteIdx;
var menu = row.Menus
.Cast<CharaMakeParams.Menu?>()
.FirstOrDefault(m => m!.Value.Customize == byteId);
if (menu == null)
{
// If none exists and the id corresponds to highlights, set the Highlights name.
if (c == CustomizeIndex.Highlights)
return string.Intern(_lobbySheet.GetRow(237)?.Text.ToDalamudString().ToString() ?? "Highlights");
// Otherwise there is an error and we use the default name.
return c.ToDefaultName();
}
// Otherwise all is normal, get the menu name or if it does not work the default name.
var textRow = _lobbySheet.GetRow(menu.Value.Id);
return string.Intern(textRow?.Text.ToDalamudString().ToString() ?? c.ToDefaultName());
}).ToArray();
// Add names for both eye colors.
nameArray[(int)CustomizeIndex.EyeColorLeft] = CustomizeIndex.EyeColorLeft.ToDefaultName();
nameArray[(int)CustomizeIndex.EyeColorRight] = CustomizeIndex.EyeColorRight.ToDefaultName();
nameArray[(int)CustomizeIndex.FacialFeature1] = CustomizeIndex.FacialFeature1.ToDefaultName();
nameArray[(int)CustomizeIndex.FacialFeature2] = CustomizeIndex.FacialFeature2.ToDefaultName();
nameArray[(int)CustomizeIndex.FacialFeature3] = CustomizeIndex.FacialFeature3.ToDefaultName();
nameArray[(int)CustomizeIndex.FacialFeature4] = CustomizeIndex.FacialFeature4.ToDefaultName();
nameArray[(int)CustomizeIndex.FacialFeature5] = CustomizeIndex.FacialFeature5.ToDefaultName();
nameArray[(int)CustomizeIndex.FacialFeature6] = CustomizeIndex.FacialFeature6.ToDefaultName();
nameArray[(int)CustomizeIndex.FacialFeature7] = CustomizeIndex.FacialFeature7.ToDefaultName();
nameArray[(int)CustomizeIndex.LegacyTattoo] = CustomizeIndex.LegacyTattoo.ToDefaultName();
nameArray[(int)CustomizeIndex.SmallIris] = CustomizeIndex.SmallIris.ToDefaultName();
nameArray[(int)CustomizeIndex.Lipstick] = CustomizeIndex.Lipstick.ToDefaultName();
nameArray[(int)CustomizeIndex.FacePaintReversed] = CustomizeIndex.FacePaintReversed.ToDefaultName();
return nameArray;
}
/// <summary> Get the manu types for all available options. </summary>
private static CharaMakeParams.MenuType[] GetMenuTypes(CharaMakeParams row)
{
// Set up the menu types for all customizations.
return Enum.GetValues<CustomizeIndex>().Select(c =>
{
// Those types are not correctly given in the menu, so special case them to color pickers.
switch (c)
{
case CustomizeIndex.HighlightsColor:
case CustomizeIndex.EyeColorLeft:
case CustomizeIndex.EyeColorRight:
case CustomizeIndex.FacePaintColor:
return CharaMakeParams.MenuType.ColorPicker;
case CustomizeIndex.BodyType: return CharaMakeParams.MenuType.Nothing;
case CustomizeIndex.FacePaintReversed:
case CustomizeIndex.Highlights:
case CustomizeIndex.SmallIris:
case CustomizeIndex.Lipstick:
return CharaMakeParams.MenuType.Checkmark;
case CustomizeIndex.FacialFeature1:
case CustomizeIndex.FacialFeature2:
case CustomizeIndex.FacialFeature3:
case CustomizeIndex.FacialFeature4:
case CustomizeIndex.FacialFeature5:
case CustomizeIndex.FacialFeature6:
case CustomizeIndex.FacialFeature7:
case CustomizeIndex.LegacyTattoo:
return CharaMakeParams.MenuType.IconCheckmark;
}
var gameId = c.ToByteAndMask().ByteIdx;
// Otherwise find the first menu corresponding to the id.
// If there is none, assume a list.
var menu = row.Menus
.Cast<CharaMakeParams.Menu?>()
.FirstOrDefault(m => m!.Value.Customize == gameId);
var ret = menu?.Type ?? CharaMakeParams.MenuType.ListSelector;
if (c is CustomizeIndex.TailShape && ret is CharaMakeParams.MenuType.ListSelector)
ret = CharaMakeParams.MenuType.List1Selector;
return ret;
}).ToArray();
}
/// <summary> Set the availability of options according to actual availability. </summary>
private static void SetAvailability(CustomizeSet set, CharaMakeParams row)
{
// TODO: Hrothgar female
if (set is { Race: Race.Hrothgar, Gender: Gender.Female })
return;
Set(true, CustomizeIndex.Height);
Set(set.Faces.Count > 0, CustomizeIndex.Face);
Set(true, CustomizeIndex.Hairstyle);
Set(true, CustomizeIndex.Highlights);
Set(true, CustomizeIndex.SkinColor);
Set(true, CustomizeIndex.EyeColorRight);
Set(true, CustomizeIndex.HairColor);
Set(true, CustomizeIndex.HighlightsColor);
Set(true, CustomizeIndex.TattooColor);
Set(set.NumEyebrows > 0, CustomizeIndex.Eyebrows);
Set(true, CustomizeIndex.EyeColorLeft);
Set(set.NumEyeShapes > 0, CustomizeIndex.EyeShape);
Set(set.NumNoseShapes > 0, CustomizeIndex.Nose);
Set(set.NumJawShapes > 0, CustomizeIndex.Jaw);
Set(set.NumMouthShapes > 0, CustomizeIndex.Mouth);
Set(set.LipColorsDark.Count > 0, CustomizeIndex.LipColor);
Set(GetListSize(row, CustomizeIndex.MuscleMass) > 0, CustomizeIndex.MuscleMass);
Set(set.TailEarShapes.Count > 0, CustomizeIndex.TailShape);
Set(GetListSize(row, CustomizeIndex.BustSize) > 0, CustomizeIndex.BustSize);
Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaint);
Set(set.FacePaints.Count > 0, CustomizeIndex.FacePaintColor);
Set(true, CustomizeIndex.FacialFeature1);
Set(true, CustomizeIndex.FacialFeature2);
Set(true, CustomizeIndex.FacialFeature3);
Set(true, CustomizeIndex.FacialFeature4);
Set(true, CustomizeIndex.FacialFeature5);
Set(true, CustomizeIndex.FacialFeature6);
Set(true, CustomizeIndex.FacialFeature7);
Set(true, CustomizeIndex.LegacyTattoo);
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);
}
}
internal static void SetOrder(CustomizeSet set)
{
var ret = Enum.GetValues<CustomizeIndex>().ToArray();
ret[(int)CustomizeIndex.TattooColor] = CustomizeIndex.EyeColorLeft;
ret[(int)CustomizeIndex.EyeColorLeft] = CustomizeIndex.EyeColorRight;
ret[(int)CustomizeIndex.EyeColorRight] = CustomizeIndex.TattooColor;
var dict = ret.Skip(2).Where(set.IsAvailable).GroupBy(set.Type).ToDictionary(k => k.Key, k => k.ToArray());
foreach (var type in Enum.GetValues<CharaMakeParams.MenuType>())
dict.TryAdd(type, []);
set.Order = dict;
}
/// <summary> Set hairstyles per face for Hrothgar and make it simple for non-Hrothgar. </summary>
private void SetHairByFace(CustomizeSet set)
{
if (set.Race != Race.Hrothgar)
{
set.HairByFace = Enumerable.Repeat(set.HairStyles, set.Faces.Count + 1).ToArray();
return;
}
var tmp = new IReadOnlyList<CustomizeData>[set.Faces.Count + 1];
tmp[0] = set.HairStyles;
for (var i = 1; i <= set.Faces.Count; ++i)
{
tmp[i] = set.HairStyles.Where(Valid).ToArray();
continue;
bool Valid(CustomizeData c)
{
var data = _customizeSheet.GetRow(c.CustomizeId)?.Unknown6 ?? 0;
return data == 0 || data == i + set.Faces.Count;
}
}
set.HairByFace = tmp;
}
/// <summary>
/// Create a list of lists of facial features and the legacy tattoo.
/// Facial Features are bools in a bitfield, so we supply an "off" and an "on" value for simplicity of use.
/// </summary>
private static void SetFacialFeatures(CustomizeSet set, CharaMakeParams row)
{
var count = set.Faces.Count;
set.FacialFeature1 = new List<(CustomizeData, CustomizeData)>(count);
set.LegacyTattoo = Create(CustomizeIndex.LegacyTattoo, 137905);
var tmp = Enumerable.Repeat(0, 7).Select(_ => new (CustomizeData, CustomizeData)[count + 1]).ToArray();
for (var i = 0; i < count; ++i)
{
var data = row.FacialFeatureByFace[i].Icons;
tmp[0][i + 1] = Create(CustomizeIndex.FacialFeature1, data[0]);
tmp[1][i + 1] = Create(CustomizeIndex.FacialFeature2, data[1]);
tmp[2][i + 1] = Create(CustomizeIndex.FacialFeature3, data[2]);
tmp[3][i + 1] = Create(CustomizeIndex.FacialFeature4, data[3]);
tmp[4][i + 1] = Create(CustomizeIndex.FacialFeature5, data[4]);
tmp[5][i + 1] = Create(CustomizeIndex.FacialFeature6, data[5]);
tmp[6][i + 1] = Create(CustomizeIndex.FacialFeature7, data[6]);
}
set.FacialFeature1 = tmp[0];
set.FacialFeature2 = tmp[1];
set.FacialFeature3 = tmp[2];
set.FacialFeature4 = tmp[3];
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));
}
}

View file

@ -0,0 +1,319 @@
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Dalamud.Plugin.Services;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Lumina.Excel.GeneratedSheets;
using OtterGui.Services;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Structs;
namespace Glamourer.GameData;
/// <summary> Contains a set of all human NPC appearances with their names. </summary>
public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
{
/// <inheritdoc/>
public string Name
=> nameof(NpcCustomizeSet);
/// <inheritdoc/>
public long Time { get; private set; }
/// <inheritdoc/>
public long Memory { get; private set; }
/// <inheritdoc/>
public int TotalCount
=> _data.Count;
/// <inheritdoc/>
public Task Awaiter { get; }
/// <inheritdoc/>
public bool Finished
=> Awaiter.IsCompletedSuccessfully;
/// <summary> The list of data. </summary>
private readonly List<NpcData> _data = [];
/// <summary> Create the data when ready. </summary>
public NpcCustomizeSet(IDataManager data, DictENpc eNpcs, DictBNpc bNpcs, DictBNpcNames bNpcNames)
{
var waitTask = Task.WhenAll(eNpcs.Awaiter, bNpcs.Awaiter, bNpcNames.Awaiter);
Awaiter = waitTask.ContinueWith(_ =>
{
var watch = Stopwatch.StartNew();
var eNpcTask = Task.Run(() => CreateEnpcData(data, eNpcs));
var bNpcTask = Task.Run(() => CreateBnpcData(data, bNpcs, bNpcNames));
FilterAndOrderNpcData(eNpcTask.Result, bNpcTask.Result);
Time = watch.ElapsedMilliseconds;
});
}
/// <summary> Create data from event NPCs. </summary>
private static List<NpcData> CreateEnpcData(IDataManager data, DictENpc eNpcs)
{
var enpcSheet = data.GetExcelSheet<ENpcBase>()!;
var list = new List<NpcData>(eNpcs.Count);
// Go through all event NPCs already collected into a dictionary.
foreach (var (id, name) in eNpcs)
{
var row = enpcSheet.GetRow(id.Id);
// We only accept NPCs with valid names.
if (row == null || name.IsNullOrWhitespace())
continue;
// Check if the customization is a valid human.
var (valid, customize) = FromEnpcBase(row);
if (!valid)
continue;
var ret = new NpcData
{
Name = name,
Customize = customize,
Id = id,
Kind = ObjectKind.EventNpc,
};
// Event NPCs have a reference to NpcEquip but also contain the appearance in their own row.
// Prefer the NpcEquip reference if it is set, otherwise use the own.
if (row.NpcEquip.Row != 0 && row.NpcEquip.Value is { } equip)
{
ApplyNpcEquip(ref ret, equip);
}
else
{
ret.Set(0, row.ModelHead | (row.DyeHead.Row << 24));
ret.Set(1, row.ModelBody | (row.DyeBody.Row << 24));
ret.Set(2, row.ModelHands | (row.DyeHands.Row << 24));
ret.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24));
ret.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24));
ret.Set(5, row.ModelEars | (row.DyeEars.Row << 24));
ret.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24));
ret.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24));
ret.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24));
ret.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24));
ret.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48));
ret.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48));
ret.VisorToggled = row.Visor;
}
list.Add(ret);
}
return list;
}
/// <summary> Create data from battle NPCs. </summary>
private static List<NpcData> CreateBnpcData(IDataManager data, DictBNpc bNpcs, DictBNpcNames bNpcNames)
{
var bnpcSheet = data.GetExcelSheet<BNpcBase>()!;
var list = new List<NpcData>((int)bnpcSheet.RowCount);
// We go through all battle NPCs in the sheet because the dictionary refers to names.
foreach (var baseRow in bnpcSheet)
{
// Only accept humans.
if (baseRow.ModelChara.Value!.Type != 1)
continue;
var bnpcNameIds = bNpcNames[baseRow.RowId];
// Only accept battle NPCs with known associated names.
if (bnpcNameIds.Count == 0)
continue;
// Check if the customization is a valid human.
var (valid, customize) = FromBnpcCustomize(baseRow.BNpcCustomize.Value!);
if (!valid)
continue;
var equip = baseRow.NpcEquip.Value!;
var ret = new NpcData
{
Customize = customize,
Id = baseRow.RowId,
Kind = ObjectKind.BattleNpc,
};
ApplyNpcEquip(ref ret, equip);
// Add the appearance for each associated name.
foreach (var bnpcNameId in bnpcNameIds)
{
if (bNpcs.TryGetValue(bnpcNameId.Id, out var name) && !name.IsNullOrWhitespace())
list.Add(ret with { Name = name });
}
}
return list;
}
/// <summary> Given the battle NPC and event NPC lists, order and deduplicate entries. </summary>
private void FilterAndOrderNpcData(IReadOnlyCollection<NpcData> eNpcEquip, IReadOnlyCollection<NpcData> bNpcEquip)
{
_data.Clear();
// This is a maximum since we deduplicate.
_data.EnsureCapacity(eNpcEquip.Count + bNpcEquip.Count);
// Convert the NPCs to a dictionary of lists grouped by name.
var groups = eNpcEquip.Concat(bNpcEquip).GroupBy(d => d.Name).ToDictionary(g => g.Key, g => g.ToList());
// Iterate through the sorted list.
foreach (var (_, duplicates) in groups.OrderBy(kvp => kvp.Key))
{
// Remove any duplicate entries for a name with identical data.
for (var i = 0; i < duplicates.Count; ++i)
{
var current = duplicates[i];
for (var j = 0; j < i; ++j)
{
if (current.DataEquals(duplicates[j]))
{
duplicates.RemoveAt(i--);
break;
}
}
}
// If there is only a single entry, add that.
if (duplicates.Count == 1)
{
_data.Add(duplicates[0]);
Memory += 96;
}
else
{
_data.AddRange(duplicates);
Memory += 96 * duplicates.Count;
}
}
// Sort non-alphanumeric entries at the end instead of the beginning.
var lastWeird = _data.FindIndex(d => char.IsAsciiLetterOrDigit(d.Name[0]));
if (lastWeird != -1)
{
_data.AddRange(_data.Take(lastWeird));
_data.RemoveRange(0, lastWeird);
}
// Reduce memory footprint.
_data.TrimExcess();
}
/// <summary> Apply equipment from a NpcEquip row. </summary>
private static void ApplyNpcEquip(ref NpcData data, NpcEquip row)
{
data.Set(0, row.ModelHead | (row.DyeHead.Row << 24));
data.Set(1, row.ModelBody | (row.DyeBody.Row << 24));
data.Set(2, row.ModelHands | (row.DyeHands.Row << 24));
data.Set(3, row.ModelLegs | (row.DyeLegs.Row << 24));
data.Set(4, row.ModelFeet | (row.DyeFeet.Row << 24));
data.Set(5, row.ModelEars | (row.DyeEars.Row << 24));
data.Set(6, row.ModelNeck | (row.DyeNeck.Row << 24));
data.Set(7, row.ModelWrists | (row.DyeWrists.Row << 24));
data.Set(8, row.ModelRightRing | (row.DyeRightRing.Row << 24));
data.Set(9, row.ModelLeftRing | (row.DyeLeftRing.Row << 24));
data.Mainhand = new CharacterWeapon(row.ModelMainHand | ((ulong)row.DyeMainHand.Row << 48));
data.Offhand = new CharacterWeapon(row.ModelOffHand | ((ulong)row.DyeOffHand.Row << 48));
data.VisorToggled = row.Visor;
}
/// <summary> Obtain customizations from a BNpcCustomize row and check if the human is valid. </summary>
private static (bool, CustomizeArray) FromBnpcCustomize(BNpcCustomize bnpcCustomize)
{
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 (!CustomizeManager.Races.Contains(customize.Race)
|| !CustomizeManager.Clans.Contains(customize.Clan)
|| !CustomizeManager.Genders.Contains(customize.Gender))
return (false, CustomizeArray.Default);
return (true, customize);
}
/// <summary> Obtain customizations from a ENpcBase row and check if the human is valid. </summary>
private static (bool, CustomizeArray) FromEnpcBase(ENpcBase enpcBase)
{
if (enpcBase.ModelChara.Value?.Type != 1)
return (false, CustomizeArray.Default);
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 (!CustomizeManager.Races.Contains(customize.Race)
|| !CustomizeManager.Clans.Contains(customize.Clan)
|| !CustomizeManager.Genders.Contains(customize.Gender))
return (false, CustomizeArray.Default);
return (true, customize);
}
/// <inheritdoc/>
public IEnumerator<NpcData> GetEnumerator()
=> _data.GetEnumerator();
/// <inheritdoc/>
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
/// <inheritdoc/>
public int Count
=> _data.Count;
/// <inheritdoc/>
public NpcData this[int index]
=> _data[index];
}

View file

@ -0,0 +1,109 @@
using System;
using System.Text;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Penumbra.GameData.Structs;
namespace Glamourer.GameData;
/// <summary> A struct containing everything to replicate the appearance of a human NPC. </summary>
public unsafe struct NpcData
{
/// <summary> The name of the NPC. </summary>
public string Name;
/// <summary> The customizations of the NPC. </summary>
public CustomizeArray Customize;
/// <summary> The equipment appearance of the NPC, 10 * CharacterArmor. </summary>
private fixed byte _equip[40];
/// <summary> The mainhand weapon appearance of the NPC. </summary>
public CharacterWeapon Mainhand;
/// <summary> The offhand weapon appearance of the NPC. </summary>
public CharacterWeapon Offhand;
/// <summary> The data ID of the NPC, either event NPC or battle NPC name. </summary>
public NpcId Id;
/// <summary> Whether the NPCs visor is toggled. </summary>
public bool VisorToggled;
/// <summary> Whether the NPC is an event NPC or a battle NPC. </summary>
public ObjectKind Kind;
/// <summary> Obtain the equipment as CharacterArmors. </summary>
public ReadOnlySpan<CharacterArmor> Equip
{
get
{
fixed (byte* ptr = _equip)
{
return new ReadOnlySpan<CharacterArmor>((CharacterArmor*)ptr, 10);
}
}
}
/// <summary> Write all the gear appearance to a single string. </summary>
public string WriteGear()
{
var sb = new StringBuilder(128);
var span = Equip;
for (var i = 0; i < 10; ++i)
{
sb.Append(span[i].Set.Id.ToString("D4"))
.Append('-')
.Append(span[i].Variant.Id.ToString("D3"))
.Append('-')
.Append(span[i].Stain.Id.ToString("D3"))
.Append(", ");
}
sb.Append(Mainhand.Skeleton.Id.ToString("D4"))
.Append('-')
.Append(Mainhand.Weapon.Id.ToString("D4"))
.Append('-')
.Append(Mainhand.Variant.Id.ToString("D3"))
.Append('-')
.Append(Mainhand.Stain.Id.ToString("D4"))
.Append(", ")
.Append(Offhand.Skeleton.Id.ToString("D4"))
.Append('-')
.Append(Offhand.Weapon.Id.ToString("D4"))
.Append('-')
.Append(Offhand.Variant.Id.ToString("D3"))
.Append('-')
.Append(Offhand.Stain.Id.ToString("D3"));
return sb.ToString();
}
/// <summary> Set an equipment piece to a given value. </summary>
internal void Set(int idx, uint value)
{
fixed (byte* ptr = _equip)
{
((uint*)ptr)[idx] = value;
}
}
/// <summary> Check if the appearance data, excluding ID and Name, of two NpcData is equal. </summary>
public bool DataEquals(in NpcData other)
{
if (VisorToggled != other.VisorToggled)
return false;
if (!Customize.Equals(other.Customize))
return false;
if (!Mainhand.Equals(other.Mainhand))
return false;
if (!Offhand.Equals(other.Offhand))
return false;
fixed (byte* ptr1 = _equip, ptr2 = other._equip)
{
return new ReadOnlySpan<byte>(ptr1, 40).SequenceEqual(new ReadOnlySpan<byte>(ptr2, 40));
}
}
}

View file

@ -8,6 +8,7 @@ using Glamourer.State;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using OtterGui.Classes; using OtterGui.Classes;
using OtterGui.Log; using OtterGui.Log;
using OtterGui.Services;
namespace Glamourer; namespace Glamourer;
@ -25,21 +26,21 @@ public class Glamourer : IDalamudPlugin
public static readonly Logger Log = new(); public static readonly Logger Log = new();
public static MessageService Messager { get; private set; } = null!; public static MessageService Messager { get; private set; } = null!;
private readonly ServiceProvider _services; private readonly ServiceManager _services;
public Glamourer(DalamudPluginInterface pluginInterface) public Glamourer(DalamudPluginInterface pluginInterface)
{ {
try try
{ {
_services = ServiceManager.CreateProvider(pluginInterface, Log); _services = ServiceManagerA.CreateProvider(pluginInterface, Log);
Messager = _services.GetRequiredService<MessageService>(); Messager = _services.GetService<MessageService>();
_services.GetRequiredService<VisorService>(); _services.GetService<VisorService>();
_services.GetRequiredService<WeaponService>(); _services.GetService<WeaponService>();
_services.GetRequiredService<ScalingService>(); _services.GetService<ScalingService>();
_services.GetRequiredService<StateListener>(); // Initialize State Listener. _services.GetService<StateListener>(); // Initialize State Listener.
_services.GetRequiredService<GlamourerWindowSystem>(); // initialize ui. _services.GetService<GlamourerWindowSystem>(); // initialize ui.
_services.GetRequiredService<CommandService>(); // initialize commands. _services.GetService<CommandService>(); // initialize commands.
_services.GetRequiredService<GlamourerIpc>(); // initialize IPC. _services.GetService<GlamourerIpc>(); // initialize IPC.
Log.Information($"Glamourer v{Version} loaded successfully."); Log.Information($"Glamourer v{Version} loaded successfully.");
} }
catch catch

View file

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

View file

@ -28,6 +28,8 @@ public enum ColorId
TriStateCheck, TriStateCheck,
TriStateCross, TriStateCross,
TriStateNeutral, TriStateNeutral,
BattleNpc,
EventNpc,
} }
public static class Colors public static class Colors
@ -60,7 +62,9 @@ public static class Colors
ColorId.QuickDesignBg => (0x00F0F0F0, "Quick Design Bar Window Background", "The color of the window background in the quick design bar." ), ColorId.QuickDesignBg => (0x00F0F0F0, "Quick Design Bar Window Background", "The color of the window background in the quick design bar." ),
ColorId.TriStateCheck => (0xFF00D000, "Checkmark in Tri-State Checkboxes", "The color of the checkmark indicating positive change in tri-state checkboxes." ), ColorId.TriStateCheck => (0xFF00D000, "Checkmark in Tri-State Checkboxes", "The color of the checkmark indicating positive change in tri-state checkboxes." ),
ColorId.TriStateCross => (0xFF0000D0, "Cross in Tri-State Checkboxes", "The color of the cross indicating negative change in tri-state checkboxes." ), ColorId.TriStateCross => (0xFF0000D0, "Cross in Tri-State Checkboxes", "The color of the cross indicating negative change in tri-state checkboxes." ),
ColorId.TriStateNeutral => (0xFFD0D0D0, "Dot in Tri-State Checkboxes", "The color of the dot indicating no change in tri-state checkboxes" ), ColorId.TriStateNeutral => (0xFFD0D0D0, "Dot in Tri-State Checkboxes", "The color of the dot indicating no change in tri-state checkboxes." ),
ColorId.BattleNpc => (0xFFFFFFFF, "Battle NPC in NPC Tab", "The color of the names of battle NPCs in the NPC tab that do not have a more specific color assigned." ),
ColorId.EventNpc => (0xFFFFFFFF, "Event NPC in NPC Tab", "The color of the names of event NPCs in the NPC tab that do not have a more specific color assigned." ),
_ => (0x00000000, string.Empty, string.Empty ), _ => (0x00000000, string.Empty, string.Empty ),
// @formatter:on // @formatter:on
}; };

View file

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

View file

@ -1,11 +1,12 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Numerics;
using Dalamud.Interface; using Dalamud.Interface;
using Glamourer.Customization;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Customization; namespace Glamourer.Gui.Customization;
@ -75,4 +76,20 @@ public partial class CustomizationDrawer
if (_lockedRedraw && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) if (_lockedRedraw && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
ImGui.SetTooltip("The race can not be changed as this requires a redraw of the character, which is not supported for this actor."); ImGui.SetTooltip("The race can not be changed as this requires a redraw of the character, which is not supported for this actor.");
} }
private void DrawBodyType()
{
if (_customize.BodyType.Value == 1)
return;
var label = _lockedRedraw
? $"Body Type {_customize.BodyType.Value}"
: $"Reset Body Type {_customize.BodyType.Value} to Default";
if (!ImGuiUtil.DrawDisabledButton(label, new Vector2(_raceSelectorWidth + _framedIconSize.X + ImGui.GetStyle().ItemSpacing.X, 0),
string.Empty, _lockedRedraw))
return;
Changed |= CustomizeFlag.BodyType;
_customize.BodyType = (CustomizeValue)1;
}
} }

View file

@ -1,10 +1,11 @@
using System; using System;
using System.Numerics; using System.Numerics;
using Dalamud.Interface.Utility; using Glamourer.GameData;
using Glamourer.Customization;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Customization; namespace Glamourer.Gui.Customization;
@ -14,7 +15,7 @@ public partial class CustomizationDrawer
private void DrawIconSelector(CustomizeIndex index) private void DrawIconSelector(CustomizeIndex index)
{ {
using var _ = SetId(index); using var id = SetId(index);
using var bigGroup = ImRaii.Group(); using var bigGroup = ImRaii.Group();
var label = _currentOption; var label = _currentOption;
@ -28,8 +29,8 @@ public partial class CustomizationDrawer
npc = true; npc = true;
} }
var icon = _service.AwaitedService.GetIcon(custom!.Value.IconId); var icon = _service.Manager.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)) if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
ImGui.OpenPopup(IconSelectorPopup); ImGui.OpenPopup(IconSelectorPopup);
@ -38,7 +39,7 @@ public partial class CustomizationDrawer
ImGuiUtil.HoverIconTooltip(icon, _iconSize); ImGuiUtil.HoverIconTooltip(icon, _iconSize);
ImGui.SameLine(); ImGui.SameLine();
using (var group = ImRaii.Group()) using (_ = ImRaii.Group())
{ {
DataInputInt(current, npc); DataInputInt(current, npc);
if (_lockedRedraw && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled)) if (_lockedRedraw && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
@ -68,7 +69,7 @@ public partial class CustomizationDrawer
for (var i = 0; i < _currentCount; ++i) for (var i = 0; i < _currentCount; ++i)
{ {
var custom = _set.Data(_currentIndex, i, _customize.Face); var custom = _set.Data(_currentIndex, i, _customize.Face);
var icon = _service.AwaitedService.GetIcon(custom.IconId); var icon = _service.Manager.GetIcon(custom.IconId);
using (var _ = ImRaii.Group()) using (var _ = ImRaii.Group())
{ {
using var frameColor = ImRaii.PushColor(ImGuiCol.Button, Colors.SelectedRed, current == i); using var frameColor = ImRaii.PushColor(ImGuiCol.Button, Colors.SelectedRed, current == i);
@ -119,16 +120,16 @@ public partial class CustomizationDrawer
ImGui.Dummy(new Vector2(ImGui.GetFrameHeight())); ImGui.Dummy(new Vector2(ImGui.GetFrameHeight()));
} }
var oldValue = _customize.Data.At(_currentIndex.ToByteAndMask().ByteIdx); var oldValue = _customize.AtIndex(_currentIndex.ToByteAndMask().ByteIdx);
var tmp = (int)oldValue; var tmp = (int)oldValue.Value;
ImGui.SetNextItemWidth(_inputIntSize); ImGui.SetNextItemWidth(_inputIntSize);
if (ImGui.InputInt("##text", ref tmp, 1, 1)) if (ImGui.InputInt("##text", ref tmp, 1, 1))
{ {
tmp = Math.Clamp(tmp, 0, byte.MaxValue); tmp = Math.Clamp(tmp, 0, byte.MaxValue);
if (tmp != oldValue) if (tmp != oldValue.Value)
{ {
_customize.Data.Set(_currentIndex.ToByteAndMask().ByteIdx, (byte)tmp); _customize.SetByIndex(_currentIndex.ToByteAndMask().ByteIdx, (CustomizeValue)tmp);
var changes = (byte)tmp ^ oldValue; var changes = (byte)tmp ^ oldValue.Value;
Changed |= ((changes & 0x01) == 0x01 ? CustomizeFlag.FacialFeature1 : 0) Changed |= ((changes & 0x01) == 0x01 ? CustomizeFlag.FacialFeature1 : 0)
| ((changes & 0x02) == 0x02 ? CustomizeFlag.FacialFeature2 : 0) | ((changes & 0x02) == 0x02 ? CustomizeFlag.FacialFeature2 : 0)
| ((changes & 0x04) == 0x04 ? CustomizeFlag.FacialFeature3 : 0) | ((changes & 0x04) == 0x04 ? CustomizeFlag.FacialFeature3 : 0)
@ -179,8 +180,8 @@ public partial class CustomizationDrawer
var enabled = _customize.Get(featureIdx) != CustomizeValue.Zero; var enabled = _customize.Get(featureIdx) != CustomizeValue.Zero;
var feature = _set.Data(featureIdx, 0, face); var feature = _set.Data(featureIdx, 0, face);
var icon = featureIdx == CustomizeIndex.LegacyTattoo var icon = featureIdx == CustomizeIndex.LegacyTattoo
? _legacyTattoo ?? _service.AwaitedService.GetIcon(feature.IconId) ? _legacyTattoo ?? _service.Manager.GetIcon(feature.IconId)
: _service.AwaitedService.GetIcon(feature.IconId); : _service.Manager.GetIcon(feature.IconId);
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X, if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X,
Vector4.Zero, enabled ? Vector4.One : _redTint)) Vector4.Zero, enabled ? Vector4.One : _redTint))
{ {

View file

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

View file

@ -4,17 +4,17 @@ using System.Reflection;
using Dalamud.Interface.Internal; using Dalamud.Interface.Internal;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Plugin; using Dalamud.Plugin;
using Glamourer.Customization; using Glamourer.GameData;
using Glamourer.Services; using Glamourer.Services;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using CustomizeData = Penumbra.GameData.Structs.CustomizeData; using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Customization; namespace Glamourer.Gui.Customization;
public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizationService _service, CodeService _codes, Configuration _config) public partial class CustomizationDrawer(DalamudPluginInterface pi, CustomizeService _service, CodeService _codes, Configuration _config)
: IDisposable : IDisposable
{ {
private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f); private readonly Vector4 _redTint = new(0.6f, 0.3f, 0.3f, 1f);
@ -22,13 +22,12 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio
private Exception? _terminate; private Exception? _terminate;
private Customize _customize = Customize.Default; private CustomizeArray _customize = CustomizeArray.Default;
private CustomizationSet _set = null!; private CustomizeSet _set = null!;
public Customize Customize public CustomizeArray Customize
=> _customize; => _customize;
public CustomizeFlag CurrentFlag { get; private set; }
public CustomizeFlag Changed { get; private set; } public CustomizeFlag Changed { get; private set; }
public CustomizeFlag ChangeApply { get; private set; } public CustomizeFlag ChangeApply { get; private set; }
@ -47,18 +46,16 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio
public void Dispose() public void Dispose()
=> _legacyTattoo?.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; _withApply = false;
Init(current, locked, lockedRedraw); Init(current, locked, lockedRedraw);
return DrawInternal(); 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; ChangeApply = apply;
_initialApply = apply; _initialApply = apply;
_withApply = !_config.HideApplyCheckmarks; _withApply = !_config.HideApplyCheckmarks;
@ -66,12 +63,12 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio
return DrawInternal(); return DrawInternal();
} }
private void Init(Customize current, bool locked, bool lockedRedraw) private void Init(CustomizeArray current, bool locked, bool lockedRedraw)
{ {
UpdateSizes(); UpdateSizes();
_terminate = null; _terminate = null;
Changed = 0; Changed = 0;
_customize.Load(current); _customize = current;
_locked = locked; _locked = locked;
_lockedRedraw = lockedRedraw; _lockedRedraw = lockedRedraw;
} }
@ -116,11 +113,13 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio
try try
{ {
if (_codes.EnabledArtisan) if (_codes.Enabled(CodeService.CodeFlag.Artisan))
return DrawArtisan(); return DrawArtisan();
DrawRaceGenderSelector(); DrawRaceGenderSelector();
_set = _service.AwaitedService.GetList(_customize.Clan, _customize.Gender); DrawBodyType();
_set = _service.Manager.GetSet(_customize.Clan, _customize.Gender);
foreach (var id in _set.Order[CharaMakeParams.MenuType.Percentage]) foreach (var id in _set.Order[CharaMakeParams.MenuType.Percentage])
PercentageSelector(id); PercentageSelector(id);
@ -153,23 +152,23 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio
private unsafe bool DrawArtisan() private unsafe bool DrawArtisan()
{ {
for (var i = 0; i < CustomizeData.Size; ++i) for (var i = 0; i < CustomizeArray.Size; ++i)
{ {
using var id = ImRaii.PushId(i); using var id = ImRaii.PushId(i);
int value = _customize.Data.Data[i]; int value = _customize.Data[i];
ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale); ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale);
if (ImGui.InputInt(string.Empty, ref value, 0, 0)) if (ImGui.InputInt(string.Empty, ref value, 0, 0))
{ {
var newValue = (byte)Math.Clamp(value, 0, byte.MaxValue); 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>()) foreach (var flag in Enum.GetValues<CustomizeIndex>())
{ {
var (j, mask) = flag.ToByteAndMask(); var (j, _) = flag.ToByteAndMask();
if (j == i) if (j == i)
Changed |= flag.ToFlag(); 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;
using Dalamud.Interface.Utility.Raii; using Dalamud.Interface.Utility.Raii;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Customization; using Glamourer.GameData;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Services; using Glamourer.Services;
@ -13,6 +13,7 @@ using OtterGui;
using OtterGui.Classes; using OtterGui.Classes;
using OtterGui.Log; using OtterGui.Log;
using OtterGui.Widgets; using OtterGui.Widgets;
using Penumbra.GameData.Enums;
namespace Glamourer.Gui; namespace Glamourer.Gui;
@ -179,7 +180,7 @@ public sealed class RevertDesignCombo : DesignComboBase, IDisposable
private readonly AutoDesignManager _autoDesignManager; private readonly AutoDesignManager _autoDesignManager;
public RevertDesignCombo(DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, DesignColors designColors, public RevertDesignCombo(DesignManager designs, DesignFileSystem fileSystem, TabSelected tabSelected, DesignColors designColors,
ItemManager items, CustomizationService customize, Logger log, DesignChanged designChanged, AutoDesignManager autoDesignManager, ItemManager items, CustomizeService customize, Logger log, DesignChanged designChanged, AutoDesignManager autoDesignManager,
EphemeralConfig config) EphemeralConfig config)
: this(designs, fileSystem, tabSelected, designColors, CreateRevertDesign(customize, items), log, designChanged, autoDesignManager, : this(designs, fileSystem, tabSelected, designColors, CreateRevertDesign(customize, items), log, designChanged, autoDesignManager,
config) config)
@ -209,7 +210,7 @@ public sealed class RevertDesignCombo : DesignComboBase, IDisposable
_autoDesignManager.AddDesign(set, CurrentSelection!.Item1 == RevertDesign ? null : CurrentSelection!.Item1); _autoDesignManager.AddDesign(set, CurrentSelection!.Item1 == RevertDesign ? null : CurrentSelection!.Item1);
} }
private static Design CreateRevertDesign(CustomizationService customize, ItemManager items) private static Design CreateRevertDesign(CustomizeService customize, ItemManager items)
=> new(customize, items) => new(customize, items)
{ {
Index = RevertDesignIndex, Index = RevertDesignIndex,

View file

@ -1,6 +1,7 @@
using System; using System;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Services;
using Glamourer.State; using Glamourer.State;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
@ -28,7 +29,9 @@ public ref struct EquipDrawData(EquipSlot slot, in DesignData designData)
public static EquipDrawData FromDesign(DesignManager manager, Design design, EquipSlot slot) public static EquipDrawData FromDesign(DesignManager manager, Design design, EquipSlot slot)
=> new(slot, design.DesignData) => new(slot, design.DesignData)
{ {
ItemSetter = slot.IsEquipmentPiece() ? i => manager.ChangeEquip(design, slot, i) : i => manager.ChangeWeapon(design, slot, i), ItemSetter = slot.IsEquipment() || slot.IsAccessory()
? i => manager.ChangeEquip(design, slot, i)
: i => manager.ChangeWeapon(design, slot, i),
StainSetter = i => manager.ChangeStain(design, slot, i), StainSetter = i => manager.ChangeStain(design, slot, i),
ApplySetter = b => manager.ChangeApplyEquip(design, slot, b), ApplySetter = b => manager.ChangeApplyEquip(design, slot, b),
ApplyStainSetter = b => manager.ChangeApplyStain(design, slot, b), ApplyStainSetter = b => manager.ChangeApplyStain(design, slot, b),

View file

@ -12,7 +12,7 @@ using Glamourer.Unlocks;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
@ -24,7 +24,7 @@ public class EquipmentDrawer
private readonly ItemManager _items; private readonly ItemManager _items;
private readonly GlamourerColorCombo _stainCombo; private readonly GlamourerColorCombo _stainCombo;
private readonly StainData _stainData; private readonly DictStain _stainData;
private readonly ItemCombo[] _itemCombo; private readonly ItemCombo[] _itemCombo;
private readonly Dictionary<FullEquipType, WeaponCombo> _weaponCombo; private readonly Dictionary<FullEquipType, WeaponCombo> _weaponCombo;
private readonly CodeService _codes; private readonly CodeService _codes;
@ -66,8 +66,8 @@ public class EquipmentDrawer
_iconSize = new Vector2(2 * ImGui.GetFrameHeight() + ImGui.GetStyle().ItemSpacing.Y); _iconSize = new Vector2(2 * ImGui.GetFrameHeight() + ImGui.GetStyle().ItemSpacing.Y);
_comboLength = DefaultWidth * ImGuiHelpers.GlobalScale; _comboLength = DefaultWidth * ImGuiHelpers.GlobalScale;
if (_requiredComboWidthUnscaled == 0) if (_requiredComboWidthUnscaled == 0)
_requiredComboWidthUnscaled = _items.ItemService.AwaitedService.AllItems(true) _requiredComboWidthUnscaled = _items.ItemData.AllItems(true)
.Concat(_items.ItemService.AwaitedService.AllItems(false)) .Concat(_items.ItemData.AllItems(false))
.Max(i => ImGui.CalcTextSize($"{i.Item2.Name} ({i.Item2.ModelString})").X) .Max(i => ImGui.CalcTextSize($"{i.Item2.Name} ({i.Item2.ModelString})").X)
/ ImGuiHelpers.GlobalScale; / ImGuiHelpers.GlobalScale;
@ -94,7 +94,7 @@ public class EquipmentDrawer
if (_config.SmallEquip) if (_config.SmallEquip)
DrawEquipSmall(equipDrawData); DrawEquipSmall(equipDrawData);
else if (!equipDrawData.Locked && _codes.EnabledArtisan) else if (!equipDrawData.Locked && _codes.Enabled(CodeService.CodeFlag.Artisan))
DrawEquipArtisan(equipDrawData); DrawEquipArtisan(equipDrawData);
else else
DrawEquipNormal(equipDrawData); DrawEquipNormal(equipDrawData);
@ -102,7 +102,7 @@ public class EquipmentDrawer
public void DrawWeapons(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons) public void DrawWeapons(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons)
{ {
if (mainhand.CurrentItem.ModelId.Id == 0) if (mainhand.CurrentItem.PrimaryId.Id == 0)
return; return;
if (_config.HideApplyCheckmarks) if (_config.HideApplyCheckmarks)
@ -117,7 +117,7 @@ public class EquipmentDrawer
if (_config.SmallEquip) if (_config.SmallEquip)
DrawWeaponsSmall(mainhand, offhand, allWeapons); DrawWeaponsSmall(mainhand, offhand, allWeapons);
else if (!mainhand.Locked && _codes.EnabledArtisan) else if (!mainhand.Locked && _codes.Enabled(CodeService.CodeFlag.Artisan))
DrawWeaponsArtisan(mainhand, offhand); DrawWeaponsArtisan(mainhand, offhand);
else else
DrawWeaponsNormal(mainhand, offhand, allWeapons); DrawWeaponsNormal(mainhand, offhand, allWeapons);
@ -202,24 +202,24 @@ public class EquipmentDrawer
void DrawWeapon(in EquipDrawData current) void DrawWeapon(in EquipDrawData current)
{ {
int setId = current.CurrentItem.ModelId.Id; int setId = current.CurrentItem.PrimaryId.Id;
int type = current.CurrentItem.WeaponType.Id; int type = current.CurrentItem.SecondaryId.Id;
int variant = current.CurrentItem.Variant.Id; int variant = current.CurrentItem.Variant.Id;
ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale);
if (ImGui.InputInt("##setId", ref setId, 0, 0)) if (ImGui.InputInt("##setId", ref setId, 0, 0))
{ {
var newSetId = (SetId)Math.Clamp(setId, 0, ushort.MaxValue); var newSetId = (PrimaryId)Math.Clamp(setId, 0, ushort.MaxValue);
if (newSetId.Id != current.CurrentItem.ModelId.Id) if (newSetId.Id != current.CurrentItem.PrimaryId.Id)
current.ItemSetter(_items.Identify(current.Slot, newSetId, current.CurrentItem.WeaponType, current.CurrentItem.Variant)); current.ItemSetter(_items.Identify(current.Slot, newSetId, current.CurrentItem.SecondaryId, current.CurrentItem.Variant));
} }
ImGui.SameLine(); ImGui.SameLine();
ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale);
if (ImGui.InputInt("##type", ref type, 0, 0)) if (ImGui.InputInt("##type", ref type, 0, 0))
{ {
var newType = (WeaponType)Math.Clamp(type, 0, ushort.MaxValue); var newType = (SecondaryId)Math.Clamp(type, 0, ushort.MaxValue);
if (newType.Id != current.CurrentItem.WeaponType.Id) if (newType.Id != current.CurrentItem.SecondaryId.Id)
current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.ModelId, newType, current.CurrentItem.Variant)); current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, newType, current.CurrentItem.Variant));
} }
ImGui.SameLine(); ImGui.SameLine();
@ -228,7 +228,8 @@ public class EquipmentDrawer
{ {
var newVariant = (Variant)Math.Clamp(variant, 0, byte.MaxValue); var newVariant = (Variant)Math.Clamp(variant, 0, byte.MaxValue);
if (newVariant.Id != current.CurrentItem.Variant.Id) if (newVariant.Id != current.CurrentItem.Variant.Id)
current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.ModelId, current.CurrentItem.WeaponType, newVariant)); current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, current.CurrentItem.SecondaryId,
newVariant));
} }
} }
} }
@ -249,13 +250,13 @@ public class EquipmentDrawer
/// <summary> Draw an input for armor that can set arbitrary values instead of choosing items. </summary> /// <summary> Draw an input for armor that can set arbitrary values instead of choosing items. </summary>
private void DrawArmorArtisan(EquipDrawData data) private void DrawArmorArtisan(EquipDrawData data)
{ {
int setId = data.CurrentItem.ModelId.Id; int setId = data.CurrentItem.PrimaryId.Id;
int variant = data.CurrentItem.Variant.Id; int variant = data.CurrentItem.Variant.Id;
ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale); ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale);
if (ImGui.InputInt("##setId", ref setId, 0, 0)) if (ImGui.InputInt("##setId", ref setId, 0, 0))
{ {
var newSetId = (SetId)Math.Clamp(setId, 0, ushort.MaxValue); var newSetId = (PrimaryId)Math.Clamp(setId, 0, ushort.MaxValue);
if (newSetId.Id != data.CurrentItem.ModelId.Id) if (newSetId.Id != data.CurrentItem.PrimaryId.Id)
data.ItemSetter(_items.Identify(data.Slot, newSetId, data.CurrentItem.Variant)); data.ItemSetter(_items.Identify(data.Slot, newSetId, data.CurrentItem.Variant));
} }
@ -265,7 +266,7 @@ public class EquipmentDrawer
{ {
var newVariant = (byte)Math.Clamp(variant, 0, byte.MaxValue); var newVariant = (byte)Math.Clamp(variant, 0, byte.MaxValue);
if (newVariant != data.CurrentItem.Variant) if (newVariant != data.CurrentItem.Variant)
data.ItemSetter(_items.Identify(data.Slot, data.CurrentItem.ModelId, newVariant)); data.ItemSetter(_items.Identify(data.Slot, data.CurrentItem.PrimaryId, newVariant));
} }
} }
@ -454,7 +455,7 @@ public class EquipmentDrawer
else if (combo.CustomVariant.Id > 0) else if (combo.CustomVariant.Id > 0)
data.ItemSetter(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant)); data.ItemSetter(_items.Identify(data.Slot, combo.CustomSetId, combo.CustomVariant));
if (!data.Locked && data.CurrentItem.ModelId.Id != 0) if (!data.Locked && data.CurrentItem.PrimaryId.Id != 0)
{ {
if (clear || ImGui.IsItemClicked(ImGuiMouseButton.Right)) if (clear || ImGui.IsItemClicked(ImGuiMouseButton.Right))
data.ItemSetter(ItemManager.NothingItem(data.Slot)); data.ItemSetter(ItemManager.NothingItem(data.Slot));

View file

@ -8,12 +8,12 @@ using Dalamud.Interface.Utility.Raii;
using Glamourer.Unlocks; using Glamourer.Unlocks;
using ImGuiNET; using ImGuiNET;
using OtterGui.Widgets; using OtterGui.Widgets;
using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Equipment; namespace Glamourer.Gui.Equipment;
public sealed class GlamourerColorCombo(float _comboWidth, StainData _stains, FavoriteManager _favorites) public sealed class GlamourerColorCombo(float _comboWidth, DictStain _stains, FavoriteManager _favorites)
: FilterComboColors(_comboWidth, CreateFunc(_stains, _favorites), Glamourer.Log) : FilterComboColors(_comboWidth, CreateFunc(_stains, _favorites), Glamourer.Log)
{ {
protected override bool DrawSelectable(int globalIdx, bool selected) protected override bool DrawSelectable(int globalIdx, bool selected)
@ -40,8 +40,9 @@ public sealed class GlamourerColorCombo(float _comboWidth, StainData _stains, Fa
return base.DrawSelectable(globalIdx, selected); return base.DrawSelectable(globalIdx, selected);
} }
private static Func<IReadOnlyList<KeyValuePair<byte, (string Name, uint Color, bool Gloss)>>> CreateFunc(StainData stains, private static Func<IReadOnlyList<KeyValuePair<byte, (string Name, uint Color, bool Gloss)>>> CreateFunc(DictStain stains,
FavoriteManager favorites) FavoriteManager favorites)
=> () => stains.Data.Select(kvp => (kvp, favorites.Contains((StainId)kvp.Key))).OrderBy(p => !p.Item2).Select(p => p.kvp) => () => stains.Select(kvp => (kvp, favorites.Contains(kvp.Key))).OrderBy(p => !p.Item2).Select(p => p.kvp)
.Prepend(new KeyValuePair<byte, (string Name, uint Dye, bool Gloss)>(0, ("None", 0, false))).ToList(); .Prepend(new KeyValuePair<StainId, Stain>(Stain.None.RowIndex, Stain.None)).Select(kvp
=> new KeyValuePair<byte, (string, uint, bool)>(kvp.Key.Id, (kvp.Value.Name, kvp.Value.RgbaColor, kvp.Value.Gloss))).ToList();
} }

View file

@ -23,8 +23,8 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
private ItemId _currentItem; private ItemId _currentItem;
private float _innerWidth; private float _innerWidth;
public SetId CustomSetId { get; private set; } public PrimaryId CustomSetId { get; private set; }
public Variant CustomVariant { get; private set; } public Variant CustomVariant { get; private set; }
public ItemCombo(IDataManager gameData, ItemManager items, EquipSlot slot, Logger log, FavoriteManager favorites) public ItemCombo(IDataManager gameData, ItemManager items, EquipSlot slot, Logger log, FavoriteManager favorites)
: base(() => GetItems(favorites, items, slot), log) : base(() => GetItems(favorites, items, slot), log)
@ -83,7 +83,7 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
} }
protected override bool IsVisible(int globalIndex, LowerString filter) protected override bool IsVisible(int globalIndex, LowerString filter)
=> base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].ModelId.Id.ToString()); => base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].PrimaryId.Id.ToString());
protected override string ToString(EquipItem obj) protected override string ToString(EquipItem obj)
=> obj.Name; => obj.Name;
@ -111,7 +111,7 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
private static IReadOnlyList<EquipItem> GetItems(FavoriteManager favorites, ItemManager items, EquipSlot slot) private static IReadOnlyList<EquipItem> GetItems(FavoriteManager favorites, ItemManager items, EquipSlot slot)
{ {
var nothing = ItemManager.NothingItem(slot); var nothing = ItemManager.NothingItem(slot);
if (!items.ItemService.AwaitedService.TryGetValue(slot.ToEquipType(), out var list)) if (!items.ItemData.ByType.TryGetValue(slot.ToEquipType(), out var list))
return new[] return new[]
{ {
nothing, nothing,

View file

@ -60,12 +60,12 @@ public sealed class WeaponCombo : FilterComboCache<EquipItem>
var ret = ImGui.Selectable(name, selected); var ret = ImGui.Selectable(name, selected);
ImGui.SameLine(); ImGui.SameLine();
using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080); using var color = ImRaii.PushColor(ImGuiCol.Text, 0xFF808080);
ImGuiUtil.RightAlign($"({obj.ModelId.Id}-{obj.WeaponType.Id}-{obj.Variant})"); ImGuiUtil.RightAlign($"({obj.PrimaryId.Id}-{obj.SecondaryId.Id}-{obj.Variant})");
return ret; return ret;
} }
protected override bool IsVisible(int globalIndex, LowerString filter) protected override bool IsVisible(int globalIndex, LowerString filter)
=> base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].ModelId.Id.ToString()); => base.IsVisible(globalIndex, filter) || filter.IsContained(Items[globalIndex].PrimaryId.Id.ToString());
protected override string ToString(EquipItem obj) protected override string ToString(EquipItem obj)
=> obj.Name; => obj.Name;
@ -80,14 +80,14 @@ public sealed class WeaponCombo : FilterComboCache<EquipItem>
var enumerable = Array.Empty<EquipItem>().AsEnumerable(); var enumerable = Array.Empty<EquipItem>().AsEnumerable();
foreach (var t in Enum.GetValues<FullEquipType>().Where(e => e.ToSlot() is EquipSlot.MainHand)) foreach (var t in Enum.GetValues<FullEquipType>().Where(e => e.ToSlot() is EquipSlot.MainHand))
{ {
if (items.ItemService.AwaitedService.TryGetValue(t, out var l)) if (items.ItemData.ByType.TryGetValue(t, out var l))
enumerable = enumerable.Concat(l); enumerable = enumerable.Concat(l);
} }
return enumerable.OrderBy(e => e.Name).ToList(); return enumerable.OrderBy(e => e.Name).ToList();
} }
if (!items.ItemService.AwaitedService.TryGetValue(type, out var list)) if (!items.ItemData.ByType.TryGetValue(type, out var list))
return Array.Empty<EquipItem>(); return Array.Empty<EquipItem>();
if (type.AllowsNothing()) if (type.AllowsNothing())

View file

@ -1,4 +1,5 @@
using System.Numerics; using System;
using System.Numerics;
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Dalamud.Interface.Windowing; using Dalamud.Interface.Windowing;
@ -61,7 +62,7 @@ public class GenericPopupWindow : Window
private void DrawFestivalPopup() private void DrawFestivalPopup()
{ {
var viewportSize = ImGui.GetWindowViewport().Size; var viewportSize = ImGui.GetWindowViewport().Size;
ImGui.SetNextWindowSize(new Vector2(viewportSize.X / 5, viewportSize.Y / 7)); ImGui.SetNextWindowSize(new Vector2(Math.Max(viewportSize.X / 5, 400), Math.Max(viewportSize.Y / 7, 150)));
ImGui.SetNextWindowPos(viewportSize / 2, ImGuiCond.Always, new Vector2(0.5f)); ImGui.SetNextWindowPos(viewportSize / 2, ImGuiCond.Always, new Vector2(0.5f));
using var popup = ImRaii.Popup("FestivalPopup", ImGuiWindowFlags.Modal); using var popup = ImRaii.Popup("FestivalPopup", ImGuiWindowFlags.Modal);
if (!popup) if (!popup)

View file

@ -10,6 +10,7 @@ using Glamourer.Gui.Tabs.ActorTab;
using Glamourer.Gui.Tabs.AutomationTab; using Glamourer.Gui.Tabs.AutomationTab;
using Glamourer.Gui.Tabs.DebugTab; using Glamourer.Gui.Tabs.DebugTab;
using Glamourer.Gui.Tabs.DesignTab; using Glamourer.Gui.Tabs.DesignTab;
using Glamourer.Gui.Tabs.NpcTab;
using Glamourer.Gui.Tabs.UnlocksTab; using Glamourer.Gui.Tabs.UnlocksTab;
using ImGuiNET; using ImGuiNET;
using OtterGui.Custom; using OtterGui.Custom;
@ -29,6 +30,7 @@ public class MainWindow : Window, IDisposable
Automation = 4, Automation = 4,
Unlocks = 5, Unlocks = 5,
Messages = 6, Messages = 6,
Npcs = 7,
} }
private readonly Configuration _config; private readonly Configuration _config;
@ -42,12 +44,14 @@ public class MainWindow : Window, IDisposable
public readonly DesignTab Designs; public readonly DesignTab Designs;
public readonly AutomationTab Automation; public readonly AutomationTab Automation;
public readonly UnlocksTab Unlocks; public readonly UnlocksTab Unlocks;
public readonly NpcTab Npcs;
public readonly MessagesTab Messages; public readonly MessagesTab Messages;
public TabType SelectTab = TabType.None; public TabType SelectTab = TabType.None;
public MainWindow(DalamudPluginInterface pi, Configuration config, SettingsTab settings, ActorTab actors, DesignTab designs, public MainWindow(DalamudPluginInterface pi, Configuration config, SettingsTab settings, ActorTab actors, DesignTab designs,
DebugTab debugTab, AutomationTab automation, UnlocksTab unlocks, TabSelected @event, MessagesTab messages, DesignQuickBar quickBar) DebugTab debugTab, AutomationTab automation, UnlocksTab unlocks, TabSelected @event, MessagesTab messages, DesignQuickBar quickBar,
NpcTab npcs)
: base(GetLabel()) : base(GetLabel())
{ {
pi.UiBuilder.DisableGposeUiHide = true; pi.UiBuilder.DisableGposeUiHide = true;
@ -65,17 +69,19 @@ public class MainWindow : Window, IDisposable
_event = @event; _event = @event;
Messages = messages; Messages = messages;
_quickBar = quickBar; _quickBar = quickBar;
Npcs = npcs;
_config = config; _config = config;
_tabs = new ITab[] _tabs =
{ [
settings, settings,
actors, actors,
designs, designs,
automation, automation,
unlocks, unlocks,
npcs,
messages, messages,
debugTab, debugTab,
}; ];
_event.Subscribe(OnTabSelected, TabSelected.Priority.MainWindow); _event.Subscribe(OnTabSelected, TabSelected.Priority.MainWindow);
IsOpen = _config.OpenWindowAtStart; IsOpen = _config.OpenWindowAtStart;
} }
@ -117,6 +123,7 @@ public class MainWindow : Window, IDisposable
TabType.Automation => Automation.Label, TabType.Automation => Automation.Label,
TabType.Unlocks => Unlocks.Label, TabType.Unlocks => Unlocks.Label,
TabType.Messages => Messages.Label, TabType.Messages => Messages.Label,
TabType.Npcs => Npcs.Label,
_ => ReadOnlySpan<byte>.Empty, _ => ReadOnlySpan<byte>.Empty,
}; };
@ -128,6 +135,7 @@ public class MainWindow : Window, IDisposable
if (label == Settings.Label) return TabType.Settings; if (label == Settings.Label) return TabType.Settings;
if (label == Automation.Label) return TabType.Automation; if (label == Automation.Label) return TabType.Automation;
if (label == Unlocks.Label) return TabType.Unlocks; if (label == Unlocks.Label) return TabType.Unlocks;
if (label == Npcs.Label) return TabType.Npcs;
if (label == Messages.Label) return TabType.Messages; if (label == Messages.Label) return TabType.Messages;
if (label == Debug.Label) return TabType.Debug; if (label == Debug.Label) return TabType.Debug;
// @formatter:on // @formatter:on

View file

@ -7,7 +7,6 @@ using Glamourer.Interop;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.State; using Glamourer.State;
using Glamourer.Structs;
using ImGuiNET; using ImGuiNET;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
@ -76,7 +75,7 @@ public class PenumbraChangedItemTooltip : IDisposable
case EquipSlot.OffHand when !CanApplyWeapon(EquipSlot.OffHand, item): case EquipSlot.OffHand when !CanApplyWeapon(EquipSlot.OffHand, item):
break; break;
case EquipSlot.RFinger: 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}Right-Click to apply to current actor (Right Finger).");
ImGui.TextUnformatted($"{prefix}Shift + Right-Click to apply to current actor (Left Finger)."); ImGui.TextUnformatted($"{prefix}Shift + Right-Click to apply to current actor (Left Finger).");
@ -92,7 +91,7 @@ public class PenumbraChangedItemTooltip : IDisposable
break; break;
default: default:
using (var tt = !openTooltip ? null : ImRaii.Tooltip()) using (_ = !openTooltip ? null : ImRaii.Tooltip())
{ {
ImGui.TextUnformatted($"{prefix}Right-Click to apply to current actor."); ImGui.TextUnformatted($"{prefix}Right-Click to apply to current actor.");
if (last.Valid) if (last.Valid)
@ -166,7 +165,7 @@ public class PenumbraChangedItemTooltip : IDisposable
{ {
case ChangedItemType.ItemOffhand: case ChangedItemType.ItemOffhand:
case ChangedItemType.Item: case ChangedItemType.Item:
if (!_items.ItemService.AwaitedService.TryGetValue(id, type is ChangedItemType.Item ? EquipSlot.MainHand : EquipSlot.OffHand, out var item)) if (!_items.ItemData.TryGetValue(id, type is ChangedItemType.Item ? EquipSlot.MainHand : EquipSlot.OffHand, out var item))
return; return;
CreateTooltip(item, "[Glamourer] ", false); CreateTooltip(item, "[Glamourer] ", false);
@ -177,7 +176,7 @@ public class PenumbraChangedItemTooltip : IDisposable
private bool CanApplyWeapon(EquipSlot slot, EquipItem item) private bool CanApplyWeapon(EquipSlot slot, EquipItem item)
{ {
var main = _objects.Player.GetMainhand(); var main = _objects.Player.GetMainhand();
var mainItem = _items.Identify(slot, main.Set, main.Type, main.Variant); var mainItem = _items.Identify(slot, main.Skeleton, main.Weapon, main.Variant);
if (slot == EquipSlot.MainHand) if (slot == EquipSlot.MainHand)
return item.Type == mainItem.Type; return item.Type == mainItem.Type;
@ -197,7 +196,7 @@ public class PenumbraChangedItemTooltip : IDisposable
if (!Player(out var state)) if (!Player(out var state))
return; return;
if (!_items.ItemService.AwaitedService.TryGetValue(id, type is ChangedItemType.Item ? EquipSlot.MainHand : EquipSlot.OffHand, out var item)) if (!_items.ItemData.TryGetValue(id, type is ChangedItemType.Item ? EquipSlot.MainHand : EquipSlot.OffHand, out var item))
return; return;
ApplyItem(state, item); ApplyItem(state, item);

View file

@ -6,29 +6,36 @@ using Dalamud.Interface.Internal.Notifications;
using Dalamud.Plugin.Services; using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.Game;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Customization;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Gui.Customization; using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment; using Glamourer.Gui.Equipment;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using Glamourer.Services;
using Glamourer.State; using Glamourer.State;
using Glamourer.Structs;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Classes; using OtterGui.Classes;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
namespace Glamourer.Gui.Tabs.ActorTab; namespace Glamourer.Gui.Tabs.ActorTab;
public class ActorPanel(ActorSelector _selector, StateManager _stateManager, CustomizationDrawer _customizationDrawer, public class ActorPanel(
EquipmentDrawer _equipmentDrawer, IdentifierService _identification, AutoDesignApplier _autoDesignApplier, ActorSelector _selector,
Configuration _config, DesignConverter _converter, ObjectManager _objects, DesignManager _designManager, ImportService _importService, StateManager _stateManager,
ICondition _conditions) CustomizationDrawer _customizationDrawer,
EquipmentDrawer _equipmentDrawer,
AutoDesignApplier _autoDesignApplier,
Configuration _config,
DesignConverter _converter,
ObjectManager _objects,
DesignManager _designManager,
ImportService _importService,
ICondition _conditions,
DictModelChara _modelChara)
{ {
private ActorIdentifier _identifier; private ActorIdentifier _identifier;
private string _actorName = string.Empty; private string _actorName = string.Empty;
@ -154,7 +161,7 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus
} }
var mainhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.MainHand); var mainhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.MainHand);
var offhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.OffHand); var offhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.OffHand);
_equipmentDrawer.DrawWeapons(mainhand, offhand, GameMain.IsInGPose()); _equipmentDrawer.DrawWeapons(mainhand, offhand, GameMain.IsInGPose());
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2)); ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
@ -164,21 +171,21 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus
private void DrawEquipmentMetaToggles() private void DrawEquipmentMetaToggles()
{ {
using (var _ = ImRaii.Group()) using (_ = ImRaii.Group())
{ {
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.HatState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.HatState, _stateManager, _state!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Head, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Head, _stateManager, _state!));
} }
ImGui.SameLine(); ImGui.SameLine();
using (var _ = ImRaii.Group()) using (_ = ImRaii.Group())
{ {
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.VisorState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.VisorState, _stateManager, _state!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Body, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Body, _stateManager, _state!));
} }
ImGui.SameLine(); ImGui.SameLine();
using (var _ = ImRaii.Group()) using (_ = ImRaii.Group())
{ {
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.WeaponState, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.WeaponState, _stateManager, _state!));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.OffHand, _stateManager, _state!)); EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.OffHand, _stateManager, _state!));
@ -187,10 +194,10 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus
private void DrawMonsterPanel() private void DrawMonsterPanel()
{ {
var names = _identification.AwaitedService.ModelCharaNames(_state!.ModelData.ModelId); var names = _modelChara[_state!.ModelData.ModelId];
var turnHuman = ImGui.Button("Turn Human"); var turnHuman = ImGui.Button("Turn Human");
ImGui.Separator(); ImGui.Separator();
using (var box = ImRaii.ListBox("##MonsterList", using (_ = ImRaii.ListBox("##MonsterList",
new Vector2(ImGui.GetContentRegionAvail().X, 10 * ImGui.GetTextLineHeightWithSpacing()))) new Vector2(ImGui.GetContentRegionAvail().X, 10 * ImGui.GetTextLineHeightWithSpacing())))
{ {
if (names.Count == 0) if (names.Count == 0)
@ -202,14 +209,14 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus
ImGui.Separator(); ImGui.Separator();
ImGui.TextUnformatted("Customization Data"); 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.Value:X2}");
ImGui.TextUnformatted($"{b,3}"); ImGui.TextUnformatted($"{b.Value,3}");
} }
ImGui.SameLine(); ImGui.SameLine();
@ -223,11 +230,11 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus
ImGui.Separator(); ImGui.Separator();
ImGui.TextUnformatted("Equipment Data"); ImGui.TextUnformatted("Equipment Data");
using (var font = ImRaii.PushFont(UiBuilder.MonoFont)) using (_ = ImRaii.PushFont(UiBuilder.MonoFont))
{ {
foreach (var b in _state.ModelData.GetEquipmentBytes()) foreach (var b in _state.ModelData.GetEquipmentBytes())
{ {
using (var g = ImRaii.Group()) using (_ = ImRaii.Group())
{ {
ImGui.TextUnformatted($" {b:X2}"); ImGui.TextUnformatted($" {b:X2}");
ImGui.TextUnformatted($"{b,3}"); ImGui.TextUnformatted($"{b,3}");
@ -289,15 +296,15 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus
BorderColor = ColorId.ActorUnavailable.Value(), BorderColor = ColorId.ActorUnavailable.Value(),
}; };
private string _newName = string.Empty; private string _newName = string.Empty;
private DesignBase? _newDesign = null; private DesignBase? _newDesign;
private void SaveDesignOpen() private void SaveDesignOpen()
{ {
ImGui.OpenPopup("Save as Design"); ImGui.OpenPopup("Save as Design");
_newName = _state!.Identifier.ToName(); _newName = _state!.Identifier.ToName();
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags(); var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags();
_newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest); _newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest);
} }
private void SaveDesignDrawPopup() private void SaveDesignDrawPopup()

View file

@ -17,11 +17,11 @@ public class ActorSelector
{ {
private readonly EphemeralConfig _config; private readonly EphemeralConfig _config;
private readonly ObjectManager _objects; private readonly ObjectManager _objects;
private readonly ActorService _actors; private readonly ActorManager _actors;
private ActorIdentifier _identifier = ActorIdentifier.Invalid; private ActorIdentifier _identifier = ActorIdentifier.Invalid;
public ActorSelector(ObjectManager objects, ActorService actors, EphemeralConfig config) public ActorSelector(ObjectManager objects, ActorManager actors, EphemeralConfig config)
{ {
_objects = objects; _objects = objects;
_actors = actors; _actors = actors;
@ -93,7 +93,7 @@ public class ActorSelector
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth
, "Select the local player character.", !_objects.Player, true)) , "Select the local player character.", !_objects.Player, true))
_identifier = _objects.Player.GetIdentifier(_actors.AwaitedService); _identifier = _objects.Player.GetIdentifier(_actors);
ImGui.SameLine(); ImGui.SameLine();
var (id, data) = _objects.TargetData; var (id, data) = _objects.TargetData;

View file

@ -3,23 +3,23 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Utility; using Dalamud.Utility;
using Glamourer.Services;
using ImGuiNET; using ImGuiNET;
using OtterGui.Custom; using OtterGui.Custom;
using OtterGui.Log; using OtterGui.Log;
using OtterGui.Widgets; using OtterGui.Widgets;
using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers;
namespace Glamourer.Gui.Tabs.AutomationTab; namespace Glamourer.Gui.Tabs.AutomationTab;
public sealed class HumanNpcCombo : FilterComboCache<(string Name, ObjectKind Kind, uint[] Ids)> public sealed class HumanNpcCombo(
string label,
DictModelChara modelCharaDict,
DictBNpcNames bNpcNames,
DictBNpc bNpcs,
HumanModelList humans,
Logger log)
: FilterComboCache<(string Name, ObjectKind Kind, uint[] Ids)>(() => CreateList(modelCharaDict, bNpcNames, bNpcs, humans), log)
{ {
private readonly string _label;
public HumanNpcCombo(string label, IdentifierService service, HumanModelList humans, Logger log)
: base(() => CreateList(service, humans), log)
=> _label = label;
protected override string ToString((string Name, ObjectKind Kind, uint[] Ids) obj) protected override string ToString((string Name, ObjectKind Kind, uint[] Ids) obj)
=> obj.Name; => obj.Name;
@ -36,7 +36,8 @@ public sealed class HumanNpcCombo : FilterComboCache<(string Name, ObjectKind Ki
} }
public bool Draw(float width) public bool Draw(float width)
=> Draw(_label, CurrentSelection.Name.IsNullOrEmpty() ? "Human Non-Player-Characters..." : CurrentSelection.Name, string.Empty, width, ImGui.GetTextLineHeightWithSpacing()); => Draw(label, CurrentSelection.Name.IsNullOrEmpty() ? "Human Non-Player-Characters..." : CurrentSelection.Name, string.Empty, width,
ImGui.GetTextLineHeightWithSpacing());
/// <summary> Compare strings in a way that letters and numbers are sorted before any special symbols. </summary> /// <summary> Compare strings in a way that letters and numbers are sorted before any special symbols. </summary>
@ -61,15 +62,16 @@ public sealed class HumanNpcCombo : FilterComboCache<(string Name, ObjectKind Ki
} }
} }
private static IReadOnlyList<(string Name, ObjectKind Kind, uint[] Ids)> CreateList(IdentifierService service, HumanModelList humans) private static IReadOnlyList<(string Name, ObjectKind Kind, uint[] Ids)> CreateList(DictModelChara modelCharaDict, DictBNpcNames bNpcNames,
DictBNpc bNpcs, HumanModelList humans)
{ {
var ret = new List<(string Name, ObjectKind Kind, uint Id)>(1024); var ret = new List<(string Name, ObjectKind Kind, uint Id)>(1024);
for (var modelChara = 0u; modelChara < service.AwaitedService.NumModelChara; ++modelChara) for (var modelChara = 0u; modelChara < modelCharaDict.Count; ++modelChara)
{ {
if (!humans.IsHuman(modelChara)) if (!humans.IsHuman(modelChara))
continue; continue;
var list = service.AwaitedService.ModelCharaNames(modelChara); var list = modelCharaDict[modelChara];
if (list.Count == 0) if (list.Count == 0)
continue; continue;
@ -78,8 +80,8 @@ public sealed class HumanNpcCombo : FilterComboCache<(string Name, ObjectKind Ki
switch (kind) switch (kind)
{ {
case ObjectKind.BattleNpc: case ObjectKind.BattleNpc:
var nameIds = service.AwaitedService.GetBnpcNames(id); var nameIds = bNpcNames[id];
ret.AddRange(nameIds.Select(nameId => (service.AwaitedService.Name(ObjectKind.BattleNpc, nameId), kind, nameId.Id))); ret.AddRange(nameIds.Select(nameId => (bNpcs[nameId], kind, nameId.Id)));
break; break;
case ObjectKind.EventNpc: case ObjectKind.EventNpc:
ret.Add((name, kind, id)); ret.Add((name, kind, id));

View file

@ -1,9 +1,9 @@
using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.ClientState.Objects.Enums;
using Glamourer.Services;
using ImGuiNET; using ImGuiNET;
using OtterGui.Custom;
using Penumbra.GameData.Actors; using Penumbra.GameData.Actors;
using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Gui;
using Penumbra.GameData.Structs;
using Penumbra.String; using Penumbra.String;
namespace Glamourer.Gui.Tabs.AutomationTab; namespace Glamourer.Gui.Tabs.AutomationTab;
@ -12,7 +12,7 @@ public class IdentifierDrawer
{ {
private readonly WorldCombo _worldCombo; private readonly WorldCombo _worldCombo;
private readonly HumanNpcCombo _humanNpcCombo; private readonly HumanNpcCombo _humanNpcCombo;
private readonly ActorService _actors; private readonly ActorManager _actors;
private string _characterName = string.Empty; private string _characterName = string.Empty;
@ -21,11 +21,12 @@ public class IdentifierDrawer
public ActorIdentifier RetainerIdentifier { get; private set; } = ActorIdentifier.Invalid; public ActorIdentifier RetainerIdentifier { get; private set; } = ActorIdentifier.Invalid;
public ActorIdentifier MannequinIdentifier { get; private set; } = ActorIdentifier.Invalid; public ActorIdentifier MannequinIdentifier { get; private set; } = ActorIdentifier.Invalid;
public IdentifierDrawer(ActorService actors, IdentifierService identifier, HumanModelList humans) public IdentifierDrawer(ActorManager actors, DictWorld dictWorld, DictModelChara dictModelChara, DictBNpcNames bNpcNames, DictBNpc bNpc,
HumanModelList humans)
{ {
_actors = actors; _actors = actors;
_worldCombo = new WorldCombo(actors.AwaitedService.Data.Worlds, Glamourer.Log); _worldCombo = new WorldCombo(dictWorld, Glamourer.Log);
_humanNpcCombo = new HumanNpcCombo("##npcs", identifier, humans, Glamourer.Log); _humanNpcCombo = new HumanNpcCombo("##npcs", dictModelChara, bNpcNames, bNpc, humans, Glamourer.Log);
} }
public void DrawName(float width) public void DrawName(float width)
@ -63,13 +64,13 @@ public class IdentifierDrawer
{ {
if (ByteString.FromString(_characterName, out var byteName)) if (ByteString.FromString(_characterName, out var byteName))
{ {
PlayerIdentifier = _actors.AwaitedService.CreatePlayer(byteName, _worldCombo.CurrentSelection.Key); PlayerIdentifier = _actors.CreatePlayer(byteName, _worldCombo.CurrentSelection.Key);
RetainerIdentifier = _actors.AwaitedService.CreateRetainer(byteName, ActorIdentifier.RetainerType.Bell); RetainerIdentifier = _actors.CreateRetainer(byteName, ActorIdentifier.RetainerType.Bell);
MannequinIdentifier = _actors.AwaitedService.CreateRetainer(byteName, ActorIdentifier.RetainerType.Mannequin); MannequinIdentifier = _actors.CreateRetainer(byteName, ActorIdentifier.RetainerType.Mannequin);
} }
NpcIdentifier = _humanNpcCombo.CurrentSelection.Kind is ObjectKind.EventNpc or ObjectKind.BattleNpc NpcIdentifier = _humanNpcCombo.CurrentSelection.Kind is ObjectKind.EventNpc or ObjectKind.BattleNpc
? _actors.AwaitedService.CreateNpc(_humanNpcCombo.CurrentSelection.Kind, _humanNpcCombo.CurrentSelection.Ids[0]) ? _actors.CreateNpc(_humanNpcCombo.CurrentSelection.Kind, _humanNpcCombo.CurrentSelection.Ids[0])
: ActorIdentifier.Invalid; : ActorIdentifier.Invalid;
} }
} }

View file

@ -6,10 +6,8 @@ using System.Text;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Customization;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.Structs;
using Glamourer.Unlocks; using Glamourer.Unlocks;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
@ -17,44 +15,29 @@ using OtterGui.Log;
using OtterGui.Raii; using OtterGui.Raii;
using OtterGui.Widgets; using OtterGui.Widgets;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
using Action = System.Action; using Action = System.Action;
using CustomizeIndex = Glamourer.Customization.CustomizeIndex;
namespace Glamourer.Gui.Tabs.AutomationTab; namespace Glamourer.Gui.Tabs.AutomationTab;
public class SetPanel public class SetPanel(
SetSelector _selector,
AutoDesignManager _manager,
JobService _jobs,
ItemUnlockManager _itemUnlocks,
RevertDesignCombo _designCombo,
CustomizeUnlockManager _customizeUnlocks,
CustomizeService _customizations,
IdentifierDrawer _identifierDrawer,
Configuration _config)
{ {
private readonly AutoDesignManager _manager; private readonly JobGroupCombo _jobGroupCombo = new(_manager, _jobs, Glamourer.Log);
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 string? _tempName; private string? _tempName;
private int _dragIndex = -1; private int _dragIndex = -1;
private Action? _endAction; 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 private AutoDesignSet Selection
=> _selector.Selection!; => _selector.Selection!;
@ -77,7 +60,7 @@ public class SetPanel
var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y }; 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; var enabled = Selection.Enabled;
if (ImGui.Checkbox("##Enabled", ref enabled)) if (ImGui.Checkbox("##Enabled", ref enabled))
@ -87,7 +70,7 @@ public class SetPanel
} }
ImGui.SameLine(); 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; var useGame = _selector.Selection!.BaseState is AutoDesignSet.Base.Game;
if (ImGui.Checkbox("##gameState", ref useGame)) if (ImGui.Checkbox("##gameState", ref useGame))
@ -98,7 +81,7 @@ public class SetPanel
} }
ImGui.SameLine(); ImGui.SameLine();
using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing)) using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
{ {
var editing = _config.ShowAutomationSetEditing; var editing = _config.ShowAutomationSetEditing;
if (ImGui.Checkbox("##Show Editing", ref editing)) if (ImGui.Checkbox("##Show Editing", ref editing))
@ -230,7 +213,7 @@ public class SetPanel
if (_config.ShowUnlockedItemWarnings) if (_config.ShowUnlockedItemWarnings)
{ {
ImGui.TableNextColumn(); 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) if (design.Revert)
return; return;
@ -301,27 +284,6 @@ public class SetPanel
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(2 * ImGuiHelpers.GlobalScale, 0)); 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 var tt = _config.UnlockedItemMode
? "\nThese items will be skipped when applied automatically.\n\nTo change this, disable the Obtained Item Mode setting." ? "\nThese items will be skipped when applied automatically.\n\nTo change this, disable the Obtained Item Mode setting."
: string.Empty; : string.Empty;
@ -333,7 +295,7 @@ public class SetPanel
if (!design.Design.DesignData.IsHuman) if (!design.Design.DesignData.IsHuman)
sb.AppendLine("The base model id can not be changed automatically to something non-human."); sb.AppendLine("The base model id can not be changed automatically to something non-human.");
var set = _customizations.AwaitedService.GetList(customize.Clan, customize.Gender); var set = _customizations.Manager.GetSet(customize.Clan, customize.Gender);
foreach (var type in CustomizationExtensions.All) foreach (var type in CustomizationExtensions.All)
{ {
var flag = type.ToFlag(); var flag = type.ToFlag();
@ -355,6 +317,27 @@ public class SetPanel
: string.Empty; : string.Empty;
DrawWarning(sb2, _config.UnlockedItemMode ? 0xA03030F0 : 0x0, size, tt, "All customizations to be applied are unlocked."); DrawWarning(sb2, _config.UnlockedItemMode ? 0xA03030F0 : 0x0, size, tt, "All customizations to be applied are unlocked.");
ImGui.SameLine(); 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) private void DrawDragDrop(AutoDesignSet set, int index)
@ -394,7 +377,7 @@ public class SetPanel
var newType = design.ApplicationType; var newType = design.ApplicationType;
var newTypeInt = (uint)newType; var newTypeInt = (uint)newType;
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale); 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)) if (ImGui.CheckboxFlags("##all", ref newTypeInt, (uint)AutoDesign.Type.All))
newType = (AutoDesign.Type)newTypeInt; newType = (AutoDesign.Type)newTypeInt;

View file

@ -7,11 +7,11 @@ using Dalamud.Interface.Utility;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.Services;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Classes; using OtterGui.Classes;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Actors;
using Penumbra.String; using Penumbra.String;
using ImGuiClip = OtterGui.ImGuiClip; using ImGuiClip = OtterGui.ImGuiClip;
@ -22,9 +22,9 @@ public class SetSelector : IDisposable
private readonly Configuration _config; private readonly Configuration _config;
private readonly AutoDesignManager _manager; private readonly AutoDesignManager _manager;
private readonly AutomationChanged _event; private readonly AutomationChanged _event;
private readonly ActorService _actors; private readonly ActorManager _actors;
private readonly ObjectManager _objects; private readonly ObjectManager _objects;
private readonly List<(AutoDesignSet, int)> _list = new(); private readonly List<(AutoDesignSet, int)> _list = [];
public AutoDesignSet? Selection { get; private set; } public AutoDesignSet? Selection { get; private set; }
public int SelectionIndex { get; private set; } = -1; public int SelectionIndex { get; private set; } = -1;
@ -44,7 +44,7 @@ public class SetSelector : IDisposable
internal int _dragDesignIndex = -1; internal int _dragDesignIndex = -1;
public SetSelector(AutoDesignManager manager, AutomationChanged @event, Configuration config, ActorService actors, ObjectManager objects) public SetSelector(AutoDesignManager manager, AutomationChanged @event, Configuration config, ActorManager actors, ObjectManager objects)
{ {
_manager = manager; _manager = manager;
_event = @event; _event = @event;
@ -289,9 +289,9 @@ public class SetSelector : IDisposable
private void NewSetButton(Vector2 size) private void NewSetButton(Vector2 size)
{ {
var id = _actors.AwaitedService.GetCurrentPlayer(); var id = _actors.GetCurrentPlayer();
if (!id.IsValid) if (!id.IsValid)
id = _actors.AwaitedService.CreatePlayer(ByteString.FromSpanUnsafe("New Design"u8, true, false, true), ushort.MaxValue); id = _actors.CreatePlayer(ByteString.FromSpanUnsafe("New Design"u8, true, false, true), ushort.MaxValue);
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), size, if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Plus.ToIconString(), size,
$"Create a new Automatic Design Set for {id}. The associated player can be changed later.", !id.IsValid, true)) $"Create a new Automatic Design Set for {id}. The associated player can be changed later.", !id.IsValid, true))
_manager.AddDesignSet("New Design", id); _manager.AddDesignSet("New Design", id);

View file

@ -2,7 +2,7 @@
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using Dalamud.Interface; using Dalamud.Interface;
using Glamourer.Customization; using Glamourer.GameData;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Interop; using Glamourer.Interop;
@ -12,10 +12,11 @@ using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Gui.Debug;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IDebugTabTree public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IGameDataDrawer
{ {
public string Label public string Label
=> $"Active Actors ({_stateManager.Count})###Active Actors"; => $"Active Actors ({_stateManager.Count})###Active Actors";
@ -69,7 +70,7 @@ public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectM
static string ItemString(in DesignData data, EquipSlot slot) static string ItemString(in DesignData data, EquipSlot slot)
{ {
var item = data.Item(slot); var item = data.Item(slot);
return $"{item.Name} ({item.ModelId.Id}{(item.WeaponType != 0 ? $"-{item.WeaponType.Id}" : string.Empty)}-{item.Variant})"; return $"{item.Name} ({item.PrimaryId.Id}{(item.SecondaryId != 0 ? $"-{item.SecondaryId.Id}" : string.Empty)}-{item.Variant})";
} }
PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state[ActorState.MetaIndex.ModelId]); PrintRow("Model ID", state.BaseData.ModelId, state.ModelData.ModelId, state[ActorState.MetaIndex.ModelId]);

View file

@ -1,73 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Interface.Utility;
using Glamourer.Services;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using ImGuiClip = OtterGui.ImGuiClip;
namespace Glamourer.Gui.Tabs.DebugTab;
public class ActorServicePanel(ActorService _actors, ItemManager _items) : IDebugTabTree
{
public string Label
=> "Actor Service";
public bool Disabled
=> !_actors.Valid;
private string _bnpcFilter = string.Empty;
private string _enpcFilter = string.Empty;
private string _companionFilter = string.Empty;
private string _mountFilter = string.Empty;
private string _ornamentFilter = string.Empty;
private string _worldFilter = string.Empty;
public void Draw()
{
DrawBnpcTable();
DebugTab.DrawNameTable("ENPCs", ref _enpcFilter, _actors.AwaitedService.Data.ENpcs.Select(kvp => (kvp.Key, kvp.Value)));
DebugTab.DrawNameTable("Companions", ref _companionFilter, _actors.AwaitedService.Data.Companions.Select(kvp => (kvp.Key, kvp.Value)));
DebugTab.DrawNameTable("Mounts", ref _mountFilter, _actors.AwaitedService.Data.Mounts.Select(kvp => (kvp.Key, kvp.Value)));
DebugTab.DrawNameTable("Ornaments", ref _ornamentFilter, _actors.AwaitedService.Data.Ornaments.Select(kvp => (kvp.Key, kvp.Value)));
DebugTab.DrawNameTable("Worlds", ref _worldFilter, _actors.AwaitedService.Data.Worlds.Select(kvp => ((uint)kvp.Key, kvp.Value)));
}
private void DrawBnpcTable()
{
using var _ = ImRaii.PushId(1);
using var tree = ImRaii.TreeNode("BNPCs");
if (!tree)
return;
var resetScroll = ImGui.InputTextWithHint("##filter", "Filter...", ref _bnpcFilter, 256);
var height = ImGui.GetTextLineHeightWithSpacing() + 2 * ImGui.GetStyle().CellPadding.Y;
using var table = ImRaii.Table("##table", 3, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter,
new Vector2(-1, 10 * height));
if (!table)
return;
if (resetScroll)
ImGui.SetScrollY(0);
ImGui.TableSetupColumn("1", ImGuiTableColumnFlags.WidthFixed, 50 * ImGuiHelpers.GlobalScale);
ImGui.TableSetupColumn("2", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableSetupColumn("3", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableNextColumn();
var skips = ImGuiClip.GetNecessarySkips(height);
ImGui.TableNextRow();
var data = _actors.AwaitedService.Data.BNpcs.Select(kvp => (kvp.Key, kvp.Key.ToString("D5"), kvp.Value));
var remainder = ImGuiClip.FilteredClippedDraw(data, skips,
p => p.Item2.Contains(_bnpcFilter) || p.Item3.Contains(_bnpcFilter, StringComparison.OrdinalIgnoreCase),
p =>
{
ImGuiUtil.DrawTableColumn(p.Item2);
ImGuiUtil.DrawTableColumn(p.Item3);
var bnpcs = _items.IdentifierService.AwaitedService.GetBnpcsFromName(p.Item1);
ImGuiUtil.DrawTableColumn(string.Join(", ", bnpcs.Select(b => b.Id.ToString())));
});
ImGuiClip.DrawEndDummy(remainder, height);
}
}

View file

@ -2,10 +2,11 @@
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Gui.Debug;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
public class AutoDesignPanel(AutoDesignManager _autoDesignManager) : IDebugTabTree public class AutoDesignPanel(AutoDesignManager _autoDesignManager) : IGameDataDrawer
{ {
public string Label public string Label
=> "Auto Designs"; => "Auto Designs";

View file

@ -1,36 +1,35 @@
using System; using System;
using Glamourer.Customization; using Glamourer.GameData;
using Glamourer.Services; using Glamourer.Services;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Gui.Debug;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
public class CustomizationServicePanel(CustomizationService _customization) : IDebugTabTree public class CustomizationServicePanel(CustomizeService customize) : IGameDataDrawer
{ {
public string Label public string Label
=> "Customization Service"; => "Customization Service";
public bool Disabled public bool Disabled
=> !_customization.Valid; => !customize.Finished;
public void Draw() public void Draw()
{ {
foreach (var clan in _customization.AwaitedService.Clans) foreach (var (clan, gender) in CustomizeManager.AllSets())
{ {
foreach (var gender in _customization.AwaitedService.Genders) var set = customize.Manager.GetSet(clan, gender);
{ DrawCustomizationInfo(set);
var set = _customization.AwaitedService.GetList(clan, gender); DrawNpcCustomizationInfo(set);
DrawCustomizationInfo(set);
DrawNpcCustomizationInfo(set);
}
} }
} }
private void DrawCustomizationInfo(CustomizationSet set) private void DrawCustomizationInfo(CustomizeSet set)
{ {
using var tree = ImRaii.TreeNode($"{_customization.ClanName(set.Clan, set.Gender)} {set.Gender}"); using var tree = ImRaii.TreeNode($"{customize.ClanName(set.Clan, set.Gender)} {set.Gender}");
if (!tree) if (!tree)
return; return;
@ -48,9 +47,9 @@ public class CustomizationServicePanel(CustomizationService _customization) : ID
} }
} }
private void DrawNpcCustomizationInfo(CustomizationSet set) private void DrawNpcCustomizationInfo(CustomizeSet set)
{ {
using var tree = ImRaii.TreeNode($"{_customization.ClanName(set.Clan, set.Gender)} {set.Gender} (NPC Options)"); using var tree = ImRaii.TreeNode($"{customize.ClanName(set.Clan, set.Gender)} {set.Gender} (NPC Options)");
if (!tree) if (!tree)
return; return;

View file

@ -1,14 +1,15 @@
using System; using System;
using System.Numerics; using System.Numerics;
using Glamourer.Customization;
using Glamourer.Unlocks; using Glamourer.Unlocks;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Gui.Debug;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
public class CustomizationUnlockPanel(CustomizeUnlockManager _customizeUnlocks) : IDebugTabTree public class CustomizationUnlockPanel(CustomizeUnlockManager _customizeUnlocks) : IGameDataDrawer
{ {
public string Label public string Label
=> "Customizations"; => "Customizations";

View file

@ -1,14 +1,14 @@
using System.IO; using System.IO;
using System.Numerics; using System.Numerics;
using Glamourer.Customization;
using Glamourer.Interop; using Glamourer.Interop;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using Penumbra.GameData.Files;
using Penumbra.GameData.Gui.Debug;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
public class DatFilePanel(ImportService _importService) : IDebugTabTree public class DatFilePanel(ImportService _importService) : IGameDataDrawer
{ {
public string Label public string Label
=> "Character Dat File"; => "Character Dat File";
@ -35,7 +35,7 @@ public class DatFilePanel(ImportService _importService) : IDebugTabTree
ImGui.TextUnformatted(_datFile.Value.Version.ToString()); ImGui.TextUnformatted(_datFile.Value.Version.ToString());
ImGui.TextUnformatted(_datFile.Value.Time.LocalDateTime.ToString("g")); ImGui.TextUnformatted(_datFile.Value.Time.LocalDateTime.ToString("g"));
ImGui.TextUnformatted(_datFile.Value.Voice.ToString()); ImGui.TextUnformatted(_datFile.Value.Voice.ToString());
ImGui.TextUnformatted(_datFile.Value.Customize.Data.ToString()); ImGui.TextUnformatted(_datFile.Value.Customize.ToString());
ImGui.TextUnformatted(_datFile.Value.Description); ImGui.TextUnformatted(_datFile.Value.Description);
} }
} }

View file

@ -1,20 +1,14 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Interface.Utility;
using ImGuiNET; using ImGuiNET;
using Microsoft.Extensions.DependencyInjection;
using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using OtterGui.Services;
using OtterGui.Widgets; using OtterGui.Widgets;
using ImGuiClip = Dalamud.Interface.Utility.ImGuiClip;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
public unsafe class DebugTab(IServiceProvider _provider) : ITab public unsafe class DebugTab(ServiceManager manager) : ITab
{ {
private readonly Configuration _config = _provider.GetRequiredService<Configuration>(); private readonly Configuration _config = manager.GetService<Configuration>();
public bool IsVisible public bool IsVisible
=> _config.DebugMode; => _config.DebugMode;
@ -24,11 +18,11 @@ public unsafe class DebugTab(IServiceProvider _provider) : ITab
private readonly DebugTabHeader[] _headers = private readonly DebugTabHeader[] _headers =
[ [
DebugTabHeader.CreateInterop(_provider), DebugTabHeader.CreateInterop(manager.Provider!),
DebugTabHeader.CreateGameData(_provider), DebugTabHeader.CreateGameData(manager.Provider!),
DebugTabHeader.CreateDesigns(_provider), DebugTabHeader.CreateDesigns(manager.Provider!),
DebugTabHeader.CreateState(_provider), DebugTabHeader.CreateState(manager.Provider!),
DebugTabHeader.CreateUnlocks(_provider), DebugTabHeader.CreateUnlocks(manager.Provider!),
]; ];
public void DrawContent() public void DrawContent()
@ -37,55 +31,12 @@ public unsafe class DebugTab(IServiceProvider _provider) : ITab
if (!child) if (!child)
return; return;
if (ImGui.CollapsingHeader("General"))
{
manager.Timers.Draw("Timers");
}
foreach (var header in _headers) foreach (var header in _headers)
header.Draw(); header.Draw();
} }
public static void DrawInputModelSet(bool withWeapon, ref int setId, ref int secondaryId, ref int variant)
{
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
ImGui.InputInt("##SetId", ref setId, 0, 0);
if (withWeapon)
{
ImGui.SameLine();
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
ImGui.InputInt("##TypeId", ref secondaryId, 0, 0);
}
ImGui.SameLine();
ImGui.SetNextItemWidth(100 * ImGuiHelpers.GlobalScale);
ImGui.InputInt("##Variant", ref variant, 0, 0);
}
public static void DrawNameTable(string label, ref string filter, IEnumerable<(uint, string)> names)
{
using var _ = ImRaii.PushId(label);
using var tree = ImRaii.TreeNode(label);
if (!tree)
return;
var resetScroll = ImGui.InputTextWithHint("##filter", "Filter...", ref filter, 256);
var height = ImGui.GetTextLineHeightWithSpacing() + 2 * ImGui.GetStyle().CellPadding.Y;
using var table = ImRaii.Table("##table", 2, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter,
new Vector2(-1, 10 * height));
if (!table)
return;
if (resetScroll)
ImGui.SetScrollY(0);
ImGui.TableSetupColumn("1", ImGuiTableColumnFlags.WidthFixed, 50 * ImGuiHelpers.GlobalScale);
ImGui.TableSetupColumn("2", ImGuiTableColumnFlags.WidthStretch);
ImGui.TableNextColumn();
var skips = ImGuiClip.GetNecessarySkips(height);
ImGui.TableNextColumn();
var f = filter;
var remainder = ImGuiClip.FilteredClippedDraw(names.Select(p => (p.Item1.ToString("D5"), p.Item2)), skips,
p => p.Item1.Contains(f) || p.Item2.Contains(f, StringComparison.OrdinalIgnoreCase),
p =>
{
ImGuiUtil.DrawTableColumn(p.Item1);
ImGuiUtil.DrawTableColumn(p.Item2);
});
ImGuiClip.DrawEndDummy(remainder, height);
}
} }

View file

@ -3,21 +3,14 @@ using System.Collections.Generic;
using ImGuiNET; using ImGuiNET;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Gui.Debug;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
public interface IDebugTabTree public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees)
{ {
public string Label { get; } public string Label { get; } = label;
public void Draw(); public IReadOnlyList<IGameDataDrawer> SubTrees { get; } = subTrees;
public bool Disabled { get; }
}
public class DebugTabHeader(string label, params IDebugTabTree[] subTrees)
{
public string Label { get; } = label;
public IReadOnlyList<IDebugTabTree> SubTrees { get; } = subTrees;
public void Draw() public void Draw()
{ {
@ -26,14 +19,13 @@ public class DebugTabHeader(string label, params IDebugTabTree[] subTrees)
foreach (var subTree in SubTrees) foreach (var subTree in SubTrees)
{ {
using (var disabled = ImRaii.Disabled(subTree.Disabled)) using var disabled = ImRaii.Disabled(subTree.Disabled);
using var tree = ImRaii.TreeNode(subTree.Label);
if (tree)
{ {
using var tree = ImRaii.TreeNode(subTree.Label); disabled.Dispose();
if (!tree) subTree.Draw();
continue;
} }
subTree.Draw();
} }
} }
@ -52,13 +44,15 @@ public class DebugTabHeader(string label, params IDebugTabTree[] subTrees)
=> new => new
( (
"Game Data", "Game Data",
provider.GetRequiredService<IdentifierPanel>(), provider.GetRequiredService<DataServiceDiagnosticsDrawer>(),
provider.GetRequiredService<RestrictedGearPanel>(), provider.GetRequiredService<IdentificationDrawer>(),
provider.GetRequiredService<ActorServicePanel>(), provider.GetRequiredService<RestrictedGearDrawer>(),
provider.GetRequiredService<ItemManagerPanel>(), provider.GetRequiredService<ActorDataDrawer>(),
provider.GetRequiredService<StainPanel>(), provider.GetRequiredService<ItemDataDrawer>(),
provider.GetRequiredService<DictStainDrawer>(),
provider.GetRequiredService<CustomizationServicePanel>(), provider.GetRequiredService<CustomizationServicePanel>(),
provider.GetRequiredService<JobPanel>(), provider.GetRequiredService<DictJobDrawer>(),
provider.GetRequiredService<DictJobGroupDrawer>(),
provider.GetRequiredService<NpcAppearancePanel>() provider.GetRequiredService<NpcAppearancePanel>()
); );

View file

@ -8,10 +8,11 @@ using ImGuiNET;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Gui.Debug;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
public class DesignConverterPanel(DesignConverter _designConverter) : IDebugTabTree public class DesignConverterPanel(DesignConverter _designConverter) : IGameDataDrawer
{ {
public string Label public string Label
=> "Design Converter"; => "Design Converter";

View file

@ -1,17 +1,16 @@
using System; using System;
using System.Linq; using System.Linq;
using Dalamud.Interface; using Dalamud.Interface;
using Glamourer.Customization;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Structs;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Gui.Debug;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _designFileSystem) : IDebugTabTree public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _designFileSystem) : IGameDataDrawer
{ {
public string Label public string Label
=> $"Design Manager ({_designManager.Designs.Count} Designs)###Design Manager"; => $"Design Manager ({_designManager.Designs.Count} Designs)###Design Manager";

View file

@ -1,19 +1,18 @@
using System; using System;
using System.Linq; using System.Linq;
using Dalamud.Interface; using Dalamud.Interface;
using Glamourer.Customization;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.Structs;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Data; using Penumbra.GameData.DataContainers;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Gui.Debug;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDebugTabTree public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IGameDataDrawer
{ {
public string Label public string Label
=> "Base64 Design Tester"; => "Base64 Design Tester";
@ -85,7 +84,7 @@ public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDe
DrawDesignData(_parse64); DrawDesignData(_parse64);
using var font = ImRaii.PushFont(UiBuilder.MonoFont); using var font = ImRaii.PushFont(UiBuilder.MonoFont);
ImGui.TextUnformatted(_base64); 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)) foreach (var (c1, c2) in _restore.Zip(_base64))
{ {
@ -99,7 +98,7 @@ public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDe
foreach (var ((b1, b2), idx) in _base64Bytes.Zip(_restoreBytes).WithIndex()) 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(idx.ToString("D2"));
ImGui.TextUnformatted(b1.ToString("X2")); ImGui.TextUnformatted(b1.ToString("X2"));
@ -121,7 +120,7 @@ public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDe
using var font = ImRaii.PushFont(UiBuilder.MonoFont); using var font = ImRaii.PushFont(UiBuilder.MonoFont);
foreach (var (b, idx) in _base64Bytes.WithIndex()) foreach (var (b, idx) in _base64Bytes.WithIndex())
{ {
using (var group = ImRaii.Group()) using (_ = ImRaii.Group())
{ {
ImGui.TextUnformatted(idx.ToString("D2")); ImGui.TextUnformatted(idx.ToString("D2"));
ImGui.TextUnformatted(b.ToString("X2")); ImGui.TextUnformatted(b.ToString("X2"));

View file

@ -1,9 +1,10 @@
using Glamourer.State; using Glamourer.State;
using ImGuiNET; using ImGuiNET;
using Penumbra.GameData.Gui.Debug;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
public class FunPanel(FunModule _funModule, Configuration _config) : IDebugTabTree public class FunPanel(FunModule _funModule, Configuration _config) : IGameDataDrawer
{ {
public string Label public string Label
=> "Fun Module"; => "Fun Module";

View file

@ -1,61 +0,0 @@
using System.Linq;
using Dalamud.Interface.Utility;
using Glamourer.Services;
using ImGuiNET;
using OtterGui;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Tabs.DebugTab;
public class IdentifierPanel(ItemManager _items) : IDebugTabTree
{
public string Label
=> "Identifier Service";
public bool Disabled
=> !_items.IdentifierService.Valid;
private string _gamePath = string.Empty;
private int _setId;
private int _secondaryId;
private int _variant;
public void Draw()
{
static void Text(string text)
{
if (text.Length > 0)
ImGui.TextUnformatted(text);
}
ImGui.TextUnformatted("Parse Game Path");
ImGui.SameLine();
ImGui.SetNextItemWidth(300 * ImGuiHelpers.GlobalScale);
ImGui.InputTextWithHint("##gamePath", "Enter game path...", ref _gamePath, 256);
var fileInfo = _items.IdentifierService.AwaitedService.GamePathParser.GetFileInfo(_gamePath);
ImGui.TextUnformatted(
$"{fileInfo.ObjectType} {fileInfo.EquipSlot} {fileInfo.PrimaryId} {fileInfo.SecondaryId} {fileInfo.Variant} {fileInfo.BodySlot} {fileInfo.CustomizationType}");
Text(string.Join("\n", _items.IdentifierService.AwaitedService.Identify(_gamePath).Keys));
ImGui.Separator();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted("Identify Model");
ImGui.SameLine();
DebugTab.DrawInputModelSet(true, ref _setId, ref _secondaryId, ref _variant);
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
var identified = _items.Identify(slot, (SetId)_setId, (Variant)_variant);
Text(identified.Name);
ImGuiUtil.HoverTooltip(string.Join("\n",
_items.IdentifierService.AwaitedService.Identify((SetId)_setId, (Variant)_variant, slot)
.Select(i => $"{i.Name} {i.Id} {i.ItemId} {i.IconId}")));
}
var weapon = _items.Identify(EquipSlot.MainHand, (SetId)_setId, (WeaponType)_secondaryId, (Variant)_variant);
Text(weapon.Name);
ImGuiUtil.HoverTooltip(string.Join("\n",
_items.IdentifierService.AwaitedService.Identify((SetId)_setId, (WeaponType)_secondaryId, (Variant)_variant, EquipSlot.MainHand)));
}
}

View file

@ -2,10 +2,11 @@
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Gui.Debug;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
public unsafe class InventoryPanel : IDebugTabTree public unsafe class InventoryPanel : IGameDataDrawer
{ {
public string Label public string Label
=> "Inventory"; => "Inventory";

View file

@ -1,16 +1,19 @@
using Dalamud.Game.ClientState.Objects.Types; using System;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Plugin; using Dalamud.Plugin;
using Glamourer.Api; using Glamourer.Api;
using Glamourer.Interop; using Glamourer.Interop;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using System; using Penumbra.GameData.Enums;
using System.Linq; using Penumbra.GameData.Gui;
using Penumbra.GameData.Gui.Debug;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManager _objectManager) : IDebugTabTree public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManager _objectManager) : IGameDataDrawer
{ {
public string Label public string Label
=> "IPC Tester"; => "IPC Tester";
@ -18,17 +21,23 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag
public bool Disabled public bool Disabled
=> false; => false;
private int _gameObjectIndex; private int _gameObjectIndex;
private string _gameObjectName = string.Empty; private CustomItemId _customItemId;
private string _base64Apply = string.Empty; private StainId _stainId;
private string _designIdentifier = string.Empty; private EquipSlot _slot = EquipSlot.Head;
private string _gameObjectName = string.Empty;
private string _base64Apply = string.Empty;
private string _designIdentifier = string.Empty;
private GlamourerIpc.GlamourerErrorCode _setItemEc;
private GlamourerIpc.GlamourerErrorCode _setItemByActorNameEc;
public void Draw() public unsafe void Draw()
{ {
ImGui.InputInt("Game Object Index", ref _gameObjectIndex, 0, 0); ImGui.InputInt("Game Object Index", ref _gameObjectIndex, 0, 0);
ImGui.InputTextWithHint("##gameObject", "Character Name...", ref _gameObjectName, 64); ImGui.InputTextWithHint("##gameObject", "Character Name...", ref _gameObjectName, 64);
ImGui.InputTextWithHint("##base64", "Design Base64...", ref _base64Apply, 2047); ImGui.InputTextWithHint("##base64", "Design Base64...", ref _base64Apply, 2047);
ImGui.InputTextWithHint("##identifier", "Design identifier...", ref _designIdentifier, 36); ImGui.InputTextWithHint("##identifier", "Design identifier...", ref _designIdentifier, 36);
DrawItemInput();
using var table = ImRaii.Table("##ipc", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); using var table = ImRaii.Table("##ipc", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
if (!table) if (!table)
return; return;
@ -54,15 +63,6 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag
else else
ImGui.TextUnformatted("Error"); ImGui.TextUnformatted("Error");
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetDesignList);
ImGui.TableNextColumn();
var designList = GlamourerIpc.GetDesignListSubscriber(_pluginInterface)
.Invoke();
if (designList != null)
ImGuiUtil.CopyOnClickSelectable(string.Join(", ", designList));
else
ImGui.TextUnformatted("Error");
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevert); ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelRevert);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (ImGui.Button("Revert##Name")) if (ImGui.Button("Revert##Name"))
@ -106,6 +106,19 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag
GlamourerIpc.ApplyOnlyCustomizationToCharacterSubscriber(_pluginInterface) GlamourerIpc.ApplyOnlyCustomizationToCharacterSubscriber(_pluginInterface)
.Invoke(_base64Apply, _objectManager.Objects[_gameObjectIndex] as Character); .Invoke(_base64Apply, _objectManager.Objects[_gameObjectIndex] as Character);
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuid);
ImGui.TableNextColumn();
if (ImGui.Button("Apply##ByGuidName") && Guid.TryParse(_designIdentifier, out var guid1))
GlamourerIpc.ApplyByGuidSubscriber(_pluginInterface).Invoke(guid1, _gameObjectName);
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidToCharacter);
ImGui.TableNextColumn();
if (ImGui.Button("Apply##ByGuidCharacter") && Guid.TryParse(_designIdentifier, out var guid2))
GlamourerIpc.ApplyByGuidToCharacterSubscriber(_pluginInterface)
.Invoke(guid2, _objectManager.Objects[_gameObjectIndex] as Character);
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlock); ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelUnlock);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (ImGui.Button("Unlock##CustomizeCharacter")) if (ImGui.Button("Unlock##CustomizeCharacter"))
@ -118,15 +131,52 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag
GlamourerIpc.RevertToAutomationCharacterSubscriber(_pluginInterface) GlamourerIpc.RevertToAutomationCharacterSubscriber(_pluginInterface)
.Invoke(_objectManager.Objects[_gameObjectIndex] as Character, 1337); .Invoke(_objectManager.Objects[_gameObjectIndex] as Character, 1337);
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuid);
ImGui.TableNextColumn();
if (ImGui.Button("Apply##ByGuidName"))
GlamourerIpc.ApplyByGuidSubscriber(_pluginInterface).Invoke(Guid.Parse(_designIdentifier), _gameObjectName);
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelApplyByGuidToCharacter); ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelGetDesignList);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
if (ImGui.Button("Apply##ByGuidCharacter")) var designList = GlamourerIpc.GetDesignListSubscriber(_pluginInterface)
GlamourerIpc.ApplyByGuidToCharacterSubscriber(_pluginInterface) .Invoke();
.Invoke(Guid.Parse(_designIdentifier), _objectManager.Objects[_gameObjectIndex] as Character); if (ImGui.Button($"Copy {designList.Length} Designs to Clipboard###CopyDesignList"))
ImGui.SetClipboardText(string.Join("\n", designList));
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItem);
ImGui.TableNextColumn();
if (ImGui.Button("Set##SetItem"))
_setItemEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemSubscriber(_pluginInterface)
.Invoke(_objectManager.Objects[_gameObjectIndex] as Character, (byte)_slot, _customItemId.Id, _stainId.Id, 1337);
if (_setItemEc != GlamourerIpc.GlamourerErrorCode.Success)
{
ImGui.SameLine();
ImGui.TextUnformatted(_setItemEc.ToString());
}
ImGuiUtil.DrawTableColumn(GlamourerIpc.LabelSetItemByActorName);
ImGui.TableNextColumn();
if (ImGui.Button("Set##SetItemByActorName"))
_setItemByActorNameEc = (GlamourerIpc.GlamourerErrorCode)GlamourerIpc.SetItemByActorNameSubscriber(_pluginInterface)
.Invoke(_gameObjectName, (byte)_slot, _customItemId.Id, _stainId.Id, 1337);
if (_setItemByActorNameEc != GlamourerIpc.GlamourerErrorCode.Success)
{
ImGui.SameLine();
ImGui.TextUnformatted(_setItemByActorNameEc.ToString());
}
}
private void DrawItemInput()
{
var tmp = _customItemId.Id;
if (ImGuiUtil.InputUlong("Custom Item ID", ref tmp))
_customItemId = (CustomItemId)tmp;
var width = ImGui.GetContentRegionAvail().X;
EquipSlotCombo.Draw("Equip Slot", string.Empty, ref _slot);
var value = (int)_stainId.Id;
ImGui.SameLine();
width -= ImGui.GetContentRegionAvail().X;
ImGui.SetNextItemWidth(width);
if (ImGui.InputInt("Stain ID", ref value, 1, 3))
{
value = Math.Clamp(value, 0, byte.MaxValue);
_stainId = (StainId)value;
}
} }
} }

View file

@ -1,39 +0,0 @@
using System;
using System.Linq;
using Glamourer.Services;
using ImGuiNET;
using OtterGui.Raii;
using Penumbra.GameData.Enums;
namespace Glamourer.Gui.Tabs.DebugTab;
public class ItemManagerPanel(ItemManager _items) : IDebugTabTree
{
public string Label
=> "Item Manager";
public bool Disabled
=> !_items.ItemService.Valid;
private string _itemFilter = string.Empty;
public void Draw()
{
ImRaii.TreeNode($"Default Sword: {_items.DefaultSword.Name} ({_items.DefaultSword.ItemId}) ({_items.DefaultSword.Weapon()})",
ImGuiTreeNodeFlags.Leaf).Dispose();
DebugTab.DrawNameTable("All Items (Main)", ref _itemFilter,
_items.ItemService.AwaitedService.AllItems(true).Select(p => (p.Item1.Id,
$"{p.Item2.Name} ({(p.Item2.WeaponType == 0 ? p.Item2.Armor().ToString() : p.Item2.Weapon().ToString())})"))
.OrderBy(p => p.Item1));
DebugTab.DrawNameTable("All Items (Off)", ref _itemFilter,
_items.ItemService.AwaitedService.AllItems(false).Select(p => (p.Item1.Id,
$"{p.Item2.Name} ({(p.Item2.WeaponType == 0 ? p.Item2.Armor().ToString() : p.Item2.Weapon().ToString())})"))
.OrderBy(p => p.Item1));
foreach (var type in Enum.GetValues<FullEquipType>().Skip(1))
{
DebugTab.DrawNameTable(type.ToName(), ref _itemFilter,
_items.ItemService.AwaitedService[type]
.Select(p => (p.ItemId.Id, $"{p.Name} ({(p.WeaponType == 0 ? p.Armor().ToString() : p.Weapon().ToString())})")));
}
}
}

View file

@ -7,11 +7,12 @@ using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Gui.Debug;
using ImGuiClip = OtterGui.ImGuiClip; using ImGuiClip = OtterGui.ImGuiClip;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
public class ItemUnlockPanel(ItemUnlockManager _itemUnlocks, ItemManager _items) : IDebugTabTree public class ItemUnlockPanel(ItemUnlockManager _itemUnlocks, ItemManager _items) : IGameDataDrawer
{ {
public string Label public string Label
=> "Unlocked Items"; => "Unlocked Items";
@ -39,7 +40,7 @@ public class ItemUnlockPanel(ItemUnlockManager _itemUnlocks, ItemManager _items)
var remainder = ImGuiClip.ClippedDraw(_itemUnlocks, skips, t => var remainder = ImGuiClip.ClippedDraw(_itemUnlocks, skips, t =>
{ {
ImGuiUtil.DrawTableColumn(t.Key.ToString()); ImGuiUtil.DrawTableColumn(t.Key.ToString());
if (_items.ItemService.AwaitedService.TryGetValue(t.Key, EquipSlot.MainHand, out var equip)) if (_items.ItemData.TryGetValue(t.Key, EquipSlot.MainHand, out var equip))
{ {
ImGuiUtil.DrawTableColumn(equip.Name); ImGuiUtil.DrawTableColumn(equip.Name);
ImGuiUtil.DrawTableColumn(equip.Type.ToName()); ImGuiUtil.DrawTableColumn(equip.Type.ToName());

View file

@ -1,76 +0,0 @@
using Glamourer.Interop;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
namespace Glamourer.Gui.Tabs.DebugTab;
public class JobPanel(JobService _jobs) : IDebugTabTree
{
public string Label
=> "Job Service";
public bool Disabled
=> false;
public void Draw()
{
DrawJobs();
DrawJobGroups();
DrawValidJobGroups();
}
private void DrawJobs()
{
using var t = ImRaii.TreeNode("Jobs");
if (!t)
return;
using var table = ImRaii.Table("##jobs", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
if (!table)
return;
foreach (var (id, job) in _jobs.Jobs)
{
ImGuiUtil.DrawTableColumn(id.ToString("D3"));
ImGuiUtil.DrawTableColumn(job.Name);
ImGuiUtil.DrawTableColumn(job.Abbreviation);
}
}
private void DrawJobGroups()
{
using var t = ImRaii.TreeNode("All Job Groups");
if (!t)
return;
using var table = ImRaii.Table("##groups", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
if (!table)
return;
foreach (var (group, idx) in _jobs.AllJobGroups.WithIndex())
{
ImGuiUtil.DrawTableColumn(idx.ToString("D3"));
ImGuiUtil.DrawTableColumn(group.Name);
ImGuiUtil.DrawTableColumn(group.Count.ToString());
}
}
private void DrawValidJobGroups()
{
using var t = ImRaii.TreeNode("Valid Job Groups");
if (!t)
return;
using var table = ImRaii.Table("##groups", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
if (!table)
return;
foreach (var (id, group) in _jobs.JobGroups)
{
ImGuiUtil.DrawTableColumn(id.ToString("D3"));
ImGuiUtil.DrawTableColumn(group.Name);
ImGuiUtil.DrawTableColumn(group.Count.ToString());
}
}
}

View file

@ -1,14 +1,13 @@
using System; using System;
using System.Numerics; using System.Numerics;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Glamourer.Customization;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using Glamourer.Structs;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Gui.Debug;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
@ -18,7 +17,7 @@ public unsafe class ModelEvaluationPanel(
VisorService _visorService, VisorService _visorService,
UpdateSlotService _updateSlotService, UpdateSlotService _updateSlotService,
ChangeCustomizeService _changeCustomizeService, ChangeCustomizeService _changeCustomizeService,
CrestService _crestService) : IDebugTabTree CrestService _crestService) : IGameDataDrawer
{ {
public string Label public string Label
=> "Model Evaluation"; => "Model Evaluation";
@ -126,15 +125,14 @@ public unsafe class ModelEvaluationPanel(
model.AsHuman->Head.Value == 0 ? actor.GetArmor(EquipSlot.Head) : CharacterArmor.Empty); 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"); using var id = ImRaii.PushId("WeaponState");
ImGuiUtil.DrawTableColumn("Weapon State"); ImGuiUtil.DrawTableColumn("Weapon State");
ImGuiUtil.DrawTableColumn(actor.IsCharacter ImGuiUtil.DrawTableColumn(actor.IsCharacter
? actor.AsCharacter->DrawData.IsWeaponHidden ? "Hidden" : "Visible" ? actor.AsCharacter->DrawData.IsWeaponHidden ? "Hidden" : "Visible"
: "No Character"); : "No Character");
var text = string.Empty; string text;
if (!model.IsHuman) if (!model.IsHuman)
{ {
text = "No Model"; text = "No Model";
@ -146,19 +144,14 @@ public unsafe class ModelEvaluationPanel(
else else
{ {
var weapon = (DrawObject*)model.AsDrawObject->Object.ChildObject; var weapon = (DrawObject*)model.AsDrawObject->Object.ChildObject;
if ((weapon->Flags & 0x09) == 0x09) text = (weapon->Flags & 0x09) == 0x09 ? "Visible" : "Hidden";
text = "Visible";
else
text = "Hidden";
} }
ImGuiUtil.DrawTableColumn(text); ImGuiUtil.DrawTableColumn(text);
ImGui.TableNextColumn(); 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"); using var id = ImRaii.PushId("Wetness");
ImGuiUtil.DrawTableColumn("Wetness"); ImGuiUtil.DrawTableColumn("Wetness");
@ -199,7 +192,7 @@ public unsafe class ModelEvaluationPanel(
if (ImGui.SmallButton("Change Piece")) if (ImGui.SmallButton("Change Piece"))
_updateSlotService.UpdateArmor(model, slot, _updateSlotService.UpdateArmor(model, slot,
new CharacterArmor((SetId)(slot == EquipSlot.Hands ? 6064 : slot == EquipSlot.Head ? 6072 : 1), 1, 0)); new CharacterArmor((PrimaryId)(slot == EquipSlot.Hands ? 6064 : slot == EquipSlot.Head ? 6072 : 1), 1, 0));
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.SmallButton("Change Stain")) if (ImGui.SmallButton("Change Stain"))
_updateSlotService.UpdateStain(model, slot, 5); _updateSlotService.UpdateStain(model, slot, 5);
@ -212,12 +205,12 @@ public unsafe class ModelEvaluationPanel(
private void DrawCustomize(Actor actor, Model model) private void DrawCustomize(Actor actor, Model model)
{ {
using var id = ImRaii.PushId("Customize"); using var id = ImRaii.PushId("Customize");
var actorCustomize = new Customize(actor.IsCharacter var actorCustomize = actor.IsCharacter
? *(Penumbra.GameData.Structs.CustomizeData*)&actor.AsCharacter->DrawData.CustomizeData ? *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData
: new Penumbra.GameData.Structs.CustomizeData()); : new CustomizeArray();
var modelCustomize = new Customize(model.IsHuman var modelCustomize = model.IsHuman
? *(Penumbra.GameData.Structs.CustomizeData*)model.AsHuman->Customize.Data ? *(CustomizeArray*)model.AsHuman->Customize.Data
: new Penumbra.GameData.Structs.CustomizeData()); : new CustomizeArray();
foreach (var type in Enum.GetValues<CustomizeIndex>()) foreach (var type in Enum.GetValues<CustomizeIndex>())
{ {
using var id2 = ImRaii.PushId((int)type); using var id2 = ImRaii.PushId((int)type);
@ -235,7 +228,7 @@ public unsafe class ModelEvaluationPanel(
var shift = BitOperations.TrailingZeroCount(mask); var shift = BitOperations.TrailingZeroCount(mask);
var newValue = value + (1 << shift); var newValue = value + (1 << shift);
modelCustomize.Set(type, (CustomizeValue)newValue); modelCustomize.Set(type, (CustomizeValue)newValue);
_changeCustomizeService.UpdateCustomize(model, modelCustomize.Data); _changeCustomizeService.UpdateCustomize(model, modelCustomize);
} }
ImGui.SameLine(); ImGui.SameLine();
@ -246,14 +239,14 @@ public unsafe class ModelEvaluationPanel(
var shift = BitOperations.TrailingZeroCount(mask); var shift = BitOperations.TrailingZeroCount(mask);
var newValue = value - (1 << shift); var newValue = value - (1 << shift);
modelCustomize.Set(type, (CustomizeValue)newValue); modelCustomize.Set(type, (CustomizeValue)newValue);
_changeCustomizeService.UpdateCustomize(model, modelCustomize.Data); _changeCustomizeService.UpdateCustomize(model, modelCustomize);
} }
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.SmallButton("Reset")) if (ImGui.SmallButton("Reset"))
{ {
modelCustomize.Set(type, actorCustomize[type]); modelCustomize.Set(type, actorCustomize[type]);
_changeCustomizeService.UpdateCustomize(model, modelCustomize.Data); _changeCustomizeService.UpdateCustomize(model, modelCustomize);
} }
} }
} }

View file

@ -3,19 +3,22 @@ using System.Numerics;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Glamourer.Customization;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.GameData;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.State; using Glamourer.State;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Gui.Debug;
using ImGuiClip = OtterGui.ImGuiClip; using ImGuiClip = OtterGui.ImGuiClip;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectManager _objectManager, DesignConverter _designConverter) : IDebugTabTree public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectManager _objectManager, DesignConverter _designConverter)
: IGameDataDrawer
{ {
public string Label public string Label
=> "NPC Appearance"; => "NPC Appearance";
@ -23,23 +26,27 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM
public bool Disabled public bool Disabled
=> false; => false;
private string _npcFilter = string.Empty; private string _npcFilter = string.Empty;
private bool _customizeOrGear = false; private bool _customizeOrGear;
public void Draw() public void Draw()
{ {
ImGui.Checkbox("Compare Customize (or Gear)", ref _customizeOrGear); ImGui.Checkbox("Compare Customize (or Gear)", ref _customizeOrGear);
ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X); ImGui.SetNextItemWidth(ImGui.GetContentRegionAvail().X);
ImGui.InputTextWithHint("##npcFilter", "Filter...", ref _npcFilter, 64); var resetScroll = ImGui.InputTextWithHint("##npcFilter", "Filter...", ref _npcFilter, 64);
using var table = ImRaii.Table("npcs", 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit, using var table = ImRaii.Table("npcs", 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.SizingFixedFit,
new Vector2(-1, 400 * ImGuiHelpers.GlobalScale)); new Vector2(-1, 400 * ImGuiHelpers.GlobalScale));
if (!table) if (!table)
return; return;
if (resetScroll)
ImGui.SetScrollY(0);
ImGui.TableSetupColumn("Button", ImGuiTableColumnFlags.WidthFixed); ImGui.TableSetupColumn("Button", ImGuiTableColumnFlags.WidthFixed);
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed, ImGuiHelpers.GlobalScale * 300); ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed, ImGuiHelpers.GlobalScale * 300);
ImGui.TableSetupColumn("Kind", ImGuiTableColumnFlags.WidthFixed); ImGui.TableSetupColumn("Kind", ImGuiTableColumnFlags.WidthFixed);
ImGui.TableSetupColumn("Id", ImGuiTableColumnFlags.WidthFixed);
ImGui.TableSetupColumn("Visor", ImGuiTableColumnFlags.WidthFixed); ImGui.TableSetupColumn("Visor", ImGuiTableColumnFlags.WidthFixed);
ImGui.TableSetupColumn("Compare", ImGuiTableColumnFlags.WidthStretch); ImGui.TableSetupColumn("Compare", ImGuiTableColumnFlags.WidthStretch);
@ -48,21 +55,19 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM
ImGui.TableNextRow(); ImGui.TableNextRow();
var idx = 0; var idx = 0;
var remainder = ImGuiClip.FilteredClippedDraw(_npcCombo.Items, skips, 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(); ImGui.TableNextColumn();
ImGuiClip.DrawEndDummy(remainder, ImGui.GetFrameHeightWithSpacing()); ImGuiClip.DrawEndDummy(remainder, ImGui.GetFrameHeightWithSpacing());
return; return;
void Draw(CustomizationNpcOptions.NpcData data) void DrawData(NpcData data)
{ {
using var id = ImRaii.PushId(idx++); using var id = ImRaii.PushId(idx++);
var disabled = !_state.GetOrCreate(_objectManager.Player, out var state); var disabled = !_state.GetOrCreate(_objectManager.Player, out var state);
ImGui.TableNextColumn(); 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)) foreach (var (slot, item, stain) in _designConverter.FromDrawData(data.Equip.ToArray(), data.Mainhand, data.Offhand, true))
_state.ChangeEquip(state!, slot, item, stain, StateChanged.Source.Manual); _state.ChangeEquip(state!, slot, item, stain, StateChanged.Source.Manual);
_state.ChangeVisorState(state!, data.VisorToggled, StateChanged.Source.Manual); _state.ChangeVisorState(state!, data.VisorToggled, StateChanged.Source.Manual);
_state.ChangeCustomize(state!, data.Customize, CustomizeFlagExtensions.All, StateChanged.Source.Manual); _state.ChangeCustomize(state!, data.Customize, CustomizeFlagExtensions.All, StateChanged.Source.Manual);
@ -76,7 +81,11 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM
ImGui.AlignTextToFramePadding(); ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(data.Kind is ObjectKind.BattleNpc ? "B" : "E"); ImGui.TextUnformatted(data.Kind is ObjectKind.BattleNpc ? "B" : "E");
using (var icon = ImRaii.PushFont(UiBuilder.IconFont)) ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(data.Id.Id.ToString());
using (_ = ImRaii.PushFont(UiBuilder.IconFont))
{ {
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding(); ImGui.AlignTextToFramePadding();
@ -86,7 +95,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM
using var mono = ImRaii.PushFont(UiBuilder.MonoFont); using var mono = ImRaii.PushFont(UiBuilder.MonoFont);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGui.AlignTextToFramePadding(); ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted(_customizeOrGear ? data.Customize.Data.ToString() : data.WriteGear()); ImGui.TextUnformatted(_customizeOrGear ? data.Customize.ToString() : data.WriteGear());
} }
} }
} }

View file

@ -3,14 +3,15 @@ using System.Globalization;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.Services;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Actors;
using Penumbra.GameData.Gui.Debug;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
public class ObjectManagerPanel(ObjectManager _objectManager, ActorService _actors) : IDebugTabTree public class ObjectManagerPanel(ObjectManager _objectManager, ActorManager _actors) : IGameDataDrawer
{ {
public string Label public string Label
=> "Object Manager"; => "Object Manager";
@ -33,7 +34,7 @@ public class ObjectManagerPanel(ObjectManager _objectManager, ActorService _acto
ImGui.TableNextColumn(); ImGui.TableNextColumn();
ImGuiUtil.DrawTableColumn("World"); ImGuiUtil.DrawTableColumn("World");
ImGuiUtil.DrawTableColumn(_actors.Valid ? _actors.AwaitedService.Data.ToWorldName(_objectManager.World) : "Service Missing"); ImGuiUtil.DrawTableColumn(_actors.Finished ? _actors.Data.ToWorldName(_objectManager.World) : "Service Missing");
ImGuiUtil.DrawTableColumn(_objectManager.World.ToString()); ImGuiUtil.DrawTableColumn(_objectManager.World.ToString());
ImGuiUtil.DrawTableColumn("Player Character"); ImGuiUtil.DrawTableColumn("Player Character");

View file

@ -1,8 +1,5 @@
using System; using System;
using Dalamud.Interface;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Glamourer.Gui.Tabs.DebugTab;
using Glamourer.Gui;
using Glamourer.Interop.Penumbra; using Glamourer.Interop.Penumbra;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using ImGuiNET; using ImGuiNET;
@ -10,11 +7,12 @@ using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.Api.Enums; using Penumbra.Api.Enums;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Gui.Debug;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItemTooltip _penumbraTooltip) : IDebugTabTree public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItemTooltip _penumbraTooltip) : IGameDataDrawer
{ {
public string Label public string Label
=> "Penumbra Interop"; => "Penumbra Interop";
@ -27,9 +25,6 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem
public void Draw() public void Draw()
{ {
if (!ImGui.CollapsingHeader("Penumbra"))
return;
using var table = ImRaii.Table("##PenumbraTable", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg); using var table = ImRaii.Table("##PenumbraTable", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
if (!table) if (!table)
return; return;
@ -59,7 +54,7 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
ImGui.InputInt("##CutsceneIndex", ref _gameObjectIndex, 0, 0); ImGui.InputInt("##CutsceneIndex", ref _gameObjectIndex, 0, 0);
ImGuiUtil.DrawTableColumn(_penumbra.Available ImGuiUtil.DrawTableColumn(_penumbra.Available
? _penumbra.CutsceneParent(_gameObjectIndex).ToString() ? _penumbra.CutsceneParent((ushort) _gameObjectIndex).ToString()
: "Penumbra Unavailable"); : "Penumbra Unavailable");
ImGuiUtil.DrawTableColumn("Redraw Object"); ImGuiUtil.DrawTableColumn("Redraw Object");
@ -67,7 +62,7 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale); ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
ImGui.InputInt("##redrawObject", ref _gameObjectIndex, 0, 0); ImGui.InputInt("##redrawObject", ref _gameObjectIndex, 0, 0);
ImGui.TableNextColumn(); ImGui.TableNextColumn();
using (var disabled = ImRaii.Disabled(!_penumbra.Available)) using (_ = ImRaii.Disabled(!_penumbra.Available))
{ {
if (ImGui.SmallButton("Redraw")) if (ImGui.SmallButton("Redraw"))
_penumbra.RedrawObject((ObjectIndex)_gameObjectIndex, RedrawType.Redraw); _penumbra.RedrawObject((ObjectIndex)_gameObjectIndex, RedrawType.Redraw);

View file

@ -1,42 +0,0 @@
using System;
using System.Linq;
using Glamourer.Services;
using ImGuiNET;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Gui.Tabs.DebugTab;
public class RestrictedGearPanel(ItemManager _items) : IDebugTabTree
{
public string Label
=> "Restricted Gear Service";
public bool Disabled
=> false;
private int _setId;
private int _secondaryId;
private int _variant;
public void Draw()
{
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted("Resolve Model");
DebugTab.DrawInputModelSet(false, ref _setId, ref _secondaryId, ref _variant);
foreach (var race in Enum.GetValues<Race>().Skip(1))
{
ReadOnlySpan<Gender> genders = [Gender.Male, Gender.Female];
foreach (var gender in genders)
{
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
var (replaced, model) =
_items.RestrictedGear.ResolveRestricted(new CharacterArmor((SetId)_setId, (Variant)_variant, 0), slot, race, gender);
if (replaced)
ImGui.TextUnformatted($"{race.ToName()} - {gender} - {slot.ToName()} resolves to {model}.");
}
}
}
}
}

View file

@ -3,10 +3,11 @@ using Glamourer.Interop;
using Glamourer.Interop.Structs; using Glamourer.Interop.Structs;
using Glamourer.State; using Glamourer.State;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Gui.Debug;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
public class RetainedStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IDebugTabTree public class RetainedStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IGameDataDrawer
{ {
public string Label public string Label
=> "Retained States (Inactive Actors)"; => "Retained States (Inactive Actors)";

View file

@ -1,53 +0,0 @@
using System;
using System.Numerics;
using Dalamud.Interface.Utility;
using Glamourer.Services;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using ImGuiClip = OtterGui.ImGuiClip;
namespace Glamourer.Gui.Tabs.DebugTab;
public class StainPanel(ItemManager _items) : IDebugTabTree
{
public string Label
=> "Stain Service";
public bool Disabled
=> false;
private string _stainFilter = string.Empty;
public void Draw()
{
var resetScroll = ImGui.InputTextWithHint("##filter", "Filter...", ref _stainFilter, 256);
var height = ImGui.GetTextLineHeightWithSpacing() + 2 * ImGui.GetStyle().CellPadding.Y;
using var table = ImRaii.Table("##table", 4,
ImGuiTableFlags.RowBg | ImGuiTableFlags.ScrollY | ImGuiTableFlags.BordersOuter | ImGuiTableFlags.SizingFixedFit,
new Vector2(-1, 10 * height));
if (!table)
return;
if (resetScroll)
ImGui.SetScrollY(0);
ImGui.TableNextColumn();
var skips = ImGuiClip.GetNecessarySkips(height);
ImGui.TableNextRow();
var remainder = ImGuiClip.FilteredClippedDraw(_items.Stains, skips,
p => p.Key.Id.ToString().Contains(_stainFilter) || p.Value.Name.Contains(_stainFilter, StringComparison.OrdinalIgnoreCase),
p =>
{
ImGuiUtil.DrawTableColumn(p.Key.Id.ToString("D3"));
ImGui.TableNextColumn();
ImGui.GetWindowDrawList().AddRectFilled(ImGui.GetCursorScreenPos(),
ImGui.GetCursorScreenPos() + new Vector2(ImGui.GetTextLineHeight()),
p.Value.RgbaColor, 5 * ImGuiHelpers.GlobalScale);
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight()));
ImGuiUtil.DrawTableColumn(p.Value.Name);
ImGuiUtil.DrawTableColumn($"#{p.Value.R:X2}{p.Value.G:X2}{p.Value.B:X2}{(p.Value.Gloss ? ", Glossy" : string.Empty)}");
});
ImGuiClip.DrawEndDummy(remainder, height);
}
}

View file

@ -7,11 +7,12 @@ using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Raii; using OtterGui.Raii;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Gui.Debug;
using ImGuiClip = OtterGui.ImGuiClip; using ImGuiClip = OtterGui.ImGuiClip;
namespace Glamourer.Gui.Tabs.DebugTab; namespace Glamourer.Gui.Tabs.DebugTab;
public class UnlockableItemsPanel(ItemUnlockManager _itemUnlocks, ItemManager _items) : IDebugTabTree public class UnlockableItemsPanel(ItemUnlockManager _itemUnlocks, ItemManager _items) : IGameDataDrawer
{ {
public string Label public string Label
=> "Unlockable Items"; => "Unlockable Items";
@ -40,7 +41,7 @@ public class UnlockableItemsPanel(ItemUnlockManager _itemUnlocks, ItemManager _i
var remainder = ImGuiClip.ClippedDraw(_itemUnlocks.Unlockable, skips, t => var remainder = ImGuiClip.ClippedDraw(_itemUnlocks.Unlockable, skips, t =>
{ {
ImGuiUtil.DrawTableColumn(t.Key.ToString()); ImGuiUtil.DrawTableColumn(t.Key.ToString());
if (_items.ItemService.AwaitedService.TryGetValue(t.Key, EquipSlot.MainHand, out var equip)) if (_items.ItemData.TryGetValue(t.Key, EquipSlot.MainHand, out var equip))
{ {
ImGuiUtil.DrawTableColumn(equip.Name); ImGuiUtil.DrawTableColumn(equip.Name);
ImGuiUtil.DrawTableColumn(equip.Type.ToName()); ImGuiUtil.DrawTableColumn(equip.Type.ToName());

View file

@ -7,14 +7,12 @@ using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.Internal.Notifications; using Dalamud.Interface.Internal.Notifications;
using FFXIVClientStructs.FFXIV.Client.System.Framework; using FFXIVClientStructs.FFXIV.Client.System.Framework;
using Glamourer.Automation; using Glamourer.Automation;
using Glamourer.Customization;
using Glamourer.Designs; using Glamourer.Designs;
using Glamourer.Events; using Glamourer.Events;
using Glamourer.Gui.Customization; using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment; using Glamourer.Gui.Equipment;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.State; using Glamourer.State;
using Glamourer.Structs;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Classes; using OtterGui.Classes;
@ -156,9 +154,9 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
private void DrawCustomizeApplication() private void DrawCustomizeApplication()
{ {
var set = _selector.Selected!.CustomizationSet; var set = _selector.Selected!.CustomizeSet;
var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender; var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.BodyType;
var flags = _selector.Selected!.ApplyCustomize == 0 ? 0 : (_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1; var flags = _selector.Selected!.ApplyCustomizeExcludingBodyType == 0 ? 0 : (_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1;
if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3)) if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3))
{ {
var newFlags = flags == 3; var newFlags = flags == 3;

View file

@ -1,36 +1,11 @@
using System.Collections; using Glamourer.GameData;
using System.Collections.Generic;
using System.Threading.Tasks;
using Dalamud.Plugin.Services;
using Glamourer.Customization;
using Glamourer.Services;
using OtterGui.Widgets; using OtterGui.Widgets;
using Penumbra.GameData;
namespace Glamourer.Gui.Tabs; namespace Glamourer.Gui.Tabs;
public class NpcCombo(ActorService actorManager, IdentifierService identifier, IDataManager data) public class NpcCombo(NpcCustomizeSet npcCustomizeSet)
: FilterComboBase<CustomizationNpcOptions.NpcData>(new LazyList(actorManager, identifier, data), false, Glamourer.Log) : FilterComboCache<NpcData>(npcCustomizeSet, Glamourer.Log)
{ {
private class LazyList(ActorService actorManager, IdentifierService identifier, IDataManager data) protected override string ToString(NpcData obj)
: IReadOnlyList<CustomizationNpcOptions.NpcData>
{
private readonly Task<IReadOnlyList<CustomizationNpcOptions.NpcData>> _task
= Task.Run(() => CustomizationNpcOptions.CreateNpcData(actorManager.AwaitedService.Data.ENpcs, actorManager.AwaitedService.Data.BNpcs, identifier.AwaitedService, data));
public IEnumerator<CustomizationNpcOptions.NpcData> GetEnumerator()
=> _task.Result.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator()
=> GetEnumerator();
public int Count
=> _task.Result.Count;
public CustomizationNpcOptions.NpcData this[int index]
=> _task.Result[index];
}
protected override string ToString(CustomizationNpcOptions.NpcData obj)
=> obj.Name; => obj.Name;
} }

View file

@ -0,0 +1,141 @@
using System;
using System.Collections.Generic;
using System.IO;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Glamourer.Designs;
using Glamourer.GameData;
using Glamourer.Services;
using ImGuiNET;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace Glamourer.Gui.Tabs.NpcTab;
public class LocalNpcAppearanceData : ISavable
{
private readonly DesignColors _colors;
public record struct Data(string Color = "", bool Favorite = false);
private readonly Dictionary<ulong, Data> _data = [];
public LocalNpcAppearanceData(DesignColors colors, SaveService saveService)
{
_colors = colors;
Load(saveService);
DataChanged += () => saveService.QueueSave(this);
}
public bool IsFavorite(in NpcData data)
=> _data.TryGetValue(ToKey(data), out var tuple) && tuple.Favorite;
public (uint Color, bool Favorite) GetData(in NpcData data)
=> _data.TryGetValue(ToKey(data), out var t)
? (GetColor(t.Color, t.Favorite, data.Kind), t.Favorite)
: (GetColor(string.Empty, false, data.Kind), false);
public string GetColor(in NpcData data)
=> _data.TryGetValue(ToKey(data), out var t) ? t.Color : string.Empty;
private uint GetColor(string color, bool favorite, ObjectKind kind)
{
if (color.Length == 0)
{
if (favorite)
return ColorId.FavoriteStarOn.Value();
return kind is ObjectKind.BattleNpc
? ColorId.BattleNpc.Value()
: ColorId.EventNpc.Value();
}
if (_colors.TryGetValue(color, out var value))
return value == 0 ? ImGui.GetColorU32(ImGuiCol.Text) : value;
return _colors.MissingColor;
}
public void ToggleFavorite(in NpcData data)
{
var key = ToKey(data);
if (_data.TryGetValue(key, out var t))
{
if (t is { Color: "", Favorite: true })
_data.Remove(key);
else
_data[key] = t with { Favorite = !t.Favorite };
}
else
{
_data[key] = new Data(string.Empty, true);
}
DataChanged.Invoke();
}
public void SetColor(in NpcData data, string color)
{
var key = ToKey(data);
if (_data.TryGetValue(key, out var t))
{
if (!t.Favorite && color.Length == 0)
_data.Remove(key);
else
_data[key] = t with { Color = color };
}
else if (color.Length != 0)
{
_data[key] = new Data(color);
}
DataChanged.Invoke();
}
private static ulong ToKey(in NpcData data)
=> (byte)data.Kind | ((ulong)data.Id.Id << 8);
public event Action DataChanged = null!;
public string ToFilename(FilenameService fileNames)
=> fileNames.NpcAppearanceFile;
public void Save(StreamWriter writer)
{
var jObj = new JObject()
{
["Version"] = 1,
["Data"] = JToken.FromObject(_data),
};
using var j = new JsonTextWriter(writer);
j.Formatting = Formatting.Indented;
jObj.WriteTo(j);
}
private void Load(SaveService save)
{
var file = save.FileNames.NpcAppearanceFile;
if (!File.Exists(file))
return;
try
{
var text = File.ReadAllText(file);
var jObj = JObject.Parse(text);
var version = jObj["Version"]?.ToObject<int>() ?? 0;
switch (version)
{
case 1:
var data = jObj["Data"]?.ToObject<Dictionary<ulong, Data>>() ?? [];
_data.EnsureCapacity(data.Count);
foreach (var kvp in data)
_data.Add(kvp.Key, kvp.Value);
return;
default: throw new Exception("Invalid version {version}.");
}
}
catch (Exception ex)
{
Glamourer.Log.Error($"Could not read local NPC appearance data:\n{ex}");
}
}
}

View file

@ -0,0 +1,45 @@
using System;
using Glamourer.Designs;
using Glamourer.GameData;
using OtterGui.Classes;
namespace Glamourer.Gui.Tabs.NpcTab;
public sealed class NpcFilter(LocalNpcAppearanceData _favorites) : FilterUtility<NpcData>
{
protected override string Tooltip
=> "Filter NPC appearances for those where their names contain the given substring.\n"
+ "Enter i:[number] to filter for NPCs of certain IDs.\n"
+ "Enter c:[string] to filter for NPC appearances set to specific colors.";
protected override (LowerString, long, int) FilterChange(string input)
=> input.Length switch
{
0 => (LowerString.Empty, 0, -1),
> 1 when input[1] == ':' =>
input[0] switch
{
'i' or 'I' => input.Length == 2 ? (LowerString.Empty, 0, -1) :
long.TryParse(input.AsSpan(2), out var r) ? (LowerString.Empty, r, 1) : (LowerString.Empty, 0, -1),
'c' or 'C' => input.Length == 2 ? (LowerString.Empty, 0, -1) : (new LowerString(input[2..]), 0, 2),
_ => (new LowerString(input), 0, 0),
},
_ => (new LowerString(input), 0, 0),
};
public override bool ApplyFilter(in NpcData value)
=> FilterMode switch
{
-1 => false,
0 => Filter.IsContained(value.Name),
1 => value.Id.Id == NumericalFilter,
2 => Filter.IsContained(GetColor(value)),
_ => false, // Should never happen
};
private string GetColor(in NpcData value)
{
var color = _favorites.GetColor(value);
return color.Length == 0 ? DesignColors.AutomaticName : color;
}
}

View file

@ -0,0 +1,294 @@
using System;
using System.Linq;
using System.Numerics;
using Dalamud.Interface;
using Dalamud.Interface.Internal.Notifications;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using Glamourer.Designs;
using Glamourer.Events;
using Glamourer.Gui.Customization;
using Glamourer.Gui.Equipment;
using Glamourer.Gui.Tabs.DesignTab;
using Glamourer.Interop;
using Glamourer.State;
using ImGuiNET;
using Lumina.Data.Parsing.Scd;
using OtterGui;
using OtterGui.Classes;
using OtterGui.Raii;
using Penumbra.GameData.Enums;
namespace Glamourer.Gui.Tabs.NpcTab;
public class NpcPanel(
NpcSelector _selector,
LocalNpcAppearanceData _favorites,
CustomizationDrawer _customizeDrawer,
EquipmentDrawer _equipDrawer,
DesignConverter _converter,
DesignManager _designManager,
StateManager _state,
ObjectManager _objects,
DesignColors _colors)
{
private readonly DesignColorCombo _colorCombo = new(_colors, true);
private string _newName = string.Empty;
private DesignBase? _newDesign;
public void Draw()
{
using var group = ImRaii.Group();
DrawHeader();
DrawPanel();
}
private void DrawHeader()
{
HeaderDrawer.Draw(_selector.HasSelection ? _selector.Selection.Name : "No Selection", ColorId.NormalDesign.Value(),
ImGui.GetColorU32(ImGuiCol.FrameBg), 2, ExportToClipboardButton(), SaveAsDesignButton(), FavoriteButton());
SaveDesignDrawPopup();
}
private HeaderDrawer.Button FavoriteButton()
{
var (desc, color) = _favorites.IsFavorite(_selector.Selection)
? ("Remove this NPC appearance from your favorites.", ColorId.FavoriteStarOn.Value())
: ("Add this NPC Appearance to your favorites.", 0x80000000);
return new HeaderDrawer.Button
{
Icon = FontAwesomeIcon.Star,
OnClick = () => _favorites.ToggleFavorite(_selector.Selection),
Visible = _selector.HasSelection,
Description = desc,
TextColor = color,
};
}
private HeaderDrawer.Button ExportToClipboardButton()
=> new()
{
Description =
"Copy the current NPCs appearance to your clipboard.\nHold Control to disable applying of customizations for the copied design.\nHold Shift to disable applying of gear for the copied design.",
Icon = FontAwesomeIcon.Copy,
OnClick = ExportToClipboard,
Visible = _selector.HasSelection,
};
private HeaderDrawer.Button SaveAsDesignButton()
=> new()
{
Description =
"Save this NPCs appearance as a design.\nHold Control to disable applying of customizations for the saved design.\nHold Shift to disable applying of gear for the saved design.",
Icon = FontAwesomeIcon.Save,
OnClick = SaveDesignOpen,
Visible = _selector.HasSelection,
};
private void ExportToClipboard()
{
try
{
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags();
var data = ToDesignData();
var text = _converter.ShareBase64(data, applyGear, applyCustomize, applyCrest);
ImGui.SetClipboardText(text);
}
catch (Exception ex)
{
Glamourer.Messager.NotificationMessage(ex, $"Could not copy {_selector.Selection.Name}'s data to clipboard.",
$"Could not copy data from NPC appearance {_selector.Selection.Kind} {_selector.Selection.Id.Id} to clipboard",
NotificationType.Error);
}
}
private void SaveDesignOpen()
{
ImGui.OpenPopup("Save as Design");
_newName = _selector.Selection.Name;
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags();
var data = ToDesignData();
_newDesign = _converter.Convert(data, applyGear, applyCustomize, applyCrest);
}
private void SaveDesignDrawPopup()
{
if (!ImGuiUtil.OpenNameField("Save as Design", ref _newName))
return;
if (_newDesign != null && _newName.Length > 0)
_designManager.CreateClone(_newDesign, _newName, true);
_newDesign = null;
_newName = string.Empty;
}
private void DrawPanel()
{
using var child = ImRaii.Child("##Panel", -Vector2.One, true);
if (!child || !_selector.HasSelection)
return;
DrawButtonRow();
DrawCustomization();
DrawEquipment();
DrawAppearanceInfo();
}
private void DrawButtonRow()
{
DrawApplyToSelf();
ImGui.SameLine();
DrawApplyToTarget();
}
private void DrawCustomization()
{
if (!ImGui.CollapsingHeader("Customization"))
return;
_customizeDrawer.Draw(_selector.Selection.Customize, true, true);
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
}
private void DrawEquipment()
{
if (!ImGui.CollapsingHeader("Equipment"))
return;
_equipDrawer.Prepare();
var designData = ToDesignData();
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
var data = new EquipDrawData(slot, designData) { Locked = true };
_equipDrawer.DrawEquip(data);
}
var mainhandData = new EquipDrawData(EquipSlot.MainHand, designData) { Locked = true };
var offhandData = new EquipDrawData(EquipSlot.OffHand, designData) { Locked = true };
_equipDrawer.DrawWeapons(mainhandData, offhandData, false);
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromValue(ActorState.MetaIndex.VisorState, _selector.Selection.VisorToggled));
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
}
private DesignData ToDesignData()
{
var selection = _selector.Selection;
var items = _converter.FromDrawData(selection.Equip.ToArray(), selection.Mainhand, selection.Offhand, true).ToArray();
var designData = new DesignData { Customize = selection.Customize };
foreach (var (slot, item, stain) in items)
{
designData.SetItem(slot, item);
designData.SetStain(slot, stain);
}
return designData;
}
private void DrawApplyToSelf()
{
var (id, data) = _objects.PlayerData;
if (!ImGuiUtil.DrawDisabledButton("Apply to Yourself", Vector2.Zero,
"Apply the current NPC appearance to your character.\nHold Control to only apply gear.\nHold Shift to only apply customizations.",
!data.Valid))
return;
if (_state.GetOrCreate(id, data.Objects[0], out var state))
{
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags();
var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, applyCrest);
_state.ApplyDesign(design, state, StateChanged.Source.Manual);
}
}
private void DrawApplyToTarget()
{
var (id, data) = _objects.TargetData;
var tt = id.IsValid
? data.Valid
? "Apply the current NPC appearance to your current target.\nHold Control to only apply gear.\nHold Shift to only apply customizations."
: "The current target can not be manipulated."
: "No valid target selected.";
if (!ImGuiUtil.DrawDisabledButton("Apply to Target", Vector2.Zero, tt, !data.Valid))
return;
if (_state.GetOrCreate(id, data.Objects[0], out var state))
{
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags();
var design = _converter.Convert(ToDesignData(), applyGear, applyCustomize, applyCrest);
_state.ApplyDesign(design, state, StateChanged.Source.Manual);
}
}
private void DrawAppearanceInfo()
{
if (!ImGui.CollapsingHeader("Appearance Details"))
return;
using var table = ImRaii.Table("Details", 2);
if (!table)
return;
using var style = ImRaii.PushStyle(ImGuiStyleVar.ButtonTextAlign, new Vector2(0, 0.5f));
ImGui.TableSetupColumn("Type", ImGuiTableColumnFlags.WidthFixed, ImGui.CalcTextSize("Last Update Datem").X);
ImGui.TableSetupColumn("Data", ImGuiTableColumnFlags.WidthStretch);
var selection = _selector.Selection;
CopyButton("NPC Name", selection.Name);
CopyButton("NPC ID", selection.Id.Id.ToString());
ImGuiUtil.DrawFrameColumn("NPC Type");
ImGui.TableNextColumn();
var width = ImGui.GetContentRegionAvail().X;
ImGuiUtil.DrawTextButton(selection.Kind is ObjectKind.BattleNpc ? "Battle NPC" : "Event NPC", new Vector2(width, 0),
ImGui.GetColorU32(ImGuiCol.FrameBg));
ImGuiUtil.DrawFrameColumn("Color");
var color = _favorites.GetColor(selection);
var colorName = color.Length == 0 ? DesignColors.AutomaticName : color;
ImGui.TableNextColumn();
if (_colorCombo.Draw("##colorCombo", colorName,
"Associate a color with this NPC appearance. Right-Click to revert to automatic coloring.",
width - ImGui.GetStyle().ItemSpacing.X - ImGui.GetFrameHeight(), ImGui.GetTextLineHeight())
&& _colorCombo.CurrentSelection != null)
{
color = _colorCombo.CurrentSelection is DesignColors.AutomaticName ? string.Empty : _colorCombo.CurrentSelection;
_favorites.SetColor(selection, color);
}
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
{
_favorites.SetColor(selection, string.Empty);
color = string.Empty;
}
if (_colors.TryGetValue(color, out var currentColor))
{
ImGui.SameLine();
if (DesignColorUi.DrawColorButton($"Color associated with {color}", currentColor, out var newColor))
_colors.SetColor(color, newColor);
}
else if (color.Length != 0)
{
ImGui.SameLine();
var size = new Vector2(ImGui.GetFrameHeight());
using var font = ImRaii.PushFont(UiBuilder.IconFont);
ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, 0, _colors.MissingColor);
ImGuiUtil.HoverTooltip("The color associated with this design does not exist.");
}
return;
static void CopyButton(string label, string text)
{
ImGuiUtil.DrawFrameColumn(label);
ImGui.TableNextColumn();
if (ImGui.Button(text, new Vector2(ImGui.GetContentRegionAvail().X, 0)))
ImGui.SetClipboardText(text);
ImGuiUtil.HoverTooltip("Click to copy to clipboard.");
}
}
}

View file

@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Glamourer.GameData;
using ImGuiNET;
using OtterGui;
using OtterGui.Raii;
using ImGuiClip = OtterGui.ImGuiClip;
namespace Glamourer.Gui.Tabs.NpcTab;
public class NpcSelector : IDisposable
{
private readonly NpcCustomizeSet _npcs;
private readonly LocalNpcAppearanceData _favorites;
private NpcFilter _filter;
private readonly List<int> _visibleOrdered = [];
private int _selectedGlobalIndex;
private bool _listDirty = true;
private Vector2 _defaultItemSpacing;
private float _width;
public NpcSelector(NpcCustomizeSet npcs, LocalNpcAppearanceData favorites)
{
_npcs = npcs;
_favorites = favorites;
_filter = new NpcFilter(_favorites);
_favorites.DataChanged += OnFavoriteChange;
}
public void Dispose()
{
_favorites.DataChanged -= OnFavoriteChange;
}
private void OnFavoriteChange()
=> _listDirty = true;
public void UpdateList()
{
if (!_listDirty)
return;
_listDirty = false;
_visibleOrdered.Clear();
var enumerable = _npcs.WithIndex();
if (!_filter.IsEmpty)
enumerable = enumerable.Where(d => _filter.ApplyFilter(d.Value));
var range = enumerable.OrderByDescending(d => _favorites.IsFavorite(d.Value))
.ThenBy(d => d.Index)
.Select(d => d.Index);
_visibleOrdered.AddRange(range);
}
public bool HasSelection
=> _selectedGlobalIndex >= 0 && _selectedGlobalIndex < _npcs.Count;
public NpcData Selection
=> HasSelection ? _npcs[_selectedGlobalIndex] : default;
public void Draw(float width)
{
_width = width;
using var group = ImRaii.Group();
_defaultItemSpacing = ImGui.GetStyle().ItemSpacing;
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, Vector2.Zero)
.Push(ImGuiStyleVar.FrameRounding, 0);
if (_filter.Draw(width))
_listDirty = true;
UpdateList();
DrawSelector();
}
private void DrawSelector()
{
using var child = ImRaii.Child("##Selector", new Vector2(_width, ImGui.GetContentRegionAvail().Y), true);
if (!child)
return;
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, _defaultItemSpacing);
ImGuiClip.ClippedDraw(_visibleOrdered, DrawSelectable, ImGui.GetTextLineHeight());
}
private void DrawSelectable(int globalIndex)
{
using var id = ImRaii.PushId(globalIndex);
using var color = ImRaii.PushColor(ImGuiCol.Text, _favorites.GetData(_npcs[globalIndex]).Color);
if (ImGui.Selectable(_npcs[globalIndex].Name, _selectedGlobalIndex == globalIndex, ImGuiSelectableFlags.AllowItemOverlap))
_selectedGlobalIndex = globalIndex;
}
}

View file

@ -0,0 +1,19 @@
using System;
using Dalamud.Interface.Utility;
using ImGuiNET;
using OtterGui.Widgets;
namespace Glamourer.Gui.Tabs.NpcTab;
public class NpcTab(NpcSelector _selector, NpcPanel _panel) : ITab
{
public ReadOnlySpan<byte> Label
=> "NPCs"u8;
public void DrawContent()
{
_selector.Draw(200 * ImGuiHelpers.GlobalScale);
ImGui.SameLine();
_panel.Draw();
}
}

View file

@ -60,7 +60,7 @@ public class SettingsTab : ITab
if (!child) if (!child)
return; return;
Checkbox("Enable Auto Designs", "Enable the application of designs associated to characters to be applied automatically.", Checkbox("Enable Auto Designs", "Enable the application of designs associated to characters in the Automation tab to be applied automatically.",
_config.EnableAutoDesigns, v => _config.EnableAutoDesigns = v); _config.EnableAutoDesigns, v => _config.EnableAutoDesigns = v);
ImGui.NewLine(); ImGui.NewLine();
ImGui.NewLine(); ImGui.NewLine();
@ -247,22 +247,17 @@ public class SettingsTab : ITab
if (ImGui.Checkbox(code, ref state)) if (ImGui.Checkbox(code, ref state))
{ {
action(state); action(state);
_config.Codes[i] = (code, state); _codeService.SaveState();
_codeService.VerifyState();
_config.Save();
} }
} }
if (_codeService.EnabledCaptain) if (ImGui.Button("Who am I?!?"))
{ _funModule.WhoAmI();
if (ImGui.Button("Who am I?!?"))
_funModule.WhoAmI();
ImGui.SameLine(); ImGui.SameLine();
if (ImGui.Button("Who is that!?!")) if (ImGui.Button("Who is that!?!"))
_funModule.WhoIsThat(); _funModule.WhoIsThat();
}
} }
private void DrawCodeHints() private void DrawCodeHints()

View file

@ -3,7 +3,7 @@ using System.Linq;
using System.Numerics; using System.Numerics;
using Dalamud.Game.Text.SeStringHandling; using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Interface.Utility; using Dalamud.Interface.Utility;
using Glamourer.Customization; using Glamourer.GameData;
using Glamourer.Interop; using Glamourer.Interop;
using Glamourer.Services; using Glamourer.Services;
using Glamourer.Unlocks; using Glamourer.Unlocks;
@ -19,7 +19,7 @@ public class UnlockOverview
{ {
private readonly ItemManager _items; private readonly ItemManager _items;
private readonly ItemUnlockManager _itemUnlocks; private readonly ItemUnlockManager _itemUnlocks;
private readonly CustomizationService _customizations; private readonly CustomizeService _customizations;
private readonly CustomizeUnlockManager _customizeUnlocks; private readonly CustomizeUnlockManager _customizeUnlocks;
private readonly PenumbraChangedItemTooltip _tooltip; private readonly PenumbraChangedItemTooltip _tooltip;
private readonly TextureService _textures; private readonly TextureService _textures;
@ -41,7 +41,7 @@ public class UnlockOverview
foreach (var type in Enum.GetValues<FullEquipType>()) foreach (var type in Enum.GetValues<FullEquipType>())
{ {
if (type.IsOffhandType() || !_items.ItemService.AwaitedService.TryGetValue(type, out var items) || items.Count == 0) if (type.IsOffhandType() || !_items.ItemData.ByType.TryGetValue(type, out var items) || items.Count == 0)
continue; continue;
if (ImGui.Selectable(type.ToName(), _selected1 == type)) if (ImGui.Selectable(type.ToName(), _selected1 == type))
@ -52,25 +52,22 @@ public class UnlockOverview
} }
} }
foreach (var clan in _customizations.AwaitedService.Clans) foreach (var (clan, gender) in CustomizeManager.AllSets())
{ {
foreach (var gender in _customizations.AwaitedService.Genders) if (_customizations.Manager.GetSet(clan, gender).HairStyles.Count == 0)
{ continue;
if (_customizations.AwaitedService.GetList(clan, gender).HairStyles.Count == 0)
continue;
if (ImGui.Selectable($"{(gender is Gender.Male ? '♂' : '♀')} {clan.ToShortName()} Hair & Paint", if (ImGui.Selectable($"{(gender is Gender.Male ? '♂' : '♀')} {clan.ToShortName()} Hair & Paint",
_selected2 == clan && _selected3 == gender)) _selected2 == clan && _selected3 == gender))
{ {
_selected1 = FullEquipType.Unknown; _selected1 = FullEquipType.Unknown;
_selected2 = clan; _selected2 = clan;
_selected3 = gender; _selected3 = gender;
}
} }
} }
} }
public UnlockOverview(ItemManager items, CustomizationService customizations, ItemUnlockManager itemUnlocks, public UnlockOverview(ItemManager items, CustomizeService customizations, ItemUnlockManager itemUnlocks,
CustomizeUnlockManager customizeUnlocks, PenumbraChangedItemTooltip tooltip, TextureService textures, CodeService codes, CustomizeUnlockManager customizeUnlocks, PenumbraChangedItemTooltip tooltip, TextureService textures, CodeService codes,
JobService jobs, FavoriteManager favorites) JobService jobs, FavoriteManager favorites)
{ {
@ -107,7 +104,7 @@ public class UnlockOverview
private void DrawCustomizations() private void DrawCustomizations()
{ {
var set = _customizations.AwaitedService.GetList(_selected2, _selected3); var set = _customizations.Manager.GetSet(_selected2, _selected3);
var spacing = IconSpacing; var spacing = IconSpacing;
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing); using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing);
@ -121,10 +118,10 @@ public class UnlockOverview
continue; continue;
var unlocked = _customizeUnlocks.IsUnlocked(customize, out var time); var unlocked = _customizeUnlocks.IsUnlocked(customize, out var time);
var icon = _customizations.AwaitedService.GetIcon(customize.IconId); var icon = _customizations.Manager.GetIcon(customize.IconId);
ImGui.Image(icon.ImGuiHandle, iconSize, Vector2.Zero, Vector2.One, ImGui.Image(icon.ImGuiHandle, iconSize, Vector2.Zero, Vector2.One,
unlocked || _codes.EnabledShirts ? Vector4.One : UnavailableTint); unlocked || _codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint);
if (ImGui.IsItemHovered()) if (ImGui.IsItemHovered())
{ {
using var tt = ImRaii.Tooltip(); using var tt = ImRaii.Tooltip();
@ -150,7 +147,7 @@ public class UnlockOverview
private void DrawItems() private void DrawItems()
{ {
if (!_items.ItemService.AwaitedService.TryGetValue(_selected1, out var items)) if (!_items.ItemData.ByType.TryGetValue(_selected1, out var items))
return; return;
var spacing = IconSpacing; var spacing = IconSpacing;
@ -160,6 +157,30 @@ public class UnlockOverview
var numRows = (items.Count + iconsPerRow - 1) / iconsPerRow; var numRows = (items.Count + iconsPerRow - 1) / iconsPerRow;
var numVisibleRows = (int)(Math.Ceiling(ImGui.GetContentRegionAvail().Y / (iconSize.Y + spacing.Y)) + 0.5f) + 1; var numVisibleRows = (int)(Math.Ceiling(ImGui.GetContentRegionAvail().Y / (iconSize.Y + spacing.Y)) + 0.5f) + 1;
var skips = ImGuiClip.GetNecessarySkips(iconSize.Y + spacing.Y);
var end = Math.Min(numVisibleRows * iconsPerRow + skips * iconsPerRow, items.Count);
var counter = 0;
for (var idx = skips * iconsPerRow; idx < end; ++idx)
{
DrawItem(items[idx]);
if (counter != iconsPerRow - 1)
{
ImGui.SameLine();
++counter;
}
else
{
counter = 0;
}
}
if (ImGui.GetCursorPosX() != 0)
ImGui.NewLine();
var remainder = numRows - numVisibleRows - skips;
if (remainder > 0)
ImGuiClip.DrawEndDummy(remainder, iconSize.Y + spacing.Y);
return;
void DrawItem(EquipItem item) void DrawItem(EquipItem item)
{ {
var unlocked = _itemUnlocks.IsUnlocked(item.Id, out var time); var unlocked = _itemUnlocks.IsUnlocked(item.Id, out var time);
@ -168,7 +189,7 @@ public class UnlockOverview
var (icon, size) = (iconHandle.ImGuiHandle, new Vector2(iconHandle.Width, iconHandle.Height)); var (icon, size) = (iconHandle.ImGuiHandle, new Vector2(iconHandle.Width, iconHandle.Height));
ImGui.Image(icon, iconSize, Vector2.Zero, Vector2.One, unlocked || _codes.EnabledShirts ? Vector4.One : UnavailableTint); ImGui.Image(icon, iconSize, Vector2.Zero, Vector2.One, unlocked || _codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint);
if (_favorites.Contains(item)) if (_favorites.Contains(item))
ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(), ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(),
2 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 4 * ImGuiHelpers.GlobalScale); 2 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 4 * ImGuiHelpers.GlobalScale);
@ -189,7 +210,7 @@ public class UnlockOverview
ImGui.TextUnformatted($"{item.Type.ToName()} ({slot.ToName()})"); ImGui.TextUnformatted($"{item.Type.ToName()} ({slot.ToName()})");
if (item.Type.ValidOffhand().IsOffhandType()) if (item.Type.ValidOffhand().IsOffhandType())
ImGui.TextUnformatted( ImGui.TextUnformatted(
$"{item.Weapon()}{(_items.ItemService.AwaitedService.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand) ? $" | {offhand.Weapon()}" : string.Empty)}"); $"{item.Weapon()}{(_items.ItemData.TryGetValue(item.ItemId, EquipSlot.OffHand, out var offhand) ? $" | {offhand.Weapon()}" : string.Empty)}");
else else
ImGui.TextUnformatted(slot is EquipSlot.MainHand ? $"{item.Weapon()}" : $"{item.Armor()}"); ImGui.TextUnformatted(slot is EquipSlot.MainHand ? $"{item.Weapon()}" : $"{item.Armor()}");
ImGui.TextUnformatted( ImGui.TextUnformatted(
@ -219,29 +240,6 @@ public class UnlockOverview
_tooltip.CreateTooltip(item, string.Empty, false); _tooltip.CreateTooltip(item, string.Empty, false);
} }
} }
var skips = ImGuiClip.GetNecessarySkips(iconSize.Y + spacing.Y);
var end = Math.Min(numVisibleRows * iconsPerRow + skips * iconsPerRow, items.Count);
var counter = 0;
for (var idx = skips * iconsPerRow; idx < end; ++idx)
{
DrawItem(items[idx]);
if (counter != iconsPerRow - 1)
{
ImGui.SameLine();
++counter;
}
else
{
counter = 0;
}
}
if (ImGui.GetCursorPosX() != 0)
ImGui.NewLine();
var remainder = numRows - numVisibleRows - skips;
if (remainder > 0)
ImGuiClip.DrawEndDummy(remainder, iconSize.Y + spacing.Y);
} }
private static Vector2 IconSpacing private static Vector2 IconSpacing

Some files were not shown because too many files have changed in this diff Show more