mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 10:17:23 +01:00
Add some support for NPC customizations.
This commit is contained in:
parent
38527f4320
commit
56ad7dc968
6 changed files with 197 additions and 11 deletions
141
Glamourer.GameData/Customization/CustomizationNpcOptions.cs
Normal file
141
Glamourer.GameData/Customization/CustomizationNpcOptions.cs
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using OtterGui;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Customization;
|
||||
|
||||
public static class CustomizationNpcOptions
|
||||
{
|
||||
public static Dictionary<(SubRace, Gender), HashSet<(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;
|
||||
}
|
||||
|
||||
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.Row != 0)
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
@ -45,7 +45,7 @@ public partial class CustomizationOptions
|
|||
|
||||
|
||||
// Get the index for the given pair of tribe and gender.
|
||||
private static int ToIndex(SubRace race, Gender 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)
|
||||
|
|
@ -59,8 +59,6 @@ public partial class CustomizationOptions
|
|||
|
||||
public partial class CustomizationOptions
|
||||
{
|
||||
private readonly bool _valid;
|
||||
|
||||
public string GetName(CustomName name)
|
||||
=> _names[(int)name];
|
||||
|
||||
|
|
@ -68,13 +66,13 @@ public partial class CustomizationOptions
|
|||
{
|
||||
var tmp = new TemporaryData(gameData, this);
|
||||
_icons = new IconStorage(pi, gameData, _customizationSets.Length * 50);
|
||||
_valid = tmp.Valid;
|
||||
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.
|
||||
|
|
@ -171,11 +169,24 @@ public partial class CustomizationOptions
|
|||
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(DataManager gameData, CustomizationOptions options)
|
||||
{
|
||||
_options = options;
|
||||
_cmpFile = new CmpFile(gameData);
|
||||
_customizeSheet = gameData.GetExcelSheet<CharaMakeCustomize>()!;
|
||||
_bnpcCustomize = gameData.GetExcelSheet<BNpcCustomize>()!;
|
||||
_enpcBase = gameData.GetExcelSheet<ENpcBase>()!;
|
||||
Lobby = gameData.GetExcelSheet<Lobby>()!;
|
||||
var tmp = gameData.Excel.GetType().GetMethod("GetSheet", BindingFlags.Instance | BindingFlags.NonPublic)?
|
||||
.MakeGenericMethod(typeof(CharaMakeParams)).Invoke(gameData.Excel, new object?[]
|
||||
|
|
@ -199,6 +210,8 @@ public partial class CustomizationOptions
|
|||
private readonly ExcelSheet<CharaMakeCustomize> _customizeSheet;
|
||||
private readonly ExcelSheet<CharaMakeParams> _listSheet;
|
||||
private readonly ExcelSheet<HairMakeType> _hairSheet;
|
||||
private readonly ExcelSheet<BNpcCustomize> _bnpcCustomize;
|
||||
private readonly ExcelSheet<ENpcBase> _enpcBase;
|
||||
public readonly ExcelSheet<Lobby> Lobby;
|
||||
private readonly CmpFile _cmpFile;
|
||||
|
||||
|
|
@ -213,6 +226,7 @@ public partial class CustomizationOptions
|
|||
|
||||
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)))
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ public class CustomizationSet
|
|||
public (CustomizeData, CustomizeData) LegacyTattoo { get; internal set; }
|
||||
public IReadOnlyList<CustomizeData> FacePaints { get; internal init; } = null!;
|
||||
|
||||
public IReadOnlyList<(CustomizeIndex Type, CustomizeValue Value)> NpcOptions { get; internal set; } =
|
||||
Array.Empty<(CustomizeIndex Type, CustomizeValue Value)>();
|
||||
|
||||
// Always Color Selector
|
||||
public IReadOnlyList<CustomizeData> SkinColors { get; internal init; } = null!;
|
||||
|
|
@ -77,6 +79,17 @@ public class CustomizationSet
|
|||
public IReadOnlyList<CustomizeData> LipColorsLight { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizeData> LipColorsDark { get; internal init; } = null!;
|
||||
|
||||
public bool Validate(CustomizeIndex index, CustomizeValue value, out CustomizeData? custom, CustomizeValue face)
|
||||
{
|
||||
if (IsAvailable(index))
|
||||
return DataByValue(index, value, out custom, face) >= 0
|
||||
|| NpcOptions.Any(t => t.Type == index && t.Value == value);
|
||||
|
||||
custom = null;
|
||||
return value == CustomizeValue.Zero;
|
||||
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public int DataByValue(CustomizeIndex index, CustomizeValue value, out CustomizeData? custom, CustomizeValue face)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ public class Glamourer : IDalamudPlugin
|
|||
public static readonly Logger Log = new();
|
||||
public static ChatService Chat { get; private set; } = null!;
|
||||
|
||||
|
||||
private readonly ServiceProvider _services;
|
||||
|
||||
public Glamourer(DalamudPluginInterface pluginInterface)
|
||||
|
|
@ -45,7 +44,5 @@ public class Glamourer : IDalamudPlugin
|
|||
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_services?.Dispose();
|
||||
}
|
||||
=> _services?.Dispose();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -822,7 +822,11 @@ public unsafe class DebugTab : ITab
|
|||
foreach (var clan in _customization.AwaitedService.Clans)
|
||||
{
|
||||
foreach (var gender in _customization.AwaitedService.Genders)
|
||||
DrawCustomizationInfo(_customization.AwaitedService.GetList(clan, gender));
|
||||
{
|
||||
var set = _customization.AwaitedService.GetList(clan, gender);
|
||||
DrawCustomizationInfo(set);
|
||||
DrawNpcCustomizationInfo(set);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -846,6 +850,23 @@ public unsafe class DebugTab : ITab
|
|||
}
|
||||
}
|
||||
|
||||
private void DrawNpcCustomizationInfo(CustomizationSet set)
|
||||
{
|
||||
using var tree = ImRaii.TreeNode($"{_customization.ClanName(set.Clan, set.Gender)} {set.Gender} (NPC Options)");
|
||||
if (!tree)
|
||||
return;
|
||||
|
||||
using var table = ImRaii.Table("npc", 2, ImGuiTableFlags.SizingFixedFit | ImGuiTableFlags.RowBg);
|
||||
if (!table)
|
||||
return;
|
||||
|
||||
foreach(var (index, value) in set.NpcOptions)
|
||||
{
|
||||
ImGuiUtil.DrawTableColumn(index.ToString());
|
||||
ImGuiUtil.DrawTableColumn(value.Value.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Designs
|
||||
|
|
|
|||
|
|
@ -122,12 +122,12 @@ public sealed class CustomizationService : AsyncServiceWrapper<ICustomizationMan
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public static bool IsCustomizationValid(CustomizationSet set, CustomizeValue face, CustomizeIndex type, CustomizeValue value,
|
||||
[NotNullWhen(true)] out CustomizeData? data)
|
||||
=> set.DataByValue(type, value, out data, face) >= 0 || !set.IsAvailable(type) && value.Value == 0;
|
||||
=> set.Validate(type, value, out data, face);
|
||||
|
||||
/// <summary> Returns whether a customization value is valid for a given clan, gender and face. </summary>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public bool IsCustomizationValid(SubRace race, Gender gender, CustomizeValue face, CustomizeIndex type, CustomizeValue value)
|
||||
=> AwaitedService.GetList(race, gender).DataByValue(type, value, out _, face) >= 0;
|
||||
=> IsCustomizationValid(AwaitedService.GetList(race, gender), face, type, value);
|
||||
|
||||
/// <summary>
|
||||
/// Check that the given race and clan are valid.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue