mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2026-02-05 23:44:39 +01:00
Merge branch 'main' into Limiana/main
This commit is contained in:
commit
5f28644b56
138 changed files with 3314 additions and 4006 deletions
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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";
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,8 +9,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
|
|||
repo.json = repo.json
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glamourer.GameData", "Glamourer.GameData\Glamourer.GameData.csproj", "{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Glamourer", "Glamourer\Glamourer.csproj", "{01EB903D-871F-4285-A8CF-6486561D5B5B}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.Api", "Penumbra.Api\Penumbra.Api.csproj", "{29C589ED-7AF1-4DE9-82EF-33EBEF19AAFA}"
|
||||
|
|
@ -27,10 +25,6 @@ Global
|
|||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{51F4DDB0-1FA0-4629-9CFE-C55B6062907B}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{01EB903D-871F-4285-A8CF-6486561D5B5B}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
using System.Buffers.Text;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.GameData.Actors;
|
||||
|
||||
|
|
|
|||
92
Glamourer/Api/GlamourerIpc.Set.cs
Normal file
92
Glamourer/Api/GlamourerIpc.Set.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -11,31 +11,34 @@ using Glamourer.Services;
|
|||
using Glamourer.State;
|
||||
using Penumbra.Api.Helpers;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.String;
|
||||
|
||||
namespace Glamourer.Api;
|
||||
|
||||
public partial class GlamourerIpc : IDisposable
|
||||
public sealed partial class GlamourerIpc : IDisposable
|
||||
{
|
||||
public const int CurrentApiVersionMajor = 0;
|
||||
public const int CurrentApiVersionMinor = 4;
|
||||
|
||||
private readonly StateManager _stateManager;
|
||||
private readonly ObjectManager _objects;
|
||||
private readonly ActorService _actors;
|
||||
private readonly ActorManager _actors;
|
||||
private readonly DesignConverter _designConverter;
|
||||
private readonly AutoDesignApplier _autoDesignApplier;
|
||||
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,
|
||||
DesignManager designManager)
|
||||
DesignManager designManager, ItemManager items)
|
||||
{
|
||||
_stateManager = stateManager;
|
||||
_objects = objects;
|
||||
_actors = actors;
|
||||
_designConverter = designConverter;
|
||||
_autoDesignApplier = autoDesignApplier;
|
||||
_items = items;
|
||||
_gPose = gPose;
|
||||
_stateChangedEvent = stateChangedEvent;
|
||||
_designManager = designManager;
|
||||
|
|
@ -82,6 +85,11 @@ public partial class GlamourerIpc : IDisposable
|
|||
_stateChangedProvider = new EventProvider<StateChanged.Type, nint, Lazy<string>>(pi, LabelStateChanged);
|
||||
_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);
|
||||
_gPose.Subscribe(OnGPoseChanged, GPoseService.Priority.GlamourerIpc);
|
||||
|
||||
|
|
@ -126,6 +134,9 @@ public partial class GlamourerIpc : IDisposable
|
|||
_gPoseChangedProvider.Dispose();
|
||||
|
||||
_getDesignListProvider.Dispose();
|
||||
|
||||
_setItemProvider.Dispose();
|
||||
_setItemByActorNameProvider.Dispose();
|
||||
}
|
||||
|
||||
private IEnumerable<ActorIdentifier> FindActors(string actorName)
|
||||
|
|
@ -154,7 +165,7 @@ public partial class GlamourerIpc : IDisposable
|
|||
|
||||
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)
|
||||
yield break;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
using System;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Automation;
|
||||
|
||||
|
|
@ -74,7 +74,7 @@ public class AutoDesign
|
|||
var ret = new JObject
|
||||
{
|
||||
["Gearset"] = GearsetIndex,
|
||||
["JobGroup"] = Jobs.Id,
|
||||
["JobGroup"] = Jobs.Id.Id,
|
||||
};
|
||||
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -4,18 +4,16 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using System.Linq;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using Glamourer.Unlocks;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
|
|
@ -28,8 +26,8 @@ public class AutoDesignApplier : IDisposable
|
|||
private readonly StateManager _state;
|
||||
private readonly JobService _jobs;
|
||||
private readonly EquippedGearset _equippedGearset;
|
||||
private readonly ActorService _actors;
|
||||
private readonly CustomizationService _customizations;
|
||||
private readonly ActorManager _actors;
|
||||
private readonly CustomizeService _customizations;
|
||||
private readonly CustomizeUnlockManager _customizeUnlocks;
|
||||
private readonly ItemUnlockManager _itemUnlocks;
|
||||
private readonly AutomationChanged _event;
|
||||
|
|
@ -50,7 +48,7 @@ public class AutoDesignApplier : IDisposable
|
|||
}
|
||||
|
||||
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,
|
||||
EquippedGearset equippedGearset)
|
||||
{
|
||||
|
|
@ -87,7 +85,7 @@ public class AutoDesignApplier : IDisposable
|
|||
if (_jobChangeState == null || !_config.EnableAutoDesigns)
|
||||
return;
|
||||
|
||||
var id = actor.GetIdentifier(_actors.AwaitedService);
|
||||
var id = actor.GetIdentifier(_actors);
|
||||
if (id == _jobChangeState.Identifier)
|
||||
{
|
||||
var current = _jobChangeState.BaseData.Item(slot);
|
||||
|
|
@ -161,7 +159,7 @@ public class AutoDesignApplier : IDisposable
|
|||
{
|
||||
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))
|
||||
{
|
||||
Reduce(actor, state, newSet, false, false);
|
||||
|
|
@ -203,13 +201,13 @@ public class AutoDesignApplier : IDisposable
|
|||
|
||||
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;
|
||||
|
||||
if (!GetPlayerSet(id, out var set))
|
||||
{
|
||||
if (_state.TryGetValue(id, out var s))
|
||||
s.LastJob = (byte)newJob.Id;
|
||||
s.LastJob = newJob.Id;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -312,13 +310,13 @@ public class AutoDesignApplier : IDisposable
|
|||
if (_manager.EnabledSets.TryGetValue(identifier, out set))
|
||||
return true;
|
||||
|
||||
identifier = _actors.AwaitedService.CreatePlayer(identifier.PlayerName, ushort.MaxValue);
|
||||
identifier = _actors.CreatePlayer(identifier.PlayerName, ushort.MaxValue);
|
||||
return _manager.EnabledSets.TryGetValue(identifier, out set);
|
||||
case IdentifierType.Retainer:
|
||||
case IdentifierType.Npc:
|
||||
return _manager.EnabledSets.TryGetValue(identifier, out set);
|
||||
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);
|
||||
default:
|
||||
set = null;
|
||||
|
|
@ -470,7 +468,7 @@ public class AutoDesignApplier : IDisposable
|
|||
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;
|
||||
foreach (var index in Enum.GetValues<CustomizeIndex>())
|
||||
{
|
||||
|
|
@ -479,7 +477,7 @@ public class AutoDesignApplier : IDisposable
|
|||
continue;
|
||||
|
||||
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 _))
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -10,13 +10,14 @@ using Glamourer.Designs;
|
|||
using Glamourer.Events;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Filesystem;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Automation;
|
||||
|
||||
|
|
@ -28,17 +29,17 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
|||
|
||||
private readonly JobService _jobs;
|
||||
private readonly DesignManager _designs;
|
||||
private readonly ActorService _actors;
|
||||
private readonly ActorManager _actors;
|
||||
private readonly AutomationChanged _event;
|
||||
private readonly DesignChanged _designEvent;
|
||||
|
||||
private readonly List<AutoDesignSet> _data = new();
|
||||
private readonly Dictionary<ActorIdentifier, AutoDesignSet> _enabled = new();
|
||||
private readonly List<AutoDesignSet> _data = [];
|
||||
private readonly Dictionary<ActorIdentifier, AutoDesignSet> _enabled = [];
|
||||
|
||||
public IReadOnlyDictionary<ActorIdentifier, AutoDesignSet> EnabledSets
|
||||
=> _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)
|
||||
{
|
||||
_jobs = jobs;
|
||||
|
|
@ -419,7 +420,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
|||
continue;
|
||||
}
|
||||
|
||||
var id = _actors.AwaitedService.FromJson(obj["Identifier"] as JObject);
|
||||
var id = _actors.FromJson(obj["Identifier"] as JObject);
|
||||
if (!IdentifierValid(id, out var group))
|
||||
{
|
||||
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;
|
||||
if (jobs >= 0)
|
||||
{
|
||||
if (!_jobs.JobGroups.TryGetValue((ushort)jobs, out var jobGroup))
|
||||
if (!_jobs.JobGroups.TryGetValue((JobGroupId)jobs, out var jobGroup))
|
||||
{
|
||||
Glamourer.Messager.NotificationMessage(
|
||||
$"Error parsing automatically applied design for set {setName}: The job condition {jobs} does not exist.",
|
||||
|
|
@ -562,9 +563,9 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
|||
var name = manager.Data.ToName(identifier.Kind, identifier.DataId);
|
||||
var table = identifier.Kind switch
|
||||
{
|
||||
ObjectKind.BattleNpc => manager.Data.BNpcs,
|
||||
ObjectKind.BattleNpc => (IReadOnlyDictionary<NpcId, string>)manager.Data.BNpcs,
|
||||
ObjectKind.EventNpc => manager.Data.ENpcs,
|
||||
_ => new Dictionary<uint, string>(),
|
||||
_ => new Dictionary<NpcId, string>(),
|
||||
};
|
||||
return table.Where(kvp => kvp.Value == name)
|
||||
.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[]
|
||||
{
|
||||
_actors.AwaitedService.CreateRetainer(identifier.PlayerName,
|
||||
_actors.CreateRetainer(identifier.PlayerName,
|
||||
identifier.Retainer == ActorIdentifier.RetainerType.Mannequin
|
||||
? ActorIdentifier.RetainerType.Mannequin
|
||||
: ActorIdentifier.RetainerType.Bell).CreatePermanent(),
|
||||
},
|
||||
IdentifierType.Npc => CreateNpcs(_actors.AwaitedService, identifier),
|
||||
IdentifierType.Npc => CreateNpcs(_actors, identifier),
|
||||
_ => Array.Empty<ActorIdentifier>(),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,59 +3,51 @@ using System.Linq;
|
|||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.String;
|
||||
|
||||
namespace Glamourer.Automation;
|
||||
|
||||
public class FixedDesignMigrator
|
||||
public class FixedDesignMigrator(JobService jobs)
|
||||
{
|
||||
private readonly JobService _jobs;
|
||||
private List<(string Name, List<(string, JobGroup, bool)> Data)>? _migratedData;
|
||||
private List<(string Name, List<(string, JobGroup, bool)> Data)>? _migratedData;
|
||||
|
||||
public FixedDesignMigrator(JobService jobs)
|
||||
=> _jobs = jobs;
|
||||
|
||||
public void ConsumeMigratedData(ActorService actors, DesignFileSystem designFileSystem, AutoDesignManager autoManager)
|
||||
public void ConsumeMigratedData(ActorManager actors, DesignFileSystem designFileSystem, AutoDesignManager autoManager)
|
||||
{
|
||||
if (_migratedData == null)
|
||||
return;
|
||||
|
||||
foreach (var data in _migratedData)
|
||||
foreach (var (name, data) in _migratedData)
|
||||
{
|
||||
var allEnabled = true;
|
||||
var name = data.Name;
|
||||
if (autoManager.Any(d => name == d.Name))
|
||||
continue;
|
||||
|
||||
var id = ActorIdentifier.Invalid;
|
||||
if (ByteString.FromString(data.Name, out var byteString, false))
|
||||
if (ByteString.FromString(name, out var byteString))
|
||||
{
|
||||
id = actors.AwaitedService.CreatePlayer(byteString, ushort.MaxValue);
|
||||
id = actors.CreatePlayer(byteString, ushort.MaxValue);
|
||||
if (!id.IsValid)
|
||||
id = actors.AwaitedService.CreateRetainer(byteString, ActorIdentifier.RetainerType.Both);
|
||||
id = actors.CreateRetainer(byteString, ActorIdentifier.RetainerType.Both);
|
||||
}
|
||||
|
||||
if (!id.IsValid)
|
||||
{
|
||||
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)
|
||||
{
|
||||
Glamourer.Messager.NotificationMessage($"Could not migrate fixed design {data.Name}.", NotificationType.Error);
|
||||
allEnabled = false;
|
||||
Glamourer.Messager.NotificationMessage($"Could not migrate fixed design {name}.", NotificationType.Error);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
autoManager.AddDesignSet(name, id);
|
||||
autoManager.SetState(autoManager.Count - 1, allEnabled);
|
||||
autoManager.SetState(autoManager.Count - 1, true);
|
||||
var set = autoManager[^1];
|
||||
foreach (var design in data.Data.AsEnumerable().Reverse())
|
||||
foreach (var design in data.AsEnumerable().Reverse())
|
||||
{
|
||||
if (!designFileSystem.Find(design.Item1, out var child) || child is not DesignFileSystem.Leaf leaf)
|
||||
{
|
||||
|
|
@ -96,7 +88,7 @@ public class FixedDesignMigrator
|
|||
}
|
||||
|
||||
var job = obj["JobGroups"]?.ToObject<int>() ?? -1;
|
||||
if (job < 0 || !_jobs.JobGroups.TryGetValue((ushort)job, out var group))
|
||||
if (job < 0 || !jobs.JobGroups.TryGetValue((JobGroupId)job, out var group))
|
||||
{
|
||||
Glamourer.Messager.NotificationMessage("Could not semi-migrate fixed design: Invalid job group specified.",
|
||||
NotificationType.Warning);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ public sealed class Design : DesignBase, ISavable
|
|||
{
|
||||
#region Data
|
||||
|
||||
internal Design(CustomizationService customize, ItemManager items)
|
||||
internal Design(CustomizeService customize, ItemManager items)
|
||||
: base(customize, items)
|
||||
{ }
|
||||
|
||||
|
|
@ -98,7 +98,7 @@ public sealed class Design : DesignBase, ISavable
|
|||
|
||||
#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;
|
||||
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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
|
|
@ -26,35 +25,35 @@ public class DesignBase
|
|||
public ref DesignData GetDesignDataRef()
|
||||
=> ref _designData;
|
||||
|
||||
internal DesignBase(CustomizationService customize, ItemManager items)
|
||||
internal DesignBase(CustomizeService customize, ItemManager 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;
|
||||
ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant;
|
||||
ApplyEquip = equipFlags & EquipFlagExtensions.All;
|
||||
_designFlags = 0;
|
||||
CustomizationSet = SetCustomizationSet(customize);
|
||||
_designData = designData;
|
||||
ApplyCustomize = customizeFlags & CustomizeFlagExtensions.AllRelevant;
|
||||
ApplyEquip = equipFlags & EquipFlagExtensions.All;
|
||||
_designFlags = 0;
|
||||
CustomizeSet = SetCustomizationSet(customize);
|
||||
}
|
||||
|
||||
internal DesignBase(DesignBase clone)
|
||||
{
|
||||
_designData = clone._designData;
|
||||
CustomizationSet = clone.CustomizationSet;
|
||||
ApplyCustomize = clone.ApplyCustomizeRaw;
|
||||
ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All;
|
||||
_designFlags = clone._designFlags & (DesignFlags)0x0F;
|
||||
_designData = clone._designData;
|
||||
CustomizeSet = clone.CustomizeSet;
|
||||
ApplyCustomize = clone.ApplyCustomizeRaw;
|
||||
ApplyEquip = clone.ApplyEquip & EquipFlagExtensions.All;
|
||||
_designFlags = clone._designFlags & (DesignFlags)0x0F;
|
||||
}
|
||||
|
||||
/// <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;
|
||||
CustomizationSet = SetCustomizationSet(customize);
|
||||
_designData = other;
|
||||
CustomizeSet = SetCustomizationSet(customize);
|
||||
}
|
||||
|
||||
#region Application Data
|
||||
|
|
@ -69,15 +68,18 @@ public class DesignBase
|
|||
WriteProtected = 0x10,
|
||||
}
|
||||
|
||||
private CustomizeFlag _applyCustomize = CustomizeFlagExtensions.AllRelevant;
|
||||
public CustomizationSet CustomizationSet { get; private set; }
|
||||
private CustomizeFlag _applyCustomize = CustomizeFlagExtensions.AllRelevant;
|
||||
public CustomizeSet CustomizeSet { get; private set; }
|
||||
|
||||
internal CustomizeFlag ApplyCustomize
|
||||
{
|
||||
get => _applyCustomize.FixApplication(CustomizationSet);
|
||||
set => _applyCustomize = value & CustomizeFlagExtensions.AllRelevant;
|
||||
get => _applyCustomize.FixApplication(CustomizeSet);
|
||||
set => _applyCustomize = (value & CustomizeFlagExtensions.AllRelevant) | CustomizeFlag.BodyType;
|
||||
}
|
||||
|
||||
internal CustomizeFlag ApplyCustomizeExcludingBodyType
|
||||
=> _applyCustomize.FixApplication(CustomizeSet) & ~CustomizeFlag.BodyType;
|
||||
|
||||
internal CustomizeFlag ApplyCustomizeRaw
|
||||
=> _applyCustomize;
|
||||
|
||||
|
|
@ -85,13 +87,13 @@ public class DesignBase
|
|||
internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant;
|
||||
private DesignFlags _designFlags = DesignFlags.ApplyHatVisible | DesignFlags.ApplyVisorState | DesignFlags.ApplyWeaponVisible;
|
||||
|
||||
public bool SetCustomize(CustomizationService customizationService, Customize customize)
|
||||
public bool SetCustomize(CustomizeService customizeService, CustomizeArray customize)
|
||||
{
|
||||
if (customize.Equals(_designData.Customize))
|
||||
return false;
|
||||
|
||||
_designData.Customize.Load(customize);
|
||||
CustomizationSet = customizationService.AwaitedService.GetList(customize.Clan, customize.Gender);
|
||||
_designData.Customize = customize;
|
||||
CustomizeSet = customizeService.Manager.GetSet(customize.Clan, customize.Gender);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -241,10 +243,10 @@ public class DesignBase
|
|||
}
|
||||
}
|
||||
|
||||
private CustomizationSet SetCustomizationSet(CustomizationService customize)
|
||||
private CustomizeSet SetCustomizationSet(CustomizeService customize)
|
||||
=> !_designData.IsHuman
|
||||
? customize.AwaitedService.GetList(SubRace.Midlander, Gender.Male)
|
||||
: customize.AwaitedService.GetList(_designData.Customize.Clan, _designData.Customize.Gender);
|
||||
? customize.Manager.GetSet(SubRace.Midlander, Gender.Male)
|
||||
: customize.Manager.GetSet(_designData.Customize.Clan, _designData.Customize.Gender);
|
||||
|
||||
#endregion
|
||||
|
||||
|
|
@ -331,7 +333,7 @@ public class DesignBase
|
|||
|
||||
#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;
|
||||
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);
|
||||
LoadCustomize(customizations, json["Customize"], ret, "Temporary Design", false, true);
|
||||
|
|
@ -436,14 +438,14 @@ public class DesignBase
|
|||
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)
|
||||
{
|
||||
if (json == null)
|
||||
{
|
||||
design._designData.ModelId = 0;
|
||||
design._designData.IsHuman = true;
|
||||
design.SetCustomize(customizations, Customize.Default);
|
||||
design.SetCustomize(customizations, CustomizeArray.Default);
|
||||
Glamourer.Messager.NotificationMessage("The loaded design does not contain any customization data, reset to default.",
|
||||
NotificationType.Warning);
|
||||
return;
|
||||
|
|
@ -474,7 +476,7 @@ public class DesignBase
|
|||
{
|
||||
var arrayText = json["Array"]?.ToObject<string>() ?? string.Empty;
|
||||
design._designData.Customize.LoadBase64(arrayText);
|
||||
design.CustomizationSet = design.SetCustomizationSet(customizations);
|
||||
design.CustomizeSet = design.SetCustomizationSet(customizations);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -486,18 +488,18 @@ public class DesignBase
|
|||
design._designData.Customize.Race = race;
|
||||
design._designData.Customize.Clan = clan;
|
||||
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.Clan, json[CustomizeIndex.Clan.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)
|
||||
{
|
||||
var tok = json[idx.ToString()];
|
||||
var data = (CustomizeValue)(tok?["Value"]?.ToObject<byte>() ?? 0);
|
||||
if (set.IsAvailable(idx))
|
||||
PrintWarning(CustomizationService.ValidateCustomizeValue(set, design._designData.Customize.Face, idx, data, out data,
|
||||
if (set.IsAvailable(idx) && design._designData.Customize.BodyType == 1)
|
||||
PrintWarning(CustomizeService.ValidateCustomizeValue(set, design._designData.Customize.Face, idx, data, out data,
|
||||
allowUnknown));
|
||||
var apply = tok?["Apply"]?.ToObject<bool>() ?? false;
|
||||
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
|
||||
{
|
||||
|
|
@ -519,7 +521,7 @@ public class DesignBase
|
|||
SetApplyVisorToggle(applyVisor);
|
||||
SetApplyWeaponVisible(applyWeapon);
|
||||
SetApplyWetness(true);
|
||||
CustomizationSet = SetCustomizationSet(customize);
|
||||
CustomizeSet = SetCustomizationSet(customize);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
using System;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using OtterGui;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
|
|
@ -62,7 +60,7 @@ public class DesignBase64Migration
|
|||
data.SetHatVisible((bytes[90] & 0x01) == 0);
|
||||
data.SetVisor((bytes[90] & 0x10) != 0);
|
||||
data.SetWeaponVisible((bytes[90] & 0x02) == 0);
|
||||
data.ModelId = (uint)bytes[91] | ((uint)bytes[92] << 8) | ((uint)bytes[93] << 16) | ((uint)bytes[94] << 24);
|
||||
data.ModelId = bytes[91] | ((uint)bytes[92] << 8) | ((uint)bytes[93] << 16) | ((uint)bytes[94] << 24);
|
||||
break;
|
||||
}
|
||||
case 5:
|
||||
|
|
@ -73,7 +71,7 @@ public class DesignBase64Migration
|
|||
data.SetHatVisible((bytes[90] & 0x01) == 0);
|
||||
data.SetVisor((bytes[90] & 0x10) != 0);
|
||||
data.SetWeaponVisible((bytes[90] & 0x02) == 0);
|
||||
data.ModelId = (uint)bytes[91] | ((uint)bytes[92] << 8) | ((uint)bytes[93] << 16) | ((uint)bytes[94] << 24);
|
||||
data.ModelId = bytes[91] | ((uint)bytes[92] << 8) | ((uint)bytes[93] << 16) | ((uint)bytes[94] << 24);
|
||||
break;
|
||||
default: throw new Exception($"Can not parse Base64 string into design for migration:\n\tInvalid Version {bytes[0]}.");
|
||||
}
|
||||
|
|
@ -102,11 +100,11 @@ public class DesignBase64Migration
|
|||
|
||||
if (!humans.IsHuman(data.ModelId))
|
||||
{
|
||||
data.LoadNonHuman(data.ModelId, *(Customize*)(ptr + 4), (nint)eq);
|
||||
data.LoadNonHuman(data.ModelId, *(CustomizeArray*)(ptr + 4), (nint)eq);
|
||||
return data;
|
||||
}
|
||||
|
||||
data.Customize.Load(*(Customize*)(ptr + 4));
|
||||
data.Customize = *(CustomizeArray*)(ptr + 4);
|
||||
foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex())
|
||||
{
|
||||
var mdl = eq[idx];
|
||||
|
|
@ -121,9 +119,9 @@ public class DesignBase64Migration
|
|||
data.SetStain(slot, mdl.Stain);
|
||||
}
|
||||
|
||||
var main = cur[0].Set.Id == 0
|
||||
var main = cur[0].Skeleton.Id == 0
|
||||
? 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)
|
||||
{
|
||||
Glamourer.Log.Warning("Base64 string invalid, weapon could not be identified.");
|
||||
|
|
@ -135,10 +133,10 @@ public class DesignBase64Migration
|
|||
|
||||
EquipItem off;
|
||||
// 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);
|
||||
var gauntlet = items.Identify(EquipSlot.Hands, cur[1].Set, (Variant)cur[1].Type.Id);
|
||||
off = items.Identify(EquipSlot.OffHand, (PrimaryId)(main.PrimaryId.Id + 50), main.SecondaryId, main.Variant, main.Type);
|
||||
var gauntlet = items.Identify(EquipSlot.Hands, cur[1].Skeleton, (Variant)cur[1].Weapon.Id);
|
||||
if (gauntlet.Valid)
|
||||
{
|
||||
data.SetItem(EquipSlot.Hands, gauntlet);
|
||||
|
|
@ -147,9 +145,9 @@ public class DesignBase64Migration
|
|||
}
|
||||
else
|
||||
{
|
||||
off = cur[0].Set.Id == 0
|
||||
off = cur[0].Skeleton.Id == 0
|
||||
? 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)
|
||||
|
|
@ -187,7 +185,7 @@ public class DesignBase64Migration
|
|||
| (equipFlags.HasFlag(EquipFlag.Wrist) ? 0x02 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.RFinger) ? 0x04 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.LFinger) ? 0x08 : 0));
|
||||
save.Customize.Write((nint)data + 4);
|
||||
save.Customize.Write(data + 4);
|
||||
((CharacterWeapon*)(data + 30))[0] = save.Item(EquipSlot.MainHand).Weapon(save.Stain(EquipSlot.MainHand));
|
||||
((CharacterWeapon*)(data + 30))[1] = save.Item(EquipSlot.OffHand).Weapon(save.Stain(EquipSlot.OffHand));
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
|
|
|
|||
|
|
@ -26,9 +26,9 @@ public class DesignColorUi
|
|||
|
||||
public DesignColorUi(DesignColors colors, DesignManager designs, Configuration config)
|
||||
{
|
||||
_colors = colors;
|
||||
_designs = designs;
|
||||
_config = config;
|
||||
_colors = colors;
|
||||
_designs = designs;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
|
|
@ -78,7 +78,7 @@ public class DesignColorUi
|
|||
{
|
||||
using var id = ImRaii.PushId(idx);
|
||||
ImGui.TableNextColumn();
|
||||
|
||||
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.Trash.ToIconString(), buttonSize, tt, disabled, true))
|
||||
{
|
||||
changeString = name;
|
||||
|
|
@ -119,7 +119,7 @@ public class DesignColorUi
|
|||
changeString = _newName;
|
||||
changeValue = 0xFFFFFFFF;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (changeString.Length > 0)
|
||||
{
|
||||
|
|
@ -139,6 +139,7 @@ public class DesignColorUi
|
|||
newColor = color;
|
||||
return false;
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip(tooltip);
|
||||
|
||||
newColor = ImGui.ColorConvertFloat4ToU32(vec);
|
||||
|
|
@ -148,12 +149,12 @@ public class DesignColorUi
|
|||
|
||||
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 uint MissingColorDefault = 0xFF0000D0;
|
||||
|
||||
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 event Action? ColorChanged;
|
||||
|
|
@ -281,7 +282,7 @@ public class DesignColors : ISavable, IReadOnlyDictionary<string, uint>
|
|||
|
||||
public static uint AutoColor(DesignBase design)
|
||||
{
|
||||
var customize = design.ApplyCustomize == 0;
|
||||
var customize = design.ApplyCustomizeExcludingBodyType == 0;
|
||||
var equip = design.ApplyEquip == 0;
|
||||
return (customize, equip) switch
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,20 +2,18 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using Glamourer.Utility;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
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;
|
||||
|
||||
|
|
@ -41,12 +39,18 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
|
|||
=> ShareBase64(state, EquipFlagExtensions.All, CustomizeFlagExtensions.All, CrestExtensions.All);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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();
|
||||
design.ApplyEquip = equipFlags & EquipFlagExtensions.All;
|
||||
|
|
@ -56,7 +60,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
|
|||
design.SetApplyVisorToggle(design.DoApplyEquip(EquipSlot.Head));
|
||||
design.SetApplyWeaponVisible(design.DoApplyEquip(EquipSlot.MainHand) || design.DoApplyEquip(EquipSlot.OffHand));
|
||||
design.SetApplyWetness(true);
|
||||
design.SetDesignData(_customize, state.ModelData);
|
||||
design.SetDesignData(_customize, data);
|
||||
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,
|
||||
CharacterWeapon mainhand, CharacterWeapon offhand)
|
||||
CharacterWeapon mainhand, CharacterWeapon offhand, bool skipWarnings)
|
||||
{
|
||||
if (armors.Count != 10)
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
yield return (slot, item, armor.Stain);
|
||||
}
|
||||
|
||||
var mh = _items.Identify(EquipSlot.MainHand, mainhand.Set, mainhand.Type, mainhand.Variant, FullEquipType.Unknown);
|
||||
if (!mh.Valid)
|
||||
var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant);
|
||||
if (!skipWarnings && !mh.Valid)
|
||||
{
|
||||
Glamourer.Log.Warning($"Appearance data {mainhand} for mainhand weapon invalid, item could not be identified.");
|
||||
mh = _items.DefaultSword;
|
||||
|
|
@ -174,8 +179,8 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
|
|||
|
||||
yield return (EquipSlot.MainHand, mh, mainhand.Stain);
|
||||
|
||||
var oh = _items.Identify(EquipSlot.OffHand, offhand.Set, offhand.Type, offhand.Variant, mh.Type);
|
||||
if (!oh.Valid)
|
||||
var oh = _items.Identify(EquipSlot.OffHand, offhand.Skeleton, offhand.Weapon, offhand.Variant, mh.Type);
|
||||
if (!skipWarnings && !oh.Valid)
|
||||
{
|
||||
Glamourer.Log.Warning($"Appearance data {offhand} for offhand weapon invalid, item could not be identified.");
|
||||
oh = _items.GetDefaultOffhand(mh);
|
||||
|
|
|
|||
|
|
@ -1,42 +1,39 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.String.Functions;
|
||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||
|
||||
namespace Glamourer.Designs;
|
||||
|
||||
public unsafe struct DesignData
|
||||
{
|
||||
private string _nameHead = string.Empty;
|
||||
private string _nameBody = string.Empty;
|
||||
private string _nameHands = string.Empty;
|
||||
private string _nameLegs = string.Empty;
|
||||
private string _nameFeet = string.Empty;
|
||||
private string _nameEars = string.Empty;
|
||||
private string _nameNeck = string.Empty;
|
||||
private string _nameWrists = string.Empty;
|
||||
private string _nameRFinger = string.Empty;
|
||||
private string _nameLFinger = string.Empty;
|
||||
private string _nameMainhand = string.Empty;
|
||||
private string _nameOffhand = string.Empty;
|
||||
private fixed uint _itemIds[12];
|
||||
private fixed ushort _iconIds[12];
|
||||
private fixed byte _equipmentBytes[48];
|
||||
public Customize Customize = Customize.Default;
|
||||
public uint ModelId;
|
||||
public CrestFlag CrestVisibility;
|
||||
private WeaponType _secondaryMainhand;
|
||||
private WeaponType _secondaryOffhand;
|
||||
private FullEquipType _typeMainhand;
|
||||
private FullEquipType _typeOffhand;
|
||||
private byte _states;
|
||||
public bool IsHuman = true;
|
||||
private string _nameHead = string.Empty;
|
||||
private string _nameBody = string.Empty;
|
||||
private string _nameHands = string.Empty;
|
||||
private string _nameLegs = string.Empty;
|
||||
private string _nameFeet = string.Empty;
|
||||
private string _nameEars = string.Empty;
|
||||
private string _nameNeck = string.Empty;
|
||||
private string _nameWrists = string.Empty;
|
||||
private string _nameRFinger = string.Empty;
|
||||
private string _nameLFinger = string.Empty;
|
||||
private string _nameMainhand = string.Empty;
|
||||
private string _nameOffhand = string.Empty;
|
||||
private fixed uint _itemIds[12];
|
||||
private fixed ushort _iconIds[12];
|
||||
private fixed byte _equipmentBytes[48];
|
||||
public CustomizeArray Customize = CustomizeArray.Default;
|
||||
public uint ModelId;
|
||||
public CrestFlag CrestVisibility;
|
||||
private SecondaryId _secondaryMainhand;
|
||||
private SecondaryId _secondaryOffhand;
|
||||
private FullEquipType _typeMainhand;
|
||||
private FullEquipType _typeOffhand;
|
||||
private byte _states;
|
||||
public bool IsHuman = true;
|
||||
|
||||
public DesignData()
|
||||
{ }
|
||||
|
|
@ -75,18 +72,18 @@ public unsafe struct DesignData
|
|||
=> slot.ToIndex() switch
|
||||
{
|
||||
// @formatter:off
|
||||
0 => EquipItem.FromIds(_itemIds[ 0], _iconIds[ 0], (SetId)(_equipmentBytes[ 0] | (_equipmentBytes[ 1] << 8)), (WeaponType)0, _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 ),
|
||||
2 => EquipItem.FromIds(_itemIds[ 2], _iconIds[ 2], (SetId)(_equipmentBytes[ 8] | (_equipmentBytes[ 9] << 8)), (WeaponType)0, _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 ),
|
||||
4 => EquipItem.FromIds(_itemIds[ 4], _iconIds[ 4], (SetId)(_equipmentBytes[16] | (_equipmentBytes[17] << 8)), (WeaponType)0, _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 ),
|
||||
6 => EquipItem.FromIds(_itemIds[ 6], _iconIds[ 6], (SetId)(_equipmentBytes[24] | (_equipmentBytes[25] << 8)), (WeaponType)0, _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 ),
|
||||
8 => EquipItem.FromIds(_itemIds[ 8], _iconIds[ 8], (SetId)(_equipmentBytes[32] | (_equipmentBytes[33] << 8)), (WeaponType)0, _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 ),
|
||||
10 => EquipItem.FromIds(_itemIds[10], _iconIds[10], (SetId)(_equipmentBytes[40] | (_equipmentBytes[41] << 8)), _secondaryMainhand, _equipmentBytes[42], _typeMainhand, name: _nameMainhand),
|
||||
11 => EquipItem.FromIds(_itemIds[11], _iconIds[11], (SetId)(_equipmentBytes[44] | (_equipmentBytes[45] << 8)), _secondaryOffhand, _equipmentBytes[46], _typeOffhand, name: _nameOffhand ),
|
||||
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((ItemId)_itemIds[ 1], (IconId)_iconIds[ 1], (PrimaryId)(_equipmentBytes[ 4] | (_equipmentBytes[ 5] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[ 6], FullEquipType.Body, name: _nameBody ),
|
||||
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((ItemId)_itemIds[ 3], (IconId)_iconIds[ 3], (PrimaryId)(_equipmentBytes[12] | (_equipmentBytes[13] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[14], FullEquipType.Legs, name: _nameLegs ),
|
||||
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((ItemId)_itemIds[ 5], (IconId)_iconIds[ 5], (PrimaryId)(_equipmentBytes[20] | (_equipmentBytes[21] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[22], FullEquipType.Ears, name: _nameEars ),
|
||||
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((ItemId)_itemIds[ 7], (IconId)_iconIds[ 7], (PrimaryId)(_equipmentBytes[28] | (_equipmentBytes[29] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[30], FullEquipType.Wrists, name: _nameWrists ),
|
||||
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((ItemId)_itemIds[ 9], (IconId)_iconIds[ 9], (PrimaryId)(_equipmentBytes[36] | (_equipmentBytes[37] << 8)), (SecondaryId)0, (Variant)_equipmentBytes[38], FullEquipType.Finger, name: _nameLFinger ),
|
||||
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((ItemId)_itemIds[11], (IconId)_iconIds[11], (PrimaryId)(_equipmentBytes[44] | (_equipmentBytes[45] << 8)), _secondaryOffhand, (Variant)_equipmentBytes[46], _typeOffhand, name: _nameOffhand ),
|
||||
_ => new EquipItem(),
|
||||
// @formatter:on
|
||||
};
|
||||
|
|
@ -129,8 +126,8 @@ public unsafe struct DesignData
|
|||
|
||||
_itemIds[index] = item.ItemId.Id;
|
||||
_iconIds[index] = item.IconId.Id;
|
||||
_equipmentBytes[4 * index + 0] = (byte)item.ModelId.Id;
|
||||
_equipmentBytes[4 * index + 1] = (byte)(item.ModelId.Id >> 8);
|
||||
_equipmentBytes[4 * index + 0] = (byte)item.PrimaryId.Id;
|
||||
_equipmentBytes[4 * index + 1] = (byte)(item.PrimaryId.Id >> 8);
|
||||
_equipmentBytes[4 * index + 2] = item.Variant.Id;
|
||||
switch (index)
|
||||
{
|
||||
|
|
@ -148,12 +145,12 @@ public unsafe struct DesignData
|
|||
// @formatter:on
|
||||
case 10:
|
||||
_nameMainhand = item.Name;
|
||||
_secondaryMainhand = item.WeaponType;
|
||||
_secondaryMainhand = item.SecondaryId;
|
||||
_typeMainhand = item.Type;
|
||||
return true;
|
||||
case 11:
|
||||
_nameOffhand = item.Name;
|
||||
_secondaryOffhand = item.WeaponType;
|
||||
_secondaryOffhand = item.SecondaryId;
|
||||
_typeOffhand = item.Type;
|
||||
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;
|
||||
IsHuman = false;
|
||||
Customize.Load(customize);
|
||||
Customize.Read(customize.Data);
|
||||
fixed (byte* ptr = _equipmentBytes)
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked(ptr, (byte*)equipData, 40);
|
||||
|
|
@ -294,8 +291,8 @@ public unsafe struct DesignData
|
|||
|
||||
public readonly byte[] GetCustomizeBytes()
|
||||
{
|
||||
var ret = new byte[CustomizeData.Size];
|
||||
fixed (byte* retPtr = ret, inPtr = Customize.Data.Data)
|
||||
var ret = new byte[CustomizeArray.Size];
|
||||
fixed (byte* retPtr = ret, inPtr = Customize.Data)
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked(retPtr, inPtr, ret.Length);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Utility;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
|
|
@ -20,18 +22,18 @@ namespace Glamourer.Designs;
|
|||
|
||||
public class DesignManager
|
||||
{
|
||||
private readonly CustomizationService _customizations;
|
||||
private readonly CustomizeService _customizations;
|
||||
private readonly ItemManager _items;
|
||||
private readonly HumanModelList _humans;
|
||||
private readonly SaveService _saveService;
|
||||
private readonly DesignChanged _event;
|
||||
private readonly List<Design> _designs = new();
|
||||
private readonly Dictionary<Guid, DesignData> _undoStore = new();
|
||||
private readonly List<Design> _designs = [];
|
||||
private readonly Dictionary<Guid, DesignData> _undoStore = [];
|
||||
|
||||
public IReadOnlyList<Design> Designs
|
||||
=> _designs;
|
||||
|
||||
public DesignManager(SaveService saveService, ItemManager items, CustomizationService customizations,
|
||||
public DesignManager(SaveService saveService, ItemManager items, CustomizeService customizations,
|
||||
DesignChanged @event, HumanModelList humans)
|
||||
{
|
||||
_saveService = saveService;
|
||||
|
|
@ -50,29 +52,44 @@ public class DesignManager
|
|||
/// </summary>
|
||||
public void LoadDesigns()
|
||||
{
|
||||
_humans.Awaiter.Wait();
|
||||
_customizations.Awaiter.Wait();
|
||||
_items.ItemData.Awaiter.Wait();
|
||||
|
||||
var stopwatch = Stopwatch.StartNew();
|
||||
_designs.Clear();
|
||||
List<(Design, string)> invalidNames = new();
|
||||
var skipped = 0;
|
||||
foreach (var file in _saveService.FileNames.Designs())
|
||||
var skipped = 0;
|
||||
ThreadLocal<List<(Design, string)>> designs = new(() => [], true);
|
||||
Parallel.ForEach(_saveService.FileNames.Designs(), (f, _) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
var text = File.ReadAllText(file.FullName);
|
||||
var text = File.ReadAllText(f.FullName);
|
||||
var data = JObject.Parse(text);
|
||||
var design = Design.LoadDesign(_customizations, _items, data);
|
||||
if (design.Identifier.ToString() != Path.GetFileNameWithoutExtension(file.Name))
|
||||
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);
|
||||
designs.Value!.Add((design, f.FullName));
|
||||
}
|
||||
catch (Exception 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);
|
||||
|
|
@ -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)}");
|
||||
|
||||
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!);
|
||||
}
|
||||
|
||||
|
|
@ -193,10 +210,10 @@ public class DesignManager
|
|||
public void ChangeColor(Design design, string newColor)
|
||||
{
|
||||
var oldColor = design.Color;
|
||||
if (oldColor == newColor)
|
||||
if (oldColor == newColor)
|
||||
return;
|
||||
|
||||
design.Color = newColor;
|
||||
design.Color = newColor;
|
||||
design.LastEdit = DateTimeOffset.UtcNow;
|
||||
_saveService.QueueSave(design);
|
||||
Glamourer.Log.Debug($"Changed color of design {design.Identifier}.");
|
||||
|
|
@ -298,7 +315,7 @@ public class DesignManager
|
|||
return;
|
||||
case CustomizeIndex.Clan:
|
||||
{
|
||||
var customize = new Customize(design.DesignData.Customize.Data.Clone());
|
||||
var customize = design.DesignData.Customize;
|
||||
if (_customizations.ChangeClan(ref customize, (SubRace)value.Value) == 0)
|
||||
return;
|
||||
if (!design.SetCustomize(_customizations, customize))
|
||||
|
|
@ -308,7 +325,7 @@ public class DesignManager
|
|||
}
|
||||
case CustomizeIndex.Gender:
|
||||
{
|
||||
var customize = new Customize(design.DesignData.Customize.Data.Clone());
|
||||
var customize = design.DesignData.Customize;
|
||||
if (_customizations.ChangeGender(ref customize, (Gender)(value.Value + 1)) == 0)
|
||||
return;
|
||||
if (!design.SetCustomize(_customizations, customize))
|
||||
|
|
|
|||
|
|
@ -2,9 +2,9 @@
|
|||
using Lumina.Excel;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Glamourer.Customization;
|
||||
namespace Glamourer.GameData;
|
||||
|
||||
// A custom version of CharaMakeParams that is easier to parse.
|
||||
/// <summary> A custom version of CharaMakeParams that is easier to parse. </summary>
|
||||
[Sheet("CharaMakeParams")]
|
||||
public class CharaMakeParams : ExcelRow
|
||||
{
|
||||
|
|
@ -64,7 +64,7 @@ public class CharaMakeParams : ExcelRow
|
|||
Race = new LazyRow<Race>(gameData, parser.ReadColumn<uint>(0), language);
|
||||
Tribe = new LazyRow<Tribe>(gameData, parser.ReadColumn<uint>(1), language);
|
||||
Gender = parser.ReadColumn<sbyte>(2);
|
||||
var currentOffset = 0;
|
||||
int currentOffset;
|
||||
for (var i = 0; i < NumMenus; ++i)
|
||||
{
|
||||
currentOffset = 3 + i;
|
||||
57
Glamourer/GameData/ColorParameters.cs
Normal file
57
Glamourer/GameData/ColorParameters.cs
Normal 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];
|
||||
}
|
||||
|
|
@ -1,28 +1,38 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Customization;
|
||||
namespace Glamourer.GameData;
|
||||
|
||||
// Any customization value can be represented in 8 bytes by its ID,
|
||||
// a byte value, an optional value-id and an optional icon or color.
|
||||
/// <summary>
|
||||
/// 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)]
|
||||
public readonly struct CustomizeData : IEquatable<CustomizeData>
|
||||
{
|
||||
/// <summary> The index of the option this value is for. </summary>
|
||||
[FieldOffset(0)]
|
||||
public readonly CustomizeIndex Index;
|
||||
|
||||
/// <summary> The value for the option. </summary>
|
||||
[FieldOffset(1)]
|
||||
public readonly CustomizeValue Value;
|
||||
|
||||
/// <summary> The internal ID for sheets. </summary>
|
||||
[FieldOffset(2)]
|
||||
public readonly ushort CustomizeId;
|
||||
|
||||
/// <summary> An ID for an associated icon. </summary>
|
||||
[FieldOffset(4)]
|
||||
public readonly uint IconId;
|
||||
|
||||
/// <summary> An ID for an associated color. </summary>
|
||||
[FieldOffset(4)]
|
||||
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)
|
||||
{
|
||||
Index = index;
|
||||
|
|
@ -32,14 +42,23 @@ public readonly struct CustomizeData : IEquatable<CustomizeData>
|
|||
CustomizeId = customizeId;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public bool Equals(CustomizeData other)
|
||||
=> Index == other.Index
|
||||
&& Value.Value == other.Value.Value
|
||||
&& CustomizeId == other.CustomizeId;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is CustomizeData other && Equals(other);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override int GetHashCode()
|
||||
=> 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);
|
||||
}
|
||||
101
Glamourer/GameData/CustomizeManager.cs
Normal file
101
Glamourer/GameData/CustomizeManager.cs
Normal 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; }
|
||||
}
|
||||
|
|
@ -4,14 +4,17 @@ using System.Linq;
|
|||
using System.Runtime.CompilerServices;
|
||||
using OtterGui;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Customization;
|
||||
namespace Glamourer.GameData;
|
||||
|
||||
// Each Subrace and Gender combo has a customization set.
|
||||
// This describes the available customizations, their types and their names.
|
||||
public class CustomizationSet
|
||||
/// <summary>
|
||||
/// Each SubRace and Gender combo has a customization set.
|
||||
/// 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;
|
||||
Clan = clan;
|
||||
|
|
@ -23,6 +26,8 @@ public class CustomizationSet
|
|||
public SubRace Clan { get; }
|
||||
public Race Race { get; }
|
||||
|
||||
public string Name { get; internal init; } = string.Empty;
|
||||
|
||||
public CustomizeFlag SettingAvailable { get; internal set; }
|
||||
|
||||
internal void SetAvailable(CustomizeIndex index)
|
||||
|
|
@ -32,7 +37,7 @@ public class CustomizationSet
|
|||
=> SettingAvailable.HasFlag(index.ToFlag());
|
||||
|
||||
// Meta
|
||||
public IReadOnlyList<string> OptionName { get; internal set; } = null!;
|
||||
public IReadOnlyList<string> OptionName { get; internal init; } = null!;
|
||||
|
||||
public string Option(CustomizeIndex index)
|
||||
=> OptionName[(int)index];
|
||||
|
|
@ -94,68 +99,6 @@ public class CustomizationSet
|
|||
{
|
||||
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
|
||||
{
|
||||
CharaMakeParams.MenuType.ListSelector => GetInteger0(out custom),
|
||||
|
|
@ -193,6 +136,68 @@ public class CustomizationSet
|
|||
CharaMakeParams.MenuType.Checkmark => GetBool(index, value, 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)]
|
||||
|
|
@ -243,19 +248,6 @@ public class CustomizationSet
|
|||
public CharaMakeParams.MenuType Type(CustomizeIndex 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)]
|
||||
public int Count(CustomizeIndex index)
|
||||
=> Count(index, CustomizeValue.Zero);
|
||||
|
|
@ -300,3 +292,10 @@ public class CustomizationSet
|
|||
private CustomizeValue HrothgarFaceHack(CustomizeValue value)
|
||||
=> Race == Race.Hrothgar && value.Value is > 4 and < 9 ? value - 4 : value;
|
||||
}
|
||||
|
||||
public static class CustomizationSetExtensions
|
||||
{
|
||||
/// <summary> Return only the available customizations in this set and Clan or Gender. </summary>
|
||||
public static CustomizeFlag FixApplication(this CustomizeFlag flag, CustomizeSet set)
|
||||
=> flag & (set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.BodyType);
|
||||
}
|
||||
473
Glamourer/GameData/CustomizeSetFactory.cs
Normal file
473
Glamourer/GameData/CustomizeSetFactory.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
319
Glamourer/GameData/NpcCustomizeSet.cs
Normal file
319
Glamourer/GameData/NpcCustomizeSet.cs
Normal 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];
|
||||
}
|
||||
109
Glamourer/GameData/NpcData.cs
Normal file
109
Glamourer/GameData/NpcData.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ using Glamourer.State;
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Log;
|
||||
using OtterGui.Services;
|
||||
|
||||
namespace Glamourer;
|
||||
|
||||
|
|
@ -25,21 +26,21 @@ public class Glamourer : IDalamudPlugin
|
|||
public static readonly Logger Log = new();
|
||||
public static MessageService Messager { get; private set; } = null!;
|
||||
|
||||
private readonly ServiceProvider _services;
|
||||
private readonly ServiceManager _services;
|
||||
|
||||
public Glamourer(DalamudPluginInterface pluginInterface)
|
||||
{
|
||||
try
|
||||
{
|
||||
_services = ServiceManager.CreateProvider(pluginInterface, Log);
|
||||
Messager = _services.GetRequiredService<MessageService>();
|
||||
_services.GetRequiredService<VisorService>();
|
||||
_services.GetRequiredService<WeaponService>();
|
||||
_services.GetRequiredService<ScalingService>();
|
||||
_services.GetRequiredService<StateListener>(); // Initialize State Listener.
|
||||
_services.GetRequiredService<GlamourerWindowSystem>(); // initialize ui.
|
||||
_services.GetRequiredService<CommandService>(); // initialize commands.
|
||||
_services.GetRequiredService<GlamourerIpc>(); // initialize IPC.
|
||||
_services = ServiceManagerA.CreateProvider(pluginInterface, Log);
|
||||
Messager = _services.GetService<MessageService>();
|
||||
_services.GetService<VisorService>();
|
||||
_services.GetService<WeaponService>();
|
||||
_services.GetService<ScalingService>();
|
||||
_services.GetService<StateListener>(); // Initialize State Listener.
|
||||
_services.GetService<GlamourerWindowSystem>(); // initialize ui.
|
||||
_services.GetService<CommandService>(); // initialize commands.
|
||||
_services.GetService<GlamourerIpc>(); // initialize IPC.
|
||||
Log.Information($"Glamourer v{Version} loaded successfully.");
|
||||
}
|
||||
catch
|
||||
|
|
|
|||
|
|
@ -84,7 +84,6 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\OtterGui\OtterGui.csproj" />
|
||||
<ProjectReference Include="..\Glamourer.GameData\Glamourer.GameData.csproj" />
|
||||
<ProjectReference Include="..\Penumbra.Api\Penumbra.Api.csproj" />
|
||||
<ProjectReference Include="..\Penumbra.String\Penumbra.string.csproj" />
|
||||
<ProjectReference Include="..\Penumbra.GameData\Penumbra.GameData.csproj" />
|
||||
|
|
@ -109,8 +108,8 @@
|
|||
|
||||
<Target Name="GetGitHash" BeforeTargets="GetAssemblyVersion" Returns="InformationalVersion">
|
||||
<Exec Command="git rev-parse --short HEAD" ConsoleToMSBuild="true" StandardOutputImportance="low" ContinueOnError="true">
|
||||
<Output TaskParameter="ExitCode" PropertyName="GitCommitHashSuccess"/>
|
||||
<Output TaskParameter="ConsoleOutput" PropertyName="GitCommitHash" Condition="$(GitCommitHashSuccess) == 0"/>
|
||||
<Output TaskParameter="ExitCode" PropertyName="GitCommitHashSuccess" />
|
||||
<Output TaskParameter="ConsoleOutput" PropertyName="GitCommitHash" Condition="$(GitCommitHashSuccess) == 0" />
|
||||
</Exec>
|
||||
|
||||
<PropertyGroup>
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ public enum ColorId
|
|||
TriStateCheck,
|
||||
TriStateCross,
|
||||
TriStateNeutral,
|
||||
BattleNpc,
|
||||
EventNpc,
|
||||
}
|
||||
|
||||
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.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.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 ),
|
||||
// @formatter:on
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.GameData;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Gui.Customization;
|
||||
|
||||
|
|
@ -15,12 +15,12 @@ public partial class CustomizationDrawer
|
|||
|
||||
private void DrawColorPicker(CustomizeIndex index)
|
||||
{
|
||||
using var _ = SetId(index);
|
||||
using var id = SetId(index);
|
||||
var (current, custom) = GetCurrentCustomization(index);
|
||||
|
||||
var color = ImGui.ColorConvertU32ToFloat4(current < 0 ? ImGui.GetColorU32(ImGuiCol.FrameBg) : custom.Color);
|
||||
|
||||
using (var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, current < 0))
|
||||
using (_ = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, 2 * ImGuiHelpers.GlobalScale, current < 0))
|
||||
{
|
||||
if (ImGui.ColorButton($"{_customize[index].Value}##color", color, ImGuiColorEditFlags.None, _framedIconSize))
|
||||
ImGui.OpenPopup(ColorPickerPopupName);
|
||||
|
|
@ -39,7 +39,7 @@ public partial class CustomizationDrawer
|
|||
|
||||
ImGui.SameLine();
|
||||
|
||||
using (var group = ImRaii.Group())
|
||||
using (_ = ImRaii.Group())
|
||||
{
|
||||
DataInputInt(current, npc);
|
||||
if (_withApply)
|
||||
|
|
@ -89,7 +89,7 @@ public partial class CustomizationDrawer
|
|||
{
|
||||
var current = _set.DataByValue(index, _customize[index], out var custom, _customize.Face);
|
||||
if (_set.IsAvailable(index) && current < 0)
|
||||
return (current, new CustomizeData(index, _customize[index], 0, 0));
|
||||
return (current, new CustomizeData(index, _customize[index]));
|
||||
|
||||
return (current, custom!.Value);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Glamourer.Customization;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Gui.Customization;
|
||||
|
||||
|
|
@ -75,4 +76,20 @@ public partial class CustomizationDrawer
|
|||
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.");
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.GameData;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Gui.Customization;
|
||||
|
||||
|
|
@ -14,7 +15,7 @@ public partial class CustomizationDrawer
|
|||
|
||||
private void DrawIconSelector(CustomizeIndex index)
|
||||
{
|
||||
using var _ = SetId(index);
|
||||
using var id = SetId(index);
|
||||
using var bigGroup = ImRaii.Group();
|
||||
var label = _currentOption;
|
||||
|
||||
|
|
@ -28,8 +29,8 @@ public partial class CustomizationDrawer
|
|||
npc = true;
|
||||
}
|
||||
|
||||
var icon = _service.AwaitedService.GetIcon(custom!.Value.IconId);
|
||||
using (var disabled = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw))
|
||||
var icon = _service.Manager.GetIcon(custom!.Value.IconId);
|
||||
using (_ = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw))
|
||||
{
|
||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
|
||||
ImGui.OpenPopup(IconSelectorPopup);
|
||||
|
|
@ -38,7 +39,7 @@ public partial class CustomizationDrawer
|
|||
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||
|
||||
ImGui.SameLine();
|
||||
using (var group = ImRaii.Group())
|
||||
using (_ = ImRaii.Group())
|
||||
{
|
||||
DataInputInt(current, npc);
|
||||
if (_lockedRedraw && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
||||
|
|
@ -68,7 +69,7 @@ public partial class CustomizationDrawer
|
|||
for (var i = 0; i < _currentCount; ++i)
|
||||
{
|
||||
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 frameColor = ImRaii.PushColor(ImGuiCol.Button, Colors.SelectedRed, current == i);
|
||||
|
|
@ -119,16 +120,16 @@ public partial class CustomizationDrawer
|
|||
ImGui.Dummy(new Vector2(ImGui.GetFrameHeight()));
|
||||
}
|
||||
|
||||
var oldValue = _customize.Data.At(_currentIndex.ToByteAndMask().ByteIdx);
|
||||
var tmp = (int)oldValue;
|
||||
var oldValue = _customize.AtIndex(_currentIndex.ToByteAndMask().ByteIdx);
|
||||
var tmp = (int)oldValue.Value;
|
||||
ImGui.SetNextItemWidth(_inputIntSize);
|
||||
if (ImGui.InputInt("##text", ref tmp, 1, 1))
|
||||
{
|
||||
tmp = Math.Clamp(tmp, 0, byte.MaxValue);
|
||||
if (tmp != oldValue)
|
||||
if (tmp != oldValue.Value)
|
||||
{
|
||||
_customize.Data.Set(_currentIndex.ToByteAndMask().ByteIdx, (byte)tmp);
|
||||
var changes = (byte)tmp ^ oldValue;
|
||||
_customize.SetByIndex(_currentIndex.ToByteAndMask().ByteIdx, (CustomizeValue)tmp);
|
||||
var changes = (byte)tmp ^ oldValue.Value;
|
||||
Changed |= ((changes & 0x01) == 0x01 ? CustomizeFlag.FacialFeature1 : 0)
|
||||
| ((changes & 0x02) == 0x02 ? CustomizeFlag.FacialFeature2 : 0)
|
||||
| ((changes & 0x04) == 0x04 ? CustomizeFlag.FacialFeature3 : 0)
|
||||
|
|
@ -179,8 +180,8 @@ public partial class CustomizationDrawer
|
|||
var enabled = _customize.Get(featureIdx) != CustomizeValue.Zero;
|
||||
var feature = _set.Data(featureIdx, 0, face);
|
||||
var icon = featureIdx == CustomizeIndex.LegacyTattoo
|
||||
? _legacyTattoo ?? _service.AwaitedService.GetIcon(feature.IconId)
|
||||
: _service.AwaitedService.GetIcon(feature.IconId);
|
||||
? _legacyTattoo ?? _service.Manager.GetIcon(feature.IconId)
|
||||
: _service.Manager.GetIcon(feature.IconId);
|
||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize, Vector2.Zero, Vector2.One, (int)ImGui.GetStyle().FramePadding.X,
|
||||
Vector4.Zero, enabled ? Vector4.One : _redTint))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using Glamourer.Customization;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Gui.Customization;
|
||||
|
||||
|
|
@ -91,10 +91,10 @@ public partial class CustomizationDrawer
|
|||
|
||||
private void DrawListSelector(CustomizeIndex index, bool indexedBy1)
|
||||
{
|
||||
using var _ = SetId(index);
|
||||
using var id = SetId(index);
|
||||
using var bigGroup = ImRaii.Group();
|
||||
|
||||
using (var disabled = ImRaii.Disabled(_locked))
|
||||
using (_ = ImRaii.Disabled(_locked))
|
||||
{
|
||||
if (indexedBy1)
|
||||
{
|
||||
|
|
@ -210,7 +210,7 @@ public partial class CustomizationDrawer
|
|||
}
|
||||
else
|
||||
{
|
||||
using (var disabled = ImRaii.Disabled(_locked))
|
||||
using (_ = ImRaii.Disabled(_locked))
|
||||
{
|
||||
if (ImGui.Checkbox("##toggle", ref tmp))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,17 +4,17 @@ using System.Reflection;
|
|||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Services;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
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
|
||||
{
|
||||
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 Customize _customize = Customize.Default;
|
||||
private CustomizationSet _set = null!;
|
||||
private CustomizeArray _customize = CustomizeArray.Default;
|
||||
private CustomizeSet _set = null!;
|
||||
|
||||
public Customize Customize
|
||||
public CustomizeArray Customize
|
||||
=> _customize;
|
||||
|
||||
public CustomizeFlag CurrentFlag { get; private set; }
|
||||
public CustomizeFlag Changed { get; private set; }
|
||||
public CustomizeFlag ChangeApply { get; private set; }
|
||||
|
||||
|
|
@ -47,18 +46,16 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio
|
|||
public void Dispose()
|
||||
=> _legacyTattoo?.Dispose();
|
||||
|
||||
public bool Draw(Customize current, bool locked, bool lockedRedraw)
|
||||
public bool Draw(CustomizeArray current, bool locked, bool lockedRedraw)
|
||||
{
|
||||
CurrentFlag = CustomizeFlagExtensions.All;
|
||||
_withApply = false;
|
||||
Init(current, locked, lockedRedraw);
|
||||
|
||||
return DrawInternal();
|
||||
}
|
||||
|
||||
public bool Draw(Customize current, CustomizeFlag apply, bool locked, bool lockedRedraw)
|
||||
public bool Draw(CustomizeArray current, CustomizeFlag apply, bool locked, bool lockedRedraw)
|
||||
{
|
||||
CurrentFlag = CustomizeFlagExtensions.All;
|
||||
ChangeApply = apply;
|
||||
_initialApply = apply;
|
||||
_withApply = !_config.HideApplyCheckmarks;
|
||||
|
|
@ -66,12 +63,12 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio
|
|||
return DrawInternal();
|
||||
}
|
||||
|
||||
private void Init(Customize current, bool locked, bool lockedRedraw)
|
||||
private void Init(CustomizeArray current, bool locked, bool lockedRedraw)
|
||||
{
|
||||
UpdateSizes();
|
||||
_terminate = null;
|
||||
Changed = 0;
|
||||
_customize.Load(current);
|
||||
_terminate = null;
|
||||
Changed = 0;
|
||||
_customize = current;
|
||||
_locked = locked;
|
||||
_lockedRedraw = lockedRedraw;
|
||||
}
|
||||
|
|
@ -116,11 +113,13 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio
|
|||
|
||||
try
|
||||
{
|
||||
if (_codes.EnabledArtisan)
|
||||
if (_codes.Enabled(CodeService.CodeFlag.Artisan))
|
||||
return DrawArtisan();
|
||||
|
||||
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])
|
||||
PercentageSelector(id);
|
||||
|
|
@ -153,23 +152,23 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio
|
|||
|
||||
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);
|
||||
int value = _customize.Data.Data[i];
|
||||
int value = _customize.Data[i];
|
||||
ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale);
|
||||
if (ImGui.InputInt(string.Empty, ref value, 0, 0))
|
||||
{
|
||||
var newValue = (byte)Math.Clamp(value, 0, byte.MaxValue);
|
||||
if (newValue != _customize.Data.Data[i])
|
||||
if (newValue != _customize.Data[i])
|
||||
foreach (var flag in Enum.GetValues<CustomizeIndex>())
|
||||
{
|
||||
var (j, mask) = flag.ToByteAndMask();
|
||||
var (j, _) = flag.ToByteAndMask();
|
||||
if (j == i)
|
||||
Changed |= flag.ToFlag();
|
||||
}
|
||||
|
||||
_customize.Data.Data[i] = newValue;
|
||||
_customize.Data[i] = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ using System.Linq;
|
|||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Utility.Raii;
|
||||
using Glamourer.Automation;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Services;
|
||||
|
|
@ -13,6 +13,7 @@ using OtterGui;
|
|||
using OtterGui.Classes;
|
||||
using OtterGui.Log;
|
||||
using OtterGui.Widgets;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
||||
|
|
@ -179,7 +180,7 @@ public sealed class RevertDesignCombo : DesignComboBase, IDisposable
|
|||
private readonly AutoDesignManager _autoDesignManager;
|
||||
|
||||
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)
|
||||
: this(designs, fileSystem, tabSelected, designColors, CreateRevertDesign(customize, items), log, designChanged, autoDesignManager,
|
||||
config)
|
||||
|
|
@ -209,7 +210,7 @@ public sealed class RevertDesignCombo : DesignComboBase, IDisposable
|
|||
_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)
|
||||
{
|
||||
Index = RevertDesignIndex,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Penumbra.GameData.Enums;
|
||||
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)
|
||||
=> 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),
|
||||
ApplySetter = b => manager.ChangeApplyEquip(design, slot, b),
|
||||
ApplyStainSetter = b => manager.ChangeApplyStain(design, slot, b),
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ using Glamourer.Unlocks;
|
|||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ public class EquipmentDrawer
|
|||
|
||||
private readonly ItemManager _items;
|
||||
private readonly GlamourerColorCombo _stainCombo;
|
||||
private readonly StainData _stainData;
|
||||
private readonly DictStain _stainData;
|
||||
private readonly ItemCombo[] _itemCombo;
|
||||
private readonly Dictionary<FullEquipType, WeaponCombo> _weaponCombo;
|
||||
private readonly CodeService _codes;
|
||||
|
|
@ -66,8 +66,8 @@ public class EquipmentDrawer
|
|||
_iconSize = new Vector2(2 * ImGui.GetFrameHeight() + ImGui.GetStyle().ItemSpacing.Y);
|
||||
_comboLength = DefaultWidth * ImGuiHelpers.GlobalScale;
|
||||
if (_requiredComboWidthUnscaled == 0)
|
||||
_requiredComboWidthUnscaled = _items.ItemService.AwaitedService.AllItems(true)
|
||||
.Concat(_items.ItemService.AwaitedService.AllItems(false))
|
||||
_requiredComboWidthUnscaled = _items.ItemData.AllItems(true)
|
||||
.Concat(_items.ItemData.AllItems(false))
|
||||
.Max(i => ImGui.CalcTextSize($"{i.Item2.Name} ({i.Item2.ModelString})").X)
|
||||
/ ImGuiHelpers.GlobalScale;
|
||||
|
||||
|
|
@ -94,7 +94,7 @@ public class EquipmentDrawer
|
|||
|
||||
if (_config.SmallEquip)
|
||||
DrawEquipSmall(equipDrawData);
|
||||
else if (!equipDrawData.Locked && _codes.EnabledArtisan)
|
||||
else if (!equipDrawData.Locked && _codes.Enabled(CodeService.CodeFlag.Artisan))
|
||||
DrawEquipArtisan(equipDrawData);
|
||||
else
|
||||
DrawEquipNormal(equipDrawData);
|
||||
|
|
@ -102,7 +102,7 @@ public class EquipmentDrawer
|
|||
|
||||
public void DrawWeapons(EquipDrawData mainhand, EquipDrawData offhand, bool allWeapons)
|
||||
{
|
||||
if (mainhand.CurrentItem.ModelId.Id == 0)
|
||||
if (mainhand.CurrentItem.PrimaryId.Id == 0)
|
||||
return;
|
||||
|
||||
if (_config.HideApplyCheckmarks)
|
||||
|
|
@ -117,7 +117,7 @@ public class EquipmentDrawer
|
|||
|
||||
if (_config.SmallEquip)
|
||||
DrawWeaponsSmall(mainhand, offhand, allWeapons);
|
||||
else if (!mainhand.Locked && _codes.EnabledArtisan)
|
||||
else if (!mainhand.Locked && _codes.Enabled(CodeService.CodeFlag.Artisan))
|
||||
DrawWeaponsArtisan(mainhand, offhand);
|
||||
else
|
||||
DrawWeaponsNormal(mainhand, offhand, allWeapons);
|
||||
|
|
@ -202,24 +202,24 @@ public class EquipmentDrawer
|
|||
|
||||
void DrawWeapon(in EquipDrawData current)
|
||||
{
|
||||
int setId = current.CurrentItem.ModelId.Id;
|
||||
int type = current.CurrentItem.WeaponType.Id;
|
||||
int setId = current.CurrentItem.PrimaryId.Id;
|
||||
int type = current.CurrentItem.SecondaryId.Id;
|
||||
int variant = current.CurrentItem.Variant.Id;
|
||||
ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale);
|
||||
if (ImGui.InputInt("##setId", ref setId, 0, 0))
|
||||
{
|
||||
var newSetId = (SetId)Math.Clamp(setId, 0, ushort.MaxValue);
|
||||
if (newSetId.Id != current.CurrentItem.ModelId.Id)
|
||||
current.ItemSetter(_items.Identify(current.Slot, newSetId, current.CurrentItem.WeaponType, current.CurrentItem.Variant));
|
||||
var newSetId = (PrimaryId)Math.Clamp(setId, 0, ushort.MaxValue);
|
||||
if (newSetId.Id != current.CurrentItem.PrimaryId.Id)
|
||||
current.ItemSetter(_items.Identify(current.Slot, newSetId, current.CurrentItem.SecondaryId, current.CurrentItem.Variant));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale);
|
||||
if (ImGui.InputInt("##type", ref type, 0, 0))
|
||||
{
|
||||
var newType = (WeaponType)Math.Clamp(type, 0, ushort.MaxValue);
|
||||
if (newType.Id != current.CurrentItem.WeaponType.Id)
|
||||
current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.ModelId, newType, current.CurrentItem.Variant));
|
||||
var newType = (SecondaryId)Math.Clamp(type, 0, ushort.MaxValue);
|
||||
if (newType.Id != current.CurrentItem.SecondaryId.Id)
|
||||
current.ItemSetter(_items.Identify(current.Slot, current.CurrentItem.PrimaryId, newType, current.CurrentItem.Variant));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
|
@ -228,7 +228,8 @@ public class EquipmentDrawer
|
|||
{
|
||||
var newVariant = (Variant)Math.Clamp(variant, 0, byte.MaxValue);
|
||||
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>
|
||||
private void DrawArmorArtisan(EquipDrawData data)
|
||||
{
|
||||
int setId = data.CurrentItem.ModelId.Id;
|
||||
int setId = data.CurrentItem.PrimaryId.Id;
|
||||
int variant = data.CurrentItem.Variant.Id;
|
||||
ImGui.SetNextItemWidth(80 * ImGuiHelpers.GlobalScale);
|
||||
if (ImGui.InputInt("##setId", ref setId, 0, 0))
|
||||
{
|
||||
var newSetId = (SetId)Math.Clamp(setId, 0, ushort.MaxValue);
|
||||
if (newSetId.Id != data.CurrentItem.ModelId.Id)
|
||||
var newSetId = (PrimaryId)Math.Clamp(setId, 0, ushort.MaxValue);
|
||||
if (newSetId.Id != data.CurrentItem.PrimaryId.Id)
|
||||
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);
|
||||
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)
|
||||
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))
|
||||
data.ItemSetter(ItemManager.NothingItem(data.Slot));
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ using Dalamud.Interface.Utility.Raii;
|
|||
using Glamourer.Unlocks;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Widgets;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
=> () => stains.Data.Select(kvp => (kvp, favorites.Contains((StainId)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();
|
||||
=> () => stains.Select(kvp => (kvp, favorites.Contains(kvp.Key))).OrderBy(p => !p.Item2).Select(p => p.kvp)
|
||||
.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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
|
|||
private ItemId _currentItem;
|
||||
private float _innerWidth;
|
||||
|
||||
public SetId CustomSetId { get; private set; }
|
||||
public Variant CustomVariant { get; private set; }
|
||||
public PrimaryId CustomSetId { get; private set; }
|
||||
public Variant CustomVariant { get; private set; }
|
||||
|
||||
public ItemCombo(IDataManager gameData, ItemManager items, EquipSlot slot, Logger log, FavoriteManager favorites)
|
||||
: base(() => GetItems(favorites, items, slot), log)
|
||||
|
|
@ -83,7 +83,7 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
|
|||
}
|
||||
|
||||
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)
|
||||
=> obj.Name;
|
||||
|
|
@ -111,7 +111,7 @@ public sealed class ItemCombo : FilterComboCache<EquipItem>
|
|||
private static IReadOnlyList<EquipItem> GetItems(FavoriteManager favorites, ItemManager items, EquipSlot 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[]
|
||||
{
|
||||
nothing,
|
||||
|
|
|
|||
|
|
@ -60,12 +60,12 @@ public sealed class WeaponCombo : FilterComboCache<EquipItem>
|
|||
var ret = ImGui.Selectable(name, selected);
|
||||
ImGui.SameLine();
|
||||
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;
|
||||
}
|
||||
|
||||
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)
|
||||
=> obj.Name;
|
||||
|
|
@ -80,14 +80,14 @@ public sealed class WeaponCombo : FilterComboCache<EquipItem>
|
|||
var enumerable = Array.Empty<EquipItem>().AsEnumerable();
|
||||
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);
|
||||
}
|
||||
|
||||
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>();
|
||||
|
||||
if (type.AllowsNothing())
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Numerics;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Interface.Windowing;
|
||||
|
|
@ -61,7 +62,7 @@ public class GenericPopupWindow : Window
|
|||
private void DrawFestivalPopup()
|
||||
{
|
||||
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));
|
||||
using var popup = ImRaii.Popup("FestivalPopup", ImGuiWindowFlags.Modal);
|
||||
if (!popup)
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ using Glamourer.Gui.Tabs.ActorTab;
|
|||
using Glamourer.Gui.Tabs.AutomationTab;
|
||||
using Glamourer.Gui.Tabs.DebugTab;
|
||||
using Glamourer.Gui.Tabs.DesignTab;
|
||||
using Glamourer.Gui.Tabs.NpcTab;
|
||||
using Glamourer.Gui.Tabs.UnlocksTab;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Custom;
|
||||
|
|
@ -29,6 +30,7 @@ public class MainWindow : Window, IDisposable
|
|||
Automation = 4,
|
||||
Unlocks = 5,
|
||||
Messages = 6,
|
||||
Npcs = 7,
|
||||
}
|
||||
|
||||
private readonly Configuration _config;
|
||||
|
|
@ -42,12 +44,14 @@ public class MainWindow : Window, IDisposable
|
|||
public readonly DesignTab Designs;
|
||||
public readonly AutomationTab Automation;
|
||||
public readonly UnlocksTab Unlocks;
|
||||
public readonly NpcTab Npcs;
|
||||
public readonly MessagesTab Messages;
|
||||
|
||||
public TabType SelectTab = TabType.None;
|
||||
|
||||
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())
|
||||
{
|
||||
pi.UiBuilder.DisableGposeUiHide = true;
|
||||
|
|
@ -65,17 +69,19 @@ public class MainWindow : Window, IDisposable
|
|||
_event = @event;
|
||||
Messages = messages;
|
||||
_quickBar = quickBar;
|
||||
Npcs = npcs;
|
||||
_config = config;
|
||||
_tabs = new ITab[]
|
||||
{
|
||||
_tabs =
|
||||
[
|
||||
settings,
|
||||
actors,
|
||||
designs,
|
||||
automation,
|
||||
unlocks,
|
||||
npcs,
|
||||
messages,
|
||||
debugTab,
|
||||
};
|
||||
];
|
||||
_event.Subscribe(OnTabSelected, TabSelected.Priority.MainWindow);
|
||||
IsOpen = _config.OpenWindowAtStart;
|
||||
}
|
||||
|
|
@ -117,6 +123,7 @@ public class MainWindow : Window, IDisposable
|
|||
TabType.Automation => Automation.Label,
|
||||
TabType.Unlocks => Unlocks.Label,
|
||||
TabType.Messages => Messages.Label,
|
||||
TabType.Npcs => Npcs.Label,
|
||||
_ => ReadOnlySpan<byte>.Empty,
|
||||
};
|
||||
|
||||
|
|
@ -128,6 +135,7 @@ public class MainWindow : Window, IDisposable
|
|||
if (label == Settings.Label) return TabType.Settings;
|
||||
if (label == Automation.Label) return TabType.Automation;
|
||||
if (label == Unlocks.Label) return TabType.Unlocks;
|
||||
if (label == Npcs.Label) return TabType.Npcs;
|
||||
if (label == Messages.Label) return TabType.Messages;
|
||||
if (label == Debug.Label) return TabType.Debug;
|
||||
// @formatter:on
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ using Glamourer.Interop;
|
|||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.Api.Enums;
|
||||
|
|
@ -76,7 +75,7 @@ public class PenumbraChangedItemTooltip : IDisposable
|
|||
case EquipSlot.OffHand when !CanApplyWeapon(EquipSlot.OffHand, item):
|
||||
break;
|
||||
case EquipSlot.RFinger:
|
||||
using (var tt = !openTooltip ? null : ImRaii.Tooltip())
|
||||
using (_ = !openTooltip ? null : ImRaii.Tooltip())
|
||||
{
|
||||
ImGui.TextUnformatted($"{prefix}Right-Click to apply to current actor (Right Finger).");
|
||||
ImGui.TextUnformatted($"{prefix}Shift + Right-Click to apply to current actor (Left Finger).");
|
||||
|
|
@ -92,7 +91,7 @@ public class PenumbraChangedItemTooltip : IDisposable
|
|||
|
||||
break;
|
||||
default:
|
||||
using (var tt = !openTooltip ? null : ImRaii.Tooltip())
|
||||
using (_ = !openTooltip ? null : ImRaii.Tooltip())
|
||||
{
|
||||
ImGui.TextUnformatted($"{prefix}Right-Click to apply to current actor.");
|
||||
if (last.Valid)
|
||||
|
|
@ -166,7 +165,7 @@ public class PenumbraChangedItemTooltip : IDisposable
|
|||
{
|
||||
case ChangedItemType.ItemOffhand:
|
||||
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;
|
||||
|
||||
CreateTooltip(item, "[Glamourer] ", false);
|
||||
|
|
@ -177,7 +176,7 @@ public class PenumbraChangedItemTooltip : IDisposable
|
|||
private bool CanApplyWeapon(EquipSlot slot, EquipItem item)
|
||||
{
|
||||
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)
|
||||
return item.Type == mainItem.Type;
|
||||
|
||||
|
|
@ -197,7 +196,7 @@ public class PenumbraChangedItemTooltip : IDisposable
|
|||
if (!Player(out var state))
|
||||
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;
|
||||
|
||||
ApplyItem(state, item);
|
||||
|
|
|
|||
|
|
@ -6,29 +6,36 @@ using Dalamud.Interface.Internal.Notifications;
|
|||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using Glamourer.Automation;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Gui.Customization;
|
||||
using Glamourer.Gui.Equipment;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.ActorTab;
|
||||
|
||||
public class ActorPanel(ActorSelector _selector, StateManager _stateManager, CustomizationDrawer _customizationDrawer,
|
||||
EquipmentDrawer _equipmentDrawer, IdentifierService _identification, AutoDesignApplier _autoDesignApplier,
|
||||
Configuration _config, DesignConverter _converter, ObjectManager _objects, DesignManager _designManager, ImportService _importService,
|
||||
ICondition _conditions)
|
||||
public class ActorPanel(
|
||||
ActorSelector _selector,
|
||||
StateManager _stateManager,
|
||||
CustomizationDrawer _customizationDrawer,
|
||||
EquipmentDrawer _equipmentDrawer,
|
||||
AutoDesignApplier _autoDesignApplier,
|
||||
Configuration _config,
|
||||
DesignConverter _converter,
|
||||
ObjectManager _objects,
|
||||
DesignManager _designManager,
|
||||
ImportService _importService,
|
||||
ICondition _conditions,
|
||||
DictModelChara _modelChara)
|
||||
{
|
||||
private ActorIdentifier _identifier;
|
||||
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 offhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.OffHand);
|
||||
var offhand = EquipDrawData.FromState(_stateManager, _state, EquipSlot.OffHand);
|
||||
_equipmentDrawer.DrawWeapons(mainhand, offhand, GameMain.IsInGPose());
|
||||
|
||||
ImGui.Dummy(new Vector2(ImGui.GetTextLineHeight() / 2));
|
||||
|
|
@ -164,21 +171,21 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus
|
|||
|
||||
private void DrawEquipmentMetaToggles()
|
||||
{
|
||||
using (var _ = ImRaii.Group())
|
||||
using (_ = ImRaii.Group())
|
||||
{
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.HatState, _stateManager, _state!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Head, _stateManager, _state!));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
using (var _ = ImRaii.Group())
|
||||
using (_ = ImRaii.Group())
|
||||
{
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.VisorState, _stateManager, _state!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Body, _stateManager, _state!));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
using (var _ = ImRaii.Group())
|
||||
using (_ = ImRaii.Group())
|
||||
{
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.WeaponState, _stateManager, _state!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.OffHand, _stateManager, _state!));
|
||||
|
|
@ -187,10 +194,10 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus
|
|||
|
||||
private void DrawMonsterPanel()
|
||||
{
|
||||
var names = _identification.AwaitedService.ModelCharaNames(_state!.ModelData.ModelId);
|
||||
var names = _modelChara[_state!.ModelData.ModelId];
|
||||
var turnHuman = ImGui.Button("Turn Human");
|
||||
ImGui.Separator();
|
||||
using (var box = ImRaii.ListBox("##MonsterList",
|
||||
using (_ = ImRaii.ListBox("##MonsterList",
|
||||
new Vector2(ImGui.GetContentRegionAvail().X, 10 * ImGui.GetTextLineHeightWithSpacing())))
|
||||
{
|
||||
if (names.Count == 0)
|
||||
|
|
@ -202,14 +209,14 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus
|
|||
|
||||
ImGui.Separator();
|
||||
ImGui.TextUnformatted("Customization Data");
|
||||
using (var font = ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
using (_ = ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
{
|
||||
foreach (var b in _state.ModelData.Customize.Data)
|
||||
foreach (var b in _state.ModelData.Customize)
|
||||
{
|
||||
using (var g = ImRaii.Group())
|
||||
using (_ = ImRaii.Group())
|
||||
{
|
||||
ImGui.TextUnformatted($" {b:X2}");
|
||||
ImGui.TextUnformatted($"{b,3}");
|
||||
ImGui.TextUnformatted($" {b.Value:X2}");
|
||||
ImGui.TextUnformatted($"{b.Value,3}");
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
|
@ -223,11 +230,11 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus
|
|||
|
||||
ImGui.Separator();
|
||||
ImGui.TextUnformatted("Equipment Data");
|
||||
using (var font = ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
using (_ = ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
{
|
||||
foreach (var b in _state.ModelData.GetEquipmentBytes())
|
||||
{
|
||||
using (var g = ImRaii.Group())
|
||||
using (_ = ImRaii.Group())
|
||||
{
|
||||
ImGui.TextUnformatted($" {b:X2}");
|
||||
ImGui.TextUnformatted($"{b,3}");
|
||||
|
|
@ -289,15 +296,15 @@ public class ActorPanel(ActorSelector _selector, StateManager _stateManager, Cus
|
|||
BorderColor = ColorId.ActorUnavailable.Value(),
|
||||
};
|
||||
|
||||
private string _newName = string.Empty;
|
||||
private DesignBase? _newDesign = null;
|
||||
private string _newName = string.Empty;
|
||||
private DesignBase? _newDesign;
|
||||
|
||||
private void SaveDesignOpen()
|
||||
{
|
||||
ImGui.OpenPopup("Save as Design");
|
||||
_newName = _state!.Identifier.ToName();
|
||||
_newName = _state!.Identifier.ToName();
|
||||
var (applyGear, applyCustomize, applyCrest) = UiHelpers.ConvertKeysToFlags();
|
||||
_newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest);
|
||||
_newDesign = _converter.Convert(_state, applyGear, applyCustomize, applyCrest);
|
||||
}
|
||||
|
||||
private void SaveDesignDrawPopup()
|
||||
|
|
|
|||
|
|
@ -17,11 +17,11 @@ public class ActorSelector
|
|||
{
|
||||
private readonly EphemeralConfig _config;
|
||||
private readonly ObjectManager _objects;
|
||||
private readonly ActorService _actors;
|
||||
private readonly ActorManager _actors;
|
||||
|
||||
private ActorIdentifier _identifier = ActorIdentifier.Invalid;
|
||||
|
||||
public ActorSelector(ObjectManager objects, ActorService actors, EphemeralConfig config)
|
||||
public ActorSelector(ObjectManager objects, ActorManager actors, EphemeralConfig config)
|
||||
{
|
||||
_objects = objects;
|
||||
_actors = actors;
|
||||
|
|
@ -93,7 +93,7 @@ public class ActorSelector
|
|||
|
||||
if (ImGuiUtil.DrawDisabledButton(FontAwesomeIcon.UserCircle.ToIconString(), buttonWidth
|
||||
, "Select the local player character.", !_objects.Player, true))
|
||||
_identifier = _objects.Player.GetIdentifier(_actors.AwaitedService);
|
||||
_identifier = _objects.Player.GetIdentifier(_actors);
|
||||
|
||||
ImGui.SameLine();
|
||||
var (id, data) = _objects.TargetData;
|
||||
|
|
|
|||
|
|
@ -3,23 +3,23 @@ using System.Collections.Generic;
|
|||
using System.Linq;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Utility;
|
||||
using Glamourer.Services;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Custom;
|
||||
using OtterGui.Log;
|
||||
using OtterGui.Widgets;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
|
||||
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)
|
||||
=> obj.Name;
|
||||
|
||||
|
|
@ -36,7 +36,8 @@ public sealed class HumanNpcCombo : FilterComboCache<(string Name, ObjectKind Ki
|
|||
}
|
||||
|
||||
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>
|
||||
|
|
@ -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);
|
||||
for (var modelChara = 0u; modelChara < service.AwaitedService.NumModelChara; ++modelChara)
|
||||
for (var modelChara = 0u; modelChara < modelCharaDict.Count; ++modelChara)
|
||||
{
|
||||
if (!humans.IsHuman(modelChara))
|
||||
continue;
|
||||
|
||||
var list = service.AwaitedService.ModelCharaNames(modelChara);
|
||||
var list = modelCharaDict[modelChara];
|
||||
if (list.Count == 0)
|
||||
continue;
|
||||
|
||||
|
|
@ -78,8 +80,8 @@ public sealed class HumanNpcCombo : FilterComboCache<(string Name, ObjectKind Ki
|
|||
switch (kind)
|
||||
{
|
||||
case ObjectKind.BattleNpc:
|
||||
var nameIds = service.AwaitedService.GetBnpcNames(id);
|
||||
ret.AddRange(nameIds.Select(nameId => (service.AwaitedService.Name(ObjectKind.BattleNpc, nameId), kind, nameId.Id)));
|
||||
var nameIds = bNpcNames[id];
|
||||
ret.AddRange(nameIds.Select(nameId => (bNpcs[nameId], kind, nameId.Id)));
|
||||
break;
|
||||
case ObjectKind.EventNpc:
|
||||
ret.Add((name, kind, id));
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Glamourer.Services;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Custom;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Gui;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.String;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.AutomationTab;
|
||||
|
|
@ -12,7 +12,7 @@ public class IdentifierDrawer
|
|||
{
|
||||
private readonly WorldCombo _worldCombo;
|
||||
private readonly HumanNpcCombo _humanNpcCombo;
|
||||
private readonly ActorService _actors;
|
||||
private readonly ActorManager _actors;
|
||||
|
||||
private string _characterName = string.Empty;
|
||||
|
||||
|
|
@ -21,11 +21,12 @@ public class IdentifierDrawer
|
|||
public ActorIdentifier RetainerIdentifier { 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;
|
||||
_worldCombo = new WorldCombo(actors.AwaitedService.Data.Worlds, Glamourer.Log);
|
||||
_humanNpcCombo = new HumanNpcCombo("##npcs", identifier, humans, Glamourer.Log);
|
||||
_worldCombo = new WorldCombo(dictWorld, Glamourer.Log);
|
||||
_humanNpcCombo = new HumanNpcCombo("##npcs", dictModelChara, bNpcNames, bNpc, humans, Glamourer.Log);
|
||||
}
|
||||
|
||||
public void DrawName(float width)
|
||||
|
|
@ -63,13 +64,13 @@ public class IdentifierDrawer
|
|||
{
|
||||
if (ByteString.FromString(_characterName, out var byteName))
|
||||
{
|
||||
PlayerIdentifier = _actors.AwaitedService.CreatePlayer(byteName, _worldCombo.CurrentSelection.Key);
|
||||
RetainerIdentifier = _actors.AwaitedService.CreateRetainer(byteName, ActorIdentifier.RetainerType.Bell);
|
||||
MannequinIdentifier = _actors.AwaitedService.CreateRetainer(byteName, ActorIdentifier.RetainerType.Mannequin);
|
||||
PlayerIdentifier = _actors.CreatePlayer(byteName, _worldCombo.CurrentSelection.Key);
|
||||
RetainerIdentifier = _actors.CreateRetainer(byteName, ActorIdentifier.RetainerType.Bell);
|
||||
MannequinIdentifier = _actors.CreateRetainer(byteName, ActorIdentifier.RetainerType.Mannequin);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,8 @@ using System.Text;
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Glamourer.Automation;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using Glamourer.Unlocks;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
|
|
@ -17,44 +15,29 @@ using OtterGui.Log;
|
|||
using OtterGui.Raii;
|
||||
using OtterGui.Widgets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Action = System.Action;
|
||||
using CustomizeIndex = Glamourer.Customization.CustomizeIndex;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.AutomationTab;
|
||||
|
||||
public class SetPanel
|
||||
public class SetPanel(
|
||||
SetSelector _selector,
|
||||
AutoDesignManager _manager,
|
||||
JobService _jobs,
|
||||
ItemUnlockManager _itemUnlocks,
|
||||
RevertDesignCombo _designCombo,
|
||||
CustomizeUnlockManager _customizeUnlocks,
|
||||
CustomizeService _customizations,
|
||||
IdentifierDrawer _identifierDrawer,
|
||||
Configuration _config)
|
||||
{
|
||||
private readonly AutoDesignManager _manager;
|
||||
private readonly SetSelector _selector;
|
||||
private readonly ItemUnlockManager _itemUnlocks;
|
||||
private readonly CustomizeUnlockManager _customizeUnlocks;
|
||||
private readonly CustomizationService _customizations;
|
||||
|
||||
private readonly Configuration _config;
|
||||
private readonly RevertDesignCombo _designCombo;
|
||||
private readonly JobGroupCombo _jobGroupCombo;
|
||||
private readonly IdentifierDrawer _identifierDrawer;
|
||||
private readonly JobGroupCombo _jobGroupCombo = new(_manager, _jobs, Glamourer.Log);
|
||||
|
||||
private string? _tempName;
|
||||
private int _dragIndex = -1;
|
||||
|
||||
private Action? _endAction;
|
||||
|
||||
public SetPanel(SetSelector selector, AutoDesignManager manager, JobService jobs, ItemUnlockManager itemUnlocks,
|
||||
RevertDesignCombo designCombo,
|
||||
CustomizeUnlockManager customizeUnlocks, CustomizationService customizations, IdentifierDrawer identifierDrawer, Configuration config)
|
||||
{
|
||||
_selector = selector;
|
||||
_manager = manager;
|
||||
_itemUnlocks = itemUnlocks;
|
||||
_customizeUnlocks = customizeUnlocks;
|
||||
_customizations = customizations;
|
||||
_identifierDrawer = identifierDrawer;
|
||||
_config = config;
|
||||
_designCombo = designCombo;
|
||||
_jobGroupCombo = new JobGroupCombo(manager, jobs, Glamourer.Log);
|
||||
}
|
||||
|
||||
private AutoDesignSet Selection
|
||||
=> _selector.Selection!;
|
||||
|
||||
|
|
@ -77,7 +60,7 @@ public class SetPanel
|
|||
|
||||
var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y };
|
||||
|
||||
using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
|
||||
using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
|
||||
{
|
||||
var enabled = Selection.Enabled;
|
||||
if (ImGui.Checkbox("##Enabled", ref enabled))
|
||||
|
|
@ -87,7 +70,7 @@ public class SetPanel
|
|||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
|
||||
using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
|
||||
{
|
||||
var useGame = _selector.Selection!.BaseState is AutoDesignSet.Base.Game;
|
||||
if (ImGui.Checkbox("##gameState", ref useGame))
|
||||
|
|
@ -98,7 +81,7 @@ public class SetPanel
|
|||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
|
||||
using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
|
||||
{
|
||||
var editing = _config.ShowAutomationSetEditing;
|
||||
if (ImGui.Checkbox("##Show Editing", ref editing))
|
||||
|
|
@ -230,7 +213,7 @@ public class SetPanel
|
|||
if (_config.ShowUnlockedItemWarnings)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
DrawWarnings(design, idx);
|
||||
DrawWarnings(design);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -278,7 +261,7 @@ public class SetPanel
|
|||
}
|
||||
}
|
||||
|
||||
private void DrawWarnings(AutoDesign design, int idx)
|
||||
private void DrawWarnings(AutoDesign design)
|
||||
{
|
||||
if (design.Revert)
|
||||
return;
|
||||
|
|
@ -301,27 +284,6 @@ public class SetPanel
|
|||
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(2 * ImGuiHelpers.GlobalScale, 0));
|
||||
|
||||
|
||||
static void DrawWarning(StringBuilder sb, uint color, Vector2 size, string suffix, string good)
|
||||
{
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
sb.Append(suffix);
|
||||
using (var font = ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, color);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip(sb.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGuiUtil.DrawTextButton(string.Empty, size, 0);
|
||||
ImGuiUtil.HoverTooltip(good);
|
||||
}
|
||||
}
|
||||
|
||||
var tt = _config.UnlockedItemMode
|
||||
? "\nThese items will be skipped when applied automatically.\n\nTo change this, disable the Obtained Item Mode setting."
|
||||
: string.Empty;
|
||||
|
|
@ -333,7 +295,7 @@ public class SetPanel
|
|||
if (!design.Design.DesignData.IsHuman)
|
||||
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)
|
||||
{
|
||||
var flag = type.ToFlag();
|
||||
|
|
@ -355,6 +317,27 @@ public class SetPanel
|
|||
: string.Empty;
|
||||
DrawWarning(sb2, _config.UnlockedItemMode ? 0xA03030F0 : 0x0, size, tt, "All customizations to be applied are unlocked.");
|
||||
ImGui.SameLine();
|
||||
return;
|
||||
|
||||
static void DrawWarning(StringBuilder sb, uint color, Vector2 size, string suffix, string good)
|
||||
{
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
sb.Append(suffix);
|
||||
using (_ = ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, color);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip(sb.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGuiUtil.DrawTextButton(string.Empty, size, 0);
|
||||
ImGuiUtil.HoverTooltip(good);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawDragDrop(AutoDesignSet set, int index)
|
||||
|
|
@ -394,7 +377,7 @@ public class SetPanel
|
|||
var newType = design.ApplicationType;
|
||||
var newTypeInt = (uint)newType;
|
||||
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
||||
using (var c = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value()))
|
||||
using (_ = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value()))
|
||||
{
|
||||
if (ImGui.CheckboxFlags("##all", ref newTypeInt, (uint)AutoDesign.Type.All))
|
||||
newType = (AutoDesign.Type)newTypeInt;
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ using Dalamud.Interface.Utility;
|
|||
using Glamourer.Automation;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Services;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.String;
|
||||
using ImGuiClip = OtterGui.ImGuiClip;
|
||||
|
||||
|
|
@ -22,9 +22,9 @@ public class SetSelector : IDisposable
|
|||
private readonly Configuration _config;
|
||||
private readonly AutoDesignManager _manager;
|
||||
private readonly AutomationChanged _event;
|
||||
private readonly ActorService _actors;
|
||||
private readonly ActorManager _actors;
|
||||
private readonly ObjectManager _objects;
|
||||
private readonly List<(AutoDesignSet, int)> _list = new();
|
||||
private readonly List<(AutoDesignSet, int)> _list = [];
|
||||
|
||||
public AutoDesignSet? Selection { get; private set; }
|
||||
public int SelectionIndex { get; private set; } = -1;
|
||||
|
|
@ -44,7 +44,7 @@ public class SetSelector : IDisposable
|
|||
|
||||
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;
|
||||
_event = @event;
|
||||
|
|
@ -289,9 +289,9 @@ public class SetSelector : IDisposable
|
|||
|
||||
private void NewSetButton(Vector2 size)
|
||||
{
|
||||
var id = _actors.AwaitedService.GetCurrentPlayer();
|
||||
var id = _actors.GetCurrentPlayer();
|
||||
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,
|
||||
$"Create a new Automatic Design Set for {id}. The associated player can be changed later.", !id.IsValid, true))
|
||||
_manager.AddDesignSet("New Design", id);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Interop;
|
||||
|
|
@ -12,10 +12,11 @@ using ImGuiNET;
|
|||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Gui.Debug;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DebugTab;
|
||||
|
||||
public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IDebugTabTree
|
||||
public class ActiveStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IGameDataDrawer
|
||||
{
|
||||
public string Label
|
||||
=> $"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)
|
||||
{
|
||||
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]);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,10 +2,11 @@
|
|||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Gui.Debug;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DebugTab;
|
||||
|
||||
public class AutoDesignPanel(AutoDesignManager _autoDesignManager) : IDebugTabTree
|
||||
public class AutoDesignPanel(AutoDesignManager _autoDesignManager) : IGameDataDrawer
|
||||
{
|
||||
public string Label
|
||||
=> "Auto Designs";
|
||||
|
|
|
|||
|
|
@ -1,36 +1,35 @@
|
|||
using System;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Services;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Gui.Debug;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DebugTab;
|
||||
|
||||
public class CustomizationServicePanel(CustomizationService _customization) : IDebugTabTree
|
||||
public class CustomizationServicePanel(CustomizeService customize) : IGameDataDrawer
|
||||
{
|
||||
public string Label
|
||||
=> "Customization Service";
|
||||
|
||||
public bool Disabled
|
||||
=> !_customization.Valid;
|
||||
=> !customize.Finished;
|
||||
|
||||
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 = _customization.AwaitedService.GetList(clan, gender);
|
||||
DrawCustomizationInfo(set);
|
||||
DrawNpcCustomizationInfo(set);
|
||||
}
|
||||
var set = customize.Manager.GetSet(clan, gender);
|
||||
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)
|
||||
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)
|
||||
return;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Unlocks;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Gui.Debug;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DebugTab;
|
||||
|
||||
public class CustomizationUnlockPanel(CustomizeUnlockManager _customizeUnlocks) : IDebugTabTree
|
||||
public class CustomizationUnlockPanel(CustomizeUnlockManager _customizeUnlocks) : IGameDataDrawer
|
||||
{
|
||||
public string Label
|
||||
=> "Customizations";
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
using System.IO;
|
||||
using System.Numerics;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Interop;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.GameData.Gui.Debug;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DebugTab;
|
||||
|
||||
public class DatFilePanel(ImportService _importService) : IDebugTabTree
|
||||
public class DatFilePanel(ImportService _importService) : IGameDataDrawer
|
||||
{
|
||||
public string Label
|
||||
=> "Character Dat File";
|
||||
|
|
@ -35,7 +35,7 @@ public class DatFilePanel(ImportService _importService) : IDebugTabTree
|
|||
ImGui.TextUnformatted(_datFile.Value.Version.ToString());
|
||||
ImGui.TextUnformatted(_datFile.Value.Time.LocalDateTime.ToString("g"));
|
||||
ImGui.TextUnformatted(_datFile.Value.Voice.ToString());
|
||||
ImGui.TextUnformatted(_datFile.Value.Customize.Data.ToString());
|
||||
ImGui.TextUnformatted(_datFile.Value.Customize.ToString());
|
||||
ImGui.TextUnformatted(_datFile.Value.Description);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,20 +1,14 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Dalamud.Interface.Utility;
|
||||
using ImGuiNET;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using OtterGui.Services;
|
||||
using OtterGui.Widgets;
|
||||
using ImGuiClip = Dalamud.Interface.Utility.ImGuiClip;
|
||||
|
||||
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
|
||||
=> _config.DebugMode;
|
||||
|
|
@ -24,11 +18,11 @@ public unsafe class DebugTab(IServiceProvider _provider) : ITab
|
|||
|
||||
private readonly DebugTabHeader[] _headers =
|
||||
[
|
||||
DebugTabHeader.CreateInterop(_provider),
|
||||
DebugTabHeader.CreateGameData(_provider),
|
||||
DebugTabHeader.CreateDesigns(_provider),
|
||||
DebugTabHeader.CreateState(_provider),
|
||||
DebugTabHeader.CreateUnlocks(_provider),
|
||||
DebugTabHeader.CreateInterop(manager.Provider!),
|
||||
DebugTabHeader.CreateGameData(manager.Provider!),
|
||||
DebugTabHeader.CreateDesigns(manager.Provider!),
|
||||
DebugTabHeader.CreateState(manager.Provider!),
|
||||
DebugTabHeader.CreateUnlocks(manager.Provider!),
|
||||
];
|
||||
|
||||
public void DrawContent()
|
||||
|
|
@ -37,55 +31,12 @@ public unsafe class DebugTab(IServiceProvider _provider) : ITab
|
|||
if (!child)
|
||||
return;
|
||||
|
||||
if (ImGui.CollapsingHeader("General"))
|
||||
{
|
||||
manager.Timers.Draw("Timers");
|
||||
}
|
||||
|
||||
foreach (var header in _headers)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,21 +3,14 @@ using System.Collections.Generic;
|
|||
using ImGuiNET;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Gui.Debug;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DebugTab;
|
||||
|
||||
public interface IDebugTabTree
|
||||
public class DebugTabHeader(string label, params IGameDataDrawer[] subTrees)
|
||||
{
|
||||
public string Label { get; }
|
||||
public void Draw();
|
||||
|
||||
public bool Disabled { get; }
|
||||
}
|
||||
|
||||
public class DebugTabHeader(string label, params IDebugTabTree[] subTrees)
|
||||
{
|
||||
public string Label { get; } = label;
|
||||
public IReadOnlyList<IDebugTabTree> SubTrees { get; } = subTrees;
|
||||
public string Label { get; } = label;
|
||||
public IReadOnlyList<IGameDataDrawer> SubTrees { get; } = subTrees;
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
|
|
@ -26,14 +19,13 @@ public class DebugTabHeader(string label, params IDebugTabTree[] 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);
|
||||
if (!tree)
|
||||
continue;
|
||||
disabled.Dispose();
|
||||
subTree.Draw();
|
||||
}
|
||||
|
||||
subTree.Draw();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -52,13 +44,15 @@ public class DebugTabHeader(string label, params IDebugTabTree[] subTrees)
|
|||
=> new
|
||||
(
|
||||
"Game Data",
|
||||
provider.GetRequiredService<IdentifierPanel>(),
|
||||
provider.GetRequiredService<RestrictedGearPanel>(),
|
||||
provider.GetRequiredService<ActorServicePanel>(),
|
||||
provider.GetRequiredService<ItemManagerPanel>(),
|
||||
provider.GetRequiredService<StainPanel>(),
|
||||
provider.GetRequiredService<DataServiceDiagnosticsDrawer>(),
|
||||
provider.GetRequiredService<IdentificationDrawer>(),
|
||||
provider.GetRequiredService<RestrictedGearDrawer>(),
|
||||
provider.GetRequiredService<ActorDataDrawer>(),
|
||||
provider.GetRequiredService<ItemDataDrawer>(),
|
||||
provider.GetRequiredService<DictStainDrawer>(),
|
||||
provider.GetRequiredService<CustomizationServicePanel>(),
|
||||
provider.GetRequiredService<JobPanel>(),
|
||||
provider.GetRequiredService<DictJobDrawer>(),
|
||||
provider.GetRequiredService<DictJobGroupDrawer>(),
|
||||
provider.GetRequiredService<NpcAppearancePanel>()
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -8,10 +8,11 @@ using ImGuiNET;
|
|||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Gui.Debug;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DebugTab;
|
||||
|
||||
public class DesignConverterPanel(DesignConverter _designConverter) : IDebugTabTree
|
||||
public class DesignConverterPanel(DesignConverter _designConverter) : IGameDataDrawer
|
||||
{
|
||||
public string Label
|
||||
=> "Design Converter";
|
||||
|
|
|
|||
|
|
@ -1,17 +1,16 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Dalamud.Interface;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Gui.Debug;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DebugTab;
|
||||
|
||||
public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _designFileSystem) : IDebugTabTree
|
||||
public class DesignManagerPanel(DesignManager _designManager, DesignFileSystem _designFileSystem) : IGameDataDrawer
|
||||
{
|
||||
public string Label
|
||||
=> $"Design Manager ({_designManager.Designs.Count} Designs)###Design Manager";
|
||||
|
|
|
|||
|
|
@ -1,19 +1,18 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Dalamud.Interface;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Gui.Debug;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DebugTab;
|
||||
|
||||
public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDebugTabTree
|
||||
public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IGameDataDrawer
|
||||
{
|
||||
public string Label
|
||||
=> "Base64 Design Tester";
|
||||
|
|
@ -85,7 +84,7 @@ public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDe
|
|||
DrawDesignData(_parse64);
|
||||
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
ImGui.TextUnformatted(_base64);
|
||||
using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing with { X = 0 }))
|
||||
using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing with { X = 0 }))
|
||||
{
|
||||
foreach (var (c1, c2) in _restore.Zip(_base64))
|
||||
{
|
||||
|
|
@ -99,7 +98,7 @@ public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDe
|
|||
|
||||
foreach (var ((b1, b2), idx) in _base64Bytes.Zip(_restoreBytes).WithIndex())
|
||||
{
|
||||
using (var group = ImRaii.Group())
|
||||
using (_ = ImRaii.Group())
|
||||
{
|
||||
ImGui.TextUnformatted(idx.ToString("D2"));
|
||||
ImGui.TextUnformatted(b1.ToString("X2"));
|
||||
|
|
@ -121,7 +120,7 @@ public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDe
|
|||
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
foreach (var (b, idx) in _base64Bytes.WithIndex())
|
||||
{
|
||||
using (var group = ImRaii.Group())
|
||||
using (_ = ImRaii.Group())
|
||||
{
|
||||
ImGui.TextUnformatted(idx.ToString("D2"));
|
||||
ImGui.TextUnformatted(b.ToString("X2"));
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
using Glamourer.State;
|
||||
using ImGuiNET;
|
||||
using Penumbra.GameData.Gui.Debug;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DebugTab;
|
||||
|
||||
public class FunPanel(FunModule _funModule, Configuration _config) : IDebugTabTree
|
||||
public class FunPanel(FunModule _funModule, Configuration _config) : IGameDataDrawer
|
||||
{
|
||||
public string Label
|
||||
=> "Fun Module";
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
|
@ -2,10 +2,11 @@
|
|||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Gui.Debug;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DebugTab;
|
||||
|
||||
public unsafe class InventoryPanel : IDebugTabTree
|
||||
public unsafe class InventoryPanel : IGameDataDrawer
|
||||
{
|
||||
public string Label
|
||||
=> "Inventory";
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using System;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.Api;
|
||||
using Glamourer.Interop;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Gui;
|
||||
using Penumbra.GameData.Gui.Debug;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DebugTab;
|
||||
|
||||
public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManager _objectManager) : IDebugTabTree
|
||||
public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManager _objectManager) : IGameDataDrawer
|
||||
{
|
||||
public string Label
|
||||
=> "IPC Tester";
|
||||
|
|
@ -18,17 +21,23 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag
|
|||
public bool Disabled
|
||||
=> false;
|
||||
|
||||
private int _gameObjectIndex;
|
||||
private string _gameObjectName = string.Empty;
|
||||
private string _base64Apply = string.Empty;
|
||||
private string _designIdentifier = string.Empty;
|
||||
private int _gameObjectIndex;
|
||||
private CustomItemId _customItemId;
|
||||
private StainId _stainId;
|
||||
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.InputTextWithHint("##gameObject", "Character Name...", ref _gameObjectName, 64);
|
||||
ImGui.InputTextWithHint("##base64", "Design Base64...", ref _base64Apply, 2047);
|
||||
ImGui.InputTextWithHint("##identifier", "Design identifier...", ref _designIdentifier, 36);
|
||||
ImGui.InputTextWithHint("##gameObject", "Character Name...", ref _gameObjectName, 64);
|
||||
ImGui.InputTextWithHint("##base64", "Design Base64...", ref _base64Apply, 2047);
|
||||
ImGui.InputTextWithHint("##identifier", "Design identifier...", ref _designIdentifier, 36);
|
||||
DrawItemInput();
|
||||
using var table = ImRaii.Table("##ipc", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
|
||||
if (!table)
|
||||
return;
|
||||
|
|
@ -54,15 +63,6 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag
|
|||
else
|
||||
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);
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGui.Button("Revert##Name"))
|
||||
|
|
@ -106,6 +106,19 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag
|
|||
GlamourerIpc.ApplyOnlyCustomizationToCharacterSubscriber(_pluginInterface)
|
||||
.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);
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGui.Button("Unlock##CustomizeCharacter"))
|
||||
|
|
@ -118,15 +131,52 @@ public class IpcTesterPanel(DalamudPluginInterface _pluginInterface, ObjectManag
|
|||
GlamourerIpc.RevertToAutomationCharacterSubscriber(_pluginInterface)
|
||||
.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();
|
||||
if (ImGui.Button("Apply##ByGuidCharacter"))
|
||||
GlamourerIpc.ApplyByGuidToCharacterSubscriber(_pluginInterface)
|
||||
.Invoke(Guid.Parse(_designIdentifier), _objectManager.Objects[_gameObjectIndex] as Character);
|
||||
var designList = GlamourerIpc.GetDesignListSubscriber(_pluginInterface)
|
||||
.Invoke();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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())})")));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,11 +7,12 @@ using ImGuiNET;
|
|||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Gui.Debug;
|
||||
using ImGuiClip = OtterGui.ImGuiClip;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DebugTab;
|
||||
|
||||
public class ItemUnlockPanel(ItemUnlockManager _itemUnlocks, ItemManager _items) : IDebugTabTree
|
||||
public class ItemUnlockPanel(ItemUnlockManager _itemUnlocks, ItemManager _items) : IGameDataDrawer
|
||||
{
|
||||
public string Label
|
||||
=> "Unlocked Items";
|
||||
|
|
@ -39,7 +40,7 @@ public class ItemUnlockPanel(ItemUnlockManager _itemUnlocks, ItemManager _items)
|
|||
var remainder = ImGuiClip.ClippedDraw(_itemUnlocks, skips, t =>
|
||||
{
|
||||
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.Type.ToName());
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Gui.Debug;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DebugTab;
|
||||
|
|
@ -18,7 +17,7 @@ public unsafe class ModelEvaluationPanel(
|
|||
VisorService _visorService,
|
||||
UpdateSlotService _updateSlotService,
|
||||
ChangeCustomizeService _changeCustomizeService,
|
||||
CrestService _crestService) : IDebugTabTree
|
||||
CrestService _crestService) : IGameDataDrawer
|
||||
{
|
||||
public string Label
|
||||
=> "Model Evaluation";
|
||||
|
|
@ -126,15 +125,14 @@ public unsafe class ModelEvaluationPanel(
|
|||
model.AsHuman->Head.Value == 0 ? actor.GetArmor(EquipSlot.Head) : CharacterArmor.Empty);
|
||||
}
|
||||
|
||||
private void DrawWeaponState(Actor actor, Model model)
|
||||
private static void DrawWeaponState(Actor actor, Model model)
|
||||
{
|
||||
using var id = ImRaii.PushId("WeaponState");
|
||||
ImGuiUtil.DrawTableColumn("Weapon State");
|
||||
ImGuiUtil.DrawTableColumn(actor.IsCharacter
|
||||
? actor.AsCharacter->DrawData.IsWeaponHidden ? "Hidden" : "Visible"
|
||||
: "No Character");
|
||||
var text = string.Empty;
|
||||
|
||||
string text;
|
||||
if (!model.IsHuman)
|
||||
{
|
||||
text = "No Model";
|
||||
|
|
@ -146,19 +144,14 @@ public unsafe class ModelEvaluationPanel(
|
|||
else
|
||||
{
|
||||
var weapon = (DrawObject*)model.AsDrawObject->Object.ChildObject;
|
||||
if ((weapon->Flags & 0x09) == 0x09)
|
||||
text = "Visible";
|
||||
else
|
||||
text = "Hidden";
|
||||
text = (weapon->Flags & 0x09) == 0x09 ? "Visible" : "Hidden";
|
||||
}
|
||||
|
||||
ImGuiUtil.DrawTableColumn(text);
|
||||
ImGui.TableNextColumn();
|
||||
if (!model.IsHuman)
|
||||
return;
|
||||
}
|
||||
|
||||
private void DrawWetness(Actor actor, Model model)
|
||||
private static void DrawWetness(Actor actor, Model model)
|
||||
{
|
||||
using var id = ImRaii.PushId("Wetness");
|
||||
ImGuiUtil.DrawTableColumn("Wetness");
|
||||
|
|
@ -199,7 +192,7 @@ public unsafe class ModelEvaluationPanel(
|
|||
|
||||
if (ImGui.SmallButton("Change Piece"))
|
||||
_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();
|
||||
if (ImGui.SmallButton("Change Stain"))
|
||||
_updateSlotService.UpdateStain(model, slot, 5);
|
||||
|
|
@ -212,12 +205,12 @@ public unsafe class ModelEvaluationPanel(
|
|||
private void DrawCustomize(Actor actor, Model model)
|
||||
{
|
||||
using var id = ImRaii.PushId("Customize");
|
||||
var actorCustomize = new Customize(actor.IsCharacter
|
||||
? *(Penumbra.GameData.Structs.CustomizeData*)&actor.AsCharacter->DrawData.CustomizeData
|
||||
: new Penumbra.GameData.Structs.CustomizeData());
|
||||
var modelCustomize = new Customize(model.IsHuman
|
||||
? *(Penumbra.GameData.Structs.CustomizeData*)model.AsHuman->Customize.Data
|
||||
: new Penumbra.GameData.Structs.CustomizeData());
|
||||
var actorCustomize = actor.IsCharacter
|
||||
? *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData
|
||||
: new CustomizeArray();
|
||||
var modelCustomize = model.IsHuman
|
||||
? *(CustomizeArray*)model.AsHuman->Customize.Data
|
||||
: new CustomizeArray();
|
||||
foreach (var type in Enum.GetValues<CustomizeIndex>())
|
||||
{
|
||||
using var id2 = ImRaii.PushId((int)type);
|
||||
|
|
@ -235,7 +228,7 @@ public unsafe class ModelEvaluationPanel(
|
|||
var shift = BitOperations.TrailingZeroCount(mask);
|
||||
var newValue = value + (1 << shift);
|
||||
modelCustomize.Set(type, (CustomizeValue)newValue);
|
||||
_changeCustomizeService.UpdateCustomize(model, modelCustomize.Data);
|
||||
_changeCustomizeService.UpdateCustomize(model, modelCustomize);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
|
@ -246,14 +239,14 @@ public unsafe class ModelEvaluationPanel(
|
|||
var shift = BitOperations.TrailingZeroCount(mask);
|
||||
var newValue = value - (1 << shift);
|
||||
modelCustomize.Set(type, (CustomizeValue)newValue);
|
||||
_changeCustomizeService.UpdateCustomize(model, modelCustomize.Data);
|
||||
_changeCustomizeService.UpdateCustomize(model, modelCustomize);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.SmallButton("Reset"))
|
||||
{
|
||||
modelCustomize.Set(type, actorCustomize[type]);
|
||||
_changeCustomizeService.UpdateCustomize(model, modelCustomize.Data);
|
||||
_changeCustomizeService.UpdateCustomize(model, modelCustomize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,19 +3,22 @@ using System.Numerics;
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.State;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Gui.Debug;
|
||||
using ImGuiClip = OtterGui.ImGuiClip;
|
||||
|
||||
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
|
||||
=> "NPC Appearance";
|
||||
|
|
@ -23,23 +26,27 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM
|
|||
public bool Disabled
|
||||
=> false;
|
||||
|
||||
private string _npcFilter = string.Empty;
|
||||
private bool _customizeOrGear = false;
|
||||
private string _npcFilter = string.Empty;
|
||||
private bool _customizeOrGear;
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
ImGui.Checkbox("Compare Customize (or Gear)", ref _customizeOrGear);
|
||||
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));
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
if (resetScroll)
|
||||
ImGui.SetScrollY(0);
|
||||
|
||||
ImGui.TableSetupColumn("Button", ImGuiTableColumnFlags.WidthFixed);
|
||||
ImGui.TableSetupColumn("Name", ImGuiTableColumnFlags.WidthFixed, ImGuiHelpers.GlobalScale * 300);
|
||||
ImGui.TableSetupColumn("Kind", ImGuiTableColumnFlags.WidthFixed);
|
||||
ImGui.TableSetupColumn("Id", ImGuiTableColumnFlags.WidthFixed);
|
||||
ImGui.TableSetupColumn("Visor", ImGuiTableColumnFlags.WidthFixed);
|
||||
ImGui.TableSetupColumn("Compare", ImGuiTableColumnFlags.WidthStretch);
|
||||
|
||||
|
|
@ -48,21 +55,19 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM
|
|||
ImGui.TableNextRow();
|
||||
var idx = 0;
|
||||
var remainder = ImGuiClip.FilteredClippedDraw(_npcCombo.Items, skips,
|
||||
d => d.Name.Contains(_npcFilter, StringComparison.OrdinalIgnoreCase), Draw);
|
||||
d => d.Name.Contains(_npcFilter, StringComparison.OrdinalIgnoreCase), DrawData);
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiClip.DrawEndDummy(remainder, ImGui.GetFrameHeightWithSpacing());
|
||||
|
||||
|
||||
return;
|
||||
|
||||
void Draw(CustomizationNpcOptions.NpcData data)
|
||||
void DrawData(NpcData data)
|
||||
{
|
||||
using var id = ImRaii.PushId(idx++);
|
||||
var disabled = !_state.GetOrCreate(_objectManager.Player, out var state);
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGuiUtil.DrawDisabledButton("Apply", Vector2.Zero, string.Empty, disabled, false))
|
||||
if (ImGuiUtil.DrawDisabledButton("Apply", Vector2.Zero, string.Empty, disabled))
|
||||
{
|
||||
foreach (var (slot, item, stain) in _designConverter.FromDrawData(data.Equip.ToArray(), data.Mainhand, data.Offhand))
|
||||
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.ChangeVisorState(state!, data.VisorToggled, 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.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.AlignTextToFramePadding();
|
||||
|
|
@ -86,7 +95,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM
|
|||
using var mono = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted(_customizeOrGear ? data.Customize.Data.ToString() : data.WriteGear());
|
||||
ImGui.TextUnformatted(_customizeOrGear ? data.Customize.ToString() : data.WriteGear());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,15 @@ using System.Globalization;
|
|||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Services;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Gui.Debug;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DebugTab;
|
||||
|
||||
public class ObjectManagerPanel(ObjectManager _objectManager, ActorService _actors) : IDebugTabTree
|
||||
public class ObjectManagerPanel(ObjectManager _objectManager, ActorManager _actors) : IGameDataDrawer
|
||||
{
|
||||
public string Label
|
||||
=> "Object Manager";
|
||||
|
|
@ -33,7 +34,7 @@ public class ObjectManagerPanel(ObjectManager _objectManager, ActorService _acto
|
|||
ImGui.TableNextColumn();
|
||||
|
||||
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("Player Character");
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
using System;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Glamourer.Gui.Tabs.DebugTab;
|
||||
using Glamourer.Gui;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.Interop.Structs;
|
||||
using ImGuiNET;
|
||||
|
|
@ -10,11 +7,12 @@ using OtterGui;
|
|||
using OtterGui.Raii;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Gui.Debug;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
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
|
||||
=> "Penumbra Interop";
|
||||
|
|
@ -27,9 +25,6 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem
|
|||
|
||||
public void Draw()
|
||||
{
|
||||
if (!ImGui.CollapsingHeader("Penumbra"))
|
||||
return;
|
||||
|
||||
using var table = ImRaii.Table("##PenumbraTable", 3, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
|
||||
if (!table)
|
||||
return;
|
||||
|
|
@ -59,7 +54,7 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem
|
|||
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.InputInt("##CutsceneIndex", ref _gameObjectIndex, 0, 0);
|
||||
ImGuiUtil.DrawTableColumn(_penumbra.Available
|
||||
? _penumbra.CutsceneParent(_gameObjectIndex).ToString()
|
||||
? _penumbra.CutsceneParent((ushort) _gameObjectIndex).ToString()
|
||||
: "Penumbra Unavailable");
|
||||
|
||||
ImGuiUtil.DrawTableColumn("Redraw Object");
|
||||
|
|
@ -67,7 +62,7 @@ public unsafe class PenumbraPanel(PenumbraService _penumbra, PenumbraChangedItem
|
|||
ImGui.SetNextItemWidth(200 * ImGuiHelpers.GlobalScale);
|
||||
ImGui.InputInt("##redrawObject", ref _gameObjectIndex, 0, 0);
|
||||
ImGui.TableNextColumn();
|
||||
using (var disabled = ImRaii.Disabled(!_penumbra.Available))
|
||||
using (_ = ImRaii.Disabled(!_penumbra.Available))
|
||||
{
|
||||
if (ImGui.SmallButton("Redraw"))
|
||||
_penumbra.RedrawObject((ObjectIndex)_gameObjectIndex, RedrawType.Redraw);
|
||||
|
|
|
|||
|
|
@ -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}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,10 +3,11 @@ using Glamourer.Interop;
|
|||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.State;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Gui.Debug;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DebugTab;
|
||||
|
||||
public class RetainedStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IDebugTabTree
|
||||
public class RetainedStatePanel(StateManager _stateManager, ObjectManager _objectManager) : IGameDataDrawer
|
||||
{
|
||||
public string Label
|
||||
=> "Retained States (Inactive Actors)";
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,11 +7,12 @@ using ImGuiNET;
|
|||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Gui.Debug;
|
||||
using ImGuiClip = OtterGui.ImGuiClip;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DebugTab;
|
||||
|
||||
public class UnlockableItemsPanel(ItemUnlockManager _itemUnlocks, ItemManager _items) : IDebugTabTree
|
||||
public class UnlockableItemsPanel(ItemUnlockManager _itemUnlocks, ItemManager _items) : IGameDataDrawer
|
||||
{
|
||||
public string Label
|
||||
=> "Unlockable Items";
|
||||
|
|
@ -40,7 +41,7 @@ public class UnlockableItemsPanel(ItemUnlockManager _itemUnlocks, ItemManager _i
|
|||
var remainder = ImGuiClip.ClippedDraw(_itemUnlocks.Unlockable, skips, t =>
|
||||
{
|
||||
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.Type.ToName());
|
||||
|
|
|
|||
|
|
@ -7,14 +7,12 @@ using Dalamud.Interface.ImGuiFileDialog;
|
|||
using Dalamud.Interface.Internal.Notifications;
|
||||
using FFXIVClientStructs.FFXIV.Client.System.Framework;
|
||||
using Glamourer.Automation;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Gui.Customization;
|
||||
using Glamourer.Gui.Equipment;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
|
|
@ -156,9 +154,9 @@ public class DesignPanel(DesignFileSystemSelector _selector, CustomizationDrawer
|
|||
|
||||
private void DrawCustomizeApplication()
|
||||
{
|
||||
var set = _selector.Selected!.CustomizationSet;
|
||||
var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender;
|
||||
var flags = _selector.Selected!.ApplyCustomize == 0 ? 0 : (_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1;
|
||||
var set = _selector.Selected!.CustomizeSet;
|
||||
var available = set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender | CustomizeFlag.BodyType;
|
||||
var flags = _selector.Selected!.ApplyCustomizeExcludingBodyType == 0 ? 0 : (_selector.Selected!.ApplyCustomize & available) == available ? 3 : 1;
|
||||
if (ImGui.CheckboxFlags("Apply All Customizations", ref flags, 3))
|
||||
{
|
||||
var newFlags = flags == 3;
|
||||
|
|
|
|||
|
|
@ -1,36 +1,11 @@
|
|||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.GameData;
|
||||
using OtterGui.Widgets;
|
||||
using Penumbra.GameData;
|
||||
|
||||
namespace Glamourer.Gui.Tabs;
|
||||
|
||||
public class NpcCombo(ActorService actorManager, IdentifierService identifier, IDataManager data)
|
||||
: FilterComboBase<CustomizationNpcOptions.NpcData>(new LazyList(actorManager, identifier, data), false, Glamourer.Log)
|
||||
public class NpcCombo(NpcCustomizeSet npcCustomizeSet)
|
||||
: FilterComboCache<NpcData>(npcCustomizeSet, Glamourer.Log)
|
||||
{
|
||||
private class LazyList(ActorService actorManager, IdentifierService identifier, IDataManager data)
|
||||
: 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)
|
||||
protected override string ToString(NpcData obj)
|
||||
=> obj.Name;
|
||||
}
|
||||
|
|
|
|||
141
Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs
Normal file
141
Glamourer/Gui/Tabs/NpcTab/LocalNpcAppearanceData.cs
Normal 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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
45
Glamourer/Gui/Tabs/NpcTab/NpcFilter.cs
Normal file
45
Glamourer/Gui/Tabs/NpcTab/NpcFilter.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
294
Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs
Normal file
294
Glamourer/Gui/Tabs/NpcTab/NpcPanel.cs
Normal 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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
95
Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs
Normal file
95
Glamourer/Gui/Tabs/NpcTab/NpcSelector.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
19
Glamourer/Gui/Tabs/NpcTab/NpcTab.cs
Normal file
19
Glamourer/Gui/Tabs/NpcTab/NpcTab.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -60,7 +60,7 @@ public class SettingsTab : ITab
|
|||
if (!child)
|
||||
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);
|
||||
ImGui.NewLine();
|
||||
ImGui.NewLine();
|
||||
|
|
@ -247,22 +247,17 @@ public class SettingsTab : ITab
|
|||
if (ImGui.Checkbox(code, ref state))
|
||||
{
|
||||
action(state);
|
||||
_config.Codes[i] = (code, state);
|
||||
_codeService.VerifyState();
|
||||
_config.Save();
|
||||
_codeService.SaveState();
|
||||
}
|
||||
}
|
||||
|
||||
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!?!"))
|
||||
_funModule.WhoIsThat();
|
||||
}
|
||||
if (ImGui.Button("Who is that!?!"))
|
||||
_funModule.WhoIsThat();
|
||||
}
|
||||
|
||||
private void DrawCodeHints()
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ using System.Linq;
|
|||
using System.Numerics;
|
||||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Unlocks;
|
||||
|
|
@ -19,7 +19,7 @@ public class UnlockOverview
|
|||
{
|
||||
private readonly ItemManager _items;
|
||||
private readonly ItemUnlockManager _itemUnlocks;
|
||||
private readonly CustomizationService _customizations;
|
||||
private readonly CustomizeService _customizations;
|
||||
private readonly CustomizeUnlockManager _customizeUnlocks;
|
||||
private readonly PenumbraChangedItemTooltip _tooltip;
|
||||
private readonly TextureService _textures;
|
||||
|
|
@ -41,7 +41,7 @@ public class UnlockOverview
|
|||
|
||||
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;
|
||||
|
||||
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.AwaitedService.GetList(clan, gender).HairStyles.Count == 0)
|
||||
continue;
|
||||
if (_customizations.Manager.GetSet(clan, gender).HairStyles.Count == 0)
|
||||
continue;
|
||||
|
||||
if (ImGui.Selectable($"{(gender is Gender.Male ? '♂' : '♀')} {clan.ToShortName()} Hair & Paint",
|
||||
_selected2 == clan && _selected3 == gender))
|
||||
{
|
||||
_selected1 = FullEquipType.Unknown;
|
||||
_selected2 = clan;
|
||||
_selected3 = gender;
|
||||
}
|
||||
if (ImGui.Selectable($"{(gender is Gender.Male ? '♂' : '♀')} {clan.ToShortName()} Hair & Paint",
|
||||
_selected2 == clan && _selected3 == gender))
|
||||
{
|
||||
_selected1 = FullEquipType.Unknown;
|
||||
_selected2 = clan;
|
||||
_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,
|
||||
JobService jobs, FavoriteManager favorites)
|
||||
{
|
||||
|
|
@ -107,7 +104,7 @@ public class UnlockOverview
|
|||
|
||||
private void DrawCustomizations()
|
||||
{
|
||||
var set = _customizations.AwaitedService.GetList(_selected2, _selected3);
|
||||
var set = _customizations.Manager.GetSet(_selected2, _selected3);
|
||||
|
||||
var spacing = IconSpacing;
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing);
|
||||
|
|
@ -121,10 +118,10 @@ public class UnlockOverview
|
|||
continue;
|
||||
|
||||
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,
|
||||
unlocked || _codes.EnabledShirts ? Vector4.One : UnavailableTint);
|
||||
unlocked || _codes.Enabled(CodeService.CodeFlag.Shirts) ? Vector4.One : UnavailableTint);
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
using var tt = ImRaii.Tooltip();
|
||||
|
|
@ -150,7 +147,7 @@ public class UnlockOverview
|
|||
|
||||
private void DrawItems()
|
||||
{
|
||||
if (!_items.ItemService.AwaitedService.TryGetValue(_selected1, out var items))
|
||||
if (!_items.ItemData.ByType.TryGetValue(_selected1, out var items))
|
||||
return;
|
||||
|
||||
var spacing = IconSpacing;
|
||||
|
|
@ -160,6 +157,30 @@ public class UnlockOverview
|
|||
var numRows = (items.Count + iconsPerRow - 1) / iconsPerRow;
|
||||
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)
|
||||
{
|
||||
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));
|
||||
|
||||
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))
|
||||
ImGui.GetWindowDrawList().AddRect(ImGui.GetItemRectMin(), ImGui.GetItemRectMax(), ColorId.FavoriteStarOn.Value(),
|
||||
2 * ImGuiHelpers.GlobalScale, ImDrawFlags.RoundCornersAll, 4 * ImGuiHelpers.GlobalScale);
|
||||
|
|
@ -189,7 +210,7 @@ public class UnlockOverview
|
|||
ImGui.TextUnformatted($"{item.Type.ToName()} ({slot.ToName()})");
|
||||
if (item.Type.ValidOffhand().IsOffhandType())
|
||||
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
|
||||
ImGui.TextUnformatted(slot is EquipSlot.MainHand ? $"{item.Weapon()}" : $"{item.Armor()}");
|
||||
ImGui.TextUnformatted(
|
||||
|
|
@ -219,29 +240,6 @@ public class UnlockOverview
|
|||
_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
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue