mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2026-02-18 13:37:44 +01:00
Remove GameData, move a bunch of customization data to Penumbra.GameData and the rest to Glamourer, update accordingly. Some reformatting and cleanup.
This commit is contained in:
parent
e9d0e61b4c
commit
987c26a51d
83 changed files with 444 additions and 1620 deletions
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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,14 +4,12 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using System.Linq;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.UI.Misc;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using Glamourer.Unlocks;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.Actors;
|
||||
|
|
@ -209,7 +207,7 @@ public class AutoDesignApplier : IDisposable
|
|||
if (!GetPlayerSet(id, out var set))
|
||||
{
|
||||
if (_state.TryGetValue(id, out var s))
|
||||
s.LastJob = (byte)newJob.Id;
|
||||
s.LastJob = newJob.Id;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ using Glamourer.Designs;
|
|||
using Glamourer.Events;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
|
|
@ -516,7 +515,7 @@ public class AutoDesignManager : ISavable, IReadOnlyList<AutoDesignSet>, IDispos
|
|||
var jobs = conditions["JobGroup"]?.ToObject<int>() ?? -1;
|
||||
if (jobs >= 0)
|
||||
{
|
||||
if (!_jobs.JobGroups.TryGetValue((ushort)jobs, out var jobGroup))
|
||||
if (!_jobs.JobGroups.TryGetValue((JobGroupId)jobs, out var jobGroup))
|
||||
{
|
||||
Glamourer.Messager.NotificationMessage(
|
||||
$"Error parsing automatically applied design for set {setName}: The job condition {jobs} does not exist.",
|
||||
|
|
|
|||
|
|
@ -3,37 +3,30 @@ using System.Linq;
|
|||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.String;
|
||||
|
||||
namespace Glamourer.Automation;
|
||||
|
||||
public class FixedDesignMigrator
|
||||
public class FixedDesignMigrator(JobService jobs)
|
||||
{
|
||||
private readonly JobService _jobs;
|
||||
private List<(string Name, List<(string, JobGroup, bool)> Data)>? _migratedData;
|
||||
|
||||
public FixedDesignMigrator(JobService jobs)
|
||||
=> _jobs = jobs;
|
||||
private List<(string Name, List<(string, JobGroup, bool)> Data)>? _migratedData;
|
||||
|
||||
public void ConsumeMigratedData(ActorManager actors, DesignFileSystem designFileSystem, AutoDesignManager autoManager)
|
||||
{
|
||||
if (_migratedData == null)
|
||||
return;
|
||||
|
||||
foreach (var data in _migratedData)
|
||||
foreach (var (name, data) in _migratedData)
|
||||
{
|
||||
var allEnabled = true;
|
||||
var name = data.Name;
|
||||
if (autoManager.Any(d => name == d.Name))
|
||||
continue;
|
||||
|
||||
var id = ActorIdentifier.Invalid;
|
||||
if (ByteString.FromString(data.Name, out var byteString, false))
|
||||
if (ByteString.FromString(name, out var byteString))
|
||||
{
|
||||
id = actors.CreatePlayer(byteString, ushort.MaxValue);
|
||||
if (!id.IsValid)
|
||||
|
|
@ -46,16 +39,15 @@ public class FixedDesignMigrator
|
|||
id = actors.CreatePlayer(byteString, actors.Data.Worlds.First().Key);
|
||||
if (!id.IsValid)
|
||||
{
|
||||
Glamourer.Messager.NotificationMessage($"Could not migrate fixed design {data.Name}.", NotificationType.Error);
|
||||
allEnabled = false;
|
||||
Glamourer.Messager.NotificationMessage($"Could not migrate fixed design {name}.", NotificationType.Error);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
autoManager.AddDesignSet(name, id);
|
||||
autoManager.SetState(autoManager.Count - 1, allEnabled);
|
||||
autoManager.SetState(autoManager.Count - 1, true);
|
||||
var set = autoManager[^1];
|
||||
foreach (var design in data.Data.AsEnumerable().Reverse())
|
||||
foreach (var design in data.AsEnumerable().Reverse())
|
||||
{
|
||||
if (!designFileSystem.Find(design.Item1, out var child) || child is not DesignFileSystem.Leaf leaf)
|
||||
{
|
||||
|
|
@ -96,7 +88,7 @@ public class FixedDesignMigrator
|
|||
}
|
||||
|
||||
var job = obj["JobGroups"]?.ToObject<int>() ?? -1;
|
||||
if (job < 0 || !_jobs.JobGroups.TryGetValue((ushort)job, out var group))
|
||||
if (job < 0 || !jobs.JobGroups.TryGetValue((JobGroupId)job, out var group))
|
||||
{
|
||||
Glamourer.Messager.NotificationMessage("Could not semi-migrate fixed design: Invalid job group specified.",
|
||||
NotificationType.Warning);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
|
@ -85,12 +84,12 @@ public class DesignBase
|
|||
internal CrestFlag ApplyCrest = CrestExtensions.AllRelevant;
|
||||
private DesignFlags _designFlags = DesignFlags.ApplyHatVisible | DesignFlags.ApplyVisorState | DesignFlags.ApplyWeaponVisible;
|
||||
|
||||
public bool SetCustomize(CustomizationService customizationService, Customize customize)
|
||||
public bool SetCustomize(CustomizationService customizationService, CustomizeArray customize)
|
||||
{
|
||||
if (customize.Equals(_designData.Customize))
|
||||
return false;
|
||||
|
||||
_designData.Customize.Load(customize);
|
||||
_designData.Customize = customize;
|
||||
CustomizationSet = customizationService.Service.GetList(customize.Clan, customize.Gender);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -443,7 +442,7 @@ public class DesignBase
|
|||
{
|
||||
design._designData.ModelId = 0;
|
||||
design._designData.IsHuman = true;
|
||||
design.SetCustomize(customizations, Customize.Default);
|
||||
design.SetCustomize(customizations, CustomizeArray.Default);
|
||||
Glamourer.Messager.NotificationMessage("The loaded design does not contain any customization data, reset to default.",
|
||||
NotificationType.Warning);
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
using System;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using OtterGui;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
|
@ -62,7 +60,7 @@ public class DesignBase64Migration
|
|||
data.SetHatVisible((bytes[90] & 0x01) == 0);
|
||||
data.SetVisor((bytes[90] & 0x10) != 0);
|
||||
data.SetWeaponVisible((bytes[90] & 0x02) == 0);
|
||||
data.ModelId = (uint)bytes[91] | ((uint)bytes[92] << 8) | ((uint)bytes[93] << 16) | ((uint)bytes[94] << 24);
|
||||
data.ModelId = bytes[91] | ((uint)bytes[92] << 8) | ((uint)bytes[93] << 16) | ((uint)bytes[94] << 24);
|
||||
break;
|
||||
}
|
||||
case 5:
|
||||
|
|
@ -73,7 +71,7 @@ public class DesignBase64Migration
|
|||
data.SetHatVisible((bytes[90] & 0x01) == 0);
|
||||
data.SetVisor((bytes[90] & 0x10) != 0);
|
||||
data.SetWeaponVisible((bytes[90] & 0x02) == 0);
|
||||
data.ModelId = (uint)bytes[91] | ((uint)bytes[92] << 8) | ((uint)bytes[93] << 16) | ((uint)bytes[94] << 24);
|
||||
data.ModelId = bytes[91] | ((uint)bytes[92] << 8) | ((uint)bytes[93] << 16) | ((uint)bytes[94] << 24);
|
||||
break;
|
||||
default: throw new Exception($"Can not parse Base64 string into design for migration:\n\tInvalid Version {bytes[0]}.");
|
||||
}
|
||||
|
|
@ -102,11 +100,11 @@ public class DesignBase64Migration
|
|||
|
||||
if (!humans.IsHuman(data.ModelId))
|
||||
{
|
||||
data.LoadNonHuman(data.ModelId, *(Customize*)(ptr + 4), (nint)eq);
|
||||
data.LoadNonHuman(data.ModelId, *(CustomizeArray*)(ptr + 4), (nint)eq);
|
||||
return data;
|
||||
}
|
||||
|
||||
data.Customize.Load(*(Customize*)(ptr + 4));
|
||||
data.Customize = *(CustomizeArray*)(ptr + 4);
|
||||
foreach (var (slot, idx) in EquipSlotExtensions.EqdpSlots.WithIndex())
|
||||
{
|
||||
var mdl = eq[idx];
|
||||
|
|
@ -187,7 +185,7 @@ public class DesignBase64Migration
|
|||
| (equipFlags.HasFlag(EquipFlag.Wrist) ? 0x02 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.RFinger) ? 0x04 : 0)
|
||||
| (equipFlags.HasFlag(EquipFlag.LFinger) ? 0x08 : 0));
|
||||
save.Customize.Write((nint)data + 4);
|
||||
save.Customize.Write(data + 4);
|
||||
((CharacterWeapon*)(data + 30))[0] = save.Item(EquipSlot.MainHand).Weapon(save.Stain(EquipSlot.MainHand));
|
||||
((CharacterWeapon*)(data + 30))[1] = save.Item(EquipSlot.OffHand).Weapon(save.Stain(EquipSlot.OffHand));
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
|
|
|
|||
|
|
@ -2,10 +2,8 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using Glamourer.Utility;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
|
@ -165,7 +163,7 @@ public class DesignConverter(ItemManager _items, DesignManager _designs, Customi
|
|||
yield return (slot, item, armor.Stain);
|
||||
}
|
||||
|
||||
var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant, FullEquipType.Unknown);
|
||||
var mh = _items.Identify(EquipSlot.MainHand, mainhand.Skeleton, mainhand.Weapon, mainhand.Variant);
|
||||
if (!mh.Valid)
|
||||
{
|
||||
Glamourer.Log.Warning($"Appearance data {mainhand} for mainhand weapon invalid, item could not be identified.");
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
|
@ -12,30 +10,30 @@ namespace Glamourer.Designs;
|
|||
|
||||
public unsafe struct DesignData
|
||||
{
|
||||
private string _nameHead = string.Empty;
|
||||
private string _nameBody = string.Empty;
|
||||
private string _nameHands = string.Empty;
|
||||
private string _nameLegs = string.Empty;
|
||||
private string _nameFeet = string.Empty;
|
||||
private string _nameEars = string.Empty;
|
||||
private string _nameNeck = string.Empty;
|
||||
private string _nameWrists = string.Empty;
|
||||
private string _nameRFinger = string.Empty;
|
||||
private string _nameLFinger = string.Empty;
|
||||
private string _nameMainhand = string.Empty;
|
||||
private string _nameOffhand = string.Empty;
|
||||
private fixed uint _itemIds[12];
|
||||
private fixed ushort _iconIds[12];
|
||||
private fixed byte _equipmentBytes[48];
|
||||
public Customize Customize = Customize.Default;
|
||||
public uint ModelId;
|
||||
public CrestFlag CrestVisibility;
|
||||
private SecondaryId _secondaryMainhand;
|
||||
private SecondaryId _secondaryOffhand;
|
||||
private FullEquipType _typeMainhand;
|
||||
private FullEquipType _typeOffhand;
|
||||
private byte _states;
|
||||
public bool IsHuman = true;
|
||||
private string _nameHead = string.Empty;
|
||||
private string _nameBody = string.Empty;
|
||||
private string _nameHands = string.Empty;
|
||||
private string _nameLegs = string.Empty;
|
||||
private string _nameFeet = string.Empty;
|
||||
private string _nameEars = string.Empty;
|
||||
private string _nameNeck = string.Empty;
|
||||
private string _nameWrists = string.Empty;
|
||||
private string _nameRFinger = string.Empty;
|
||||
private string _nameLFinger = string.Empty;
|
||||
private string _nameMainhand = string.Empty;
|
||||
private string _nameOffhand = string.Empty;
|
||||
private fixed uint _itemIds[12];
|
||||
private fixed ushort _iconIds[12];
|
||||
private fixed byte _equipmentBytes[48];
|
||||
public CustomizeArray Customize = CustomizeArray.Default;
|
||||
public uint ModelId;
|
||||
public CrestFlag CrestVisibility;
|
||||
private SecondaryId _secondaryMainhand;
|
||||
private SecondaryId _secondaryOffhand;
|
||||
private FullEquipType _typeMainhand;
|
||||
private FullEquipType _typeOffhand;
|
||||
private byte _states;
|
||||
public bool IsHuman = true;
|
||||
|
||||
public DesignData()
|
||||
{ }
|
||||
|
|
@ -255,11 +253,11 @@ public unsafe struct DesignData
|
|||
}
|
||||
|
||||
|
||||
public bool LoadNonHuman(uint modelId, Customize customize, nint equipData)
|
||||
public bool LoadNonHuman(uint modelId, CustomizeArray customize, nint equipData)
|
||||
{
|
||||
ModelId = modelId;
|
||||
IsHuman = false;
|
||||
Customize.Load(customize);
|
||||
Customize.Read(customize.Data);
|
||||
fixed (byte* ptr = _equipmentBytes)
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked(ptr, (byte*)equipData, 40);
|
||||
|
|
@ -294,7 +292,7 @@ public unsafe struct DesignData
|
|||
public readonly byte[] GetCustomizeBytes()
|
||||
{
|
||||
var ret = new byte[CustomizeArray.Size];
|
||||
fixed (byte* retPtr = ret, inPtr = Customize.Data.Data)
|
||||
fixed (byte* retPtr = ret, inPtr = Customize.Data)
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked(retPtr, inPtr, ret.Length);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,12 +3,10 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
using Dalamud.Utility;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using OtterGui;
|
||||
|
|
@ -25,8 +23,8 @@ public class DesignManager
|
|||
private readonly HumanModelList _humans;
|
||||
private readonly SaveService _saveService;
|
||||
private readonly DesignChanged _event;
|
||||
private readonly List<Design> _designs = new();
|
||||
private readonly Dictionary<Guid, DesignData> _undoStore = new();
|
||||
private readonly List<Design> _designs = [];
|
||||
private readonly Dictionary<Guid, DesignData> _undoStore = [];
|
||||
|
||||
public IReadOnlyList<Design> Designs
|
||||
=> _designs;
|
||||
|
|
@ -298,7 +296,7 @@ public class DesignManager
|
|||
return;
|
||||
case CustomizeIndex.Clan:
|
||||
{
|
||||
var customize = new Customize(design.DesignData.Customize.Data.Clone());
|
||||
var customize = design.DesignData.Customize;
|
||||
if (_customizations.ChangeClan(ref customize, (SubRace)value.Value) == 0)
|
||||
return;
|
||||
if (!design.SetCustomize(_customizations, customize))
|
||||
|
|
@ -308,7 +306,7 @@ public class DesignManager
|
|||
}
|
||||
case CustomizeIndex.Gender:
|
||||
{
|
||||
var customize = new Customize(design.DesignData.Customize.Data.Clone());
|
||||
var customize = design.DesignData.Customize;
|
||||
if (_customizations.ChangeGender(ref customize, (Gender)(value.Value + 1)) == 0)
|
||||
return;
|
||||
if (!design.SetCustomize(_customizations, customize))
|
||||
|
|
|
|||
128
Glamourer/GameData/CharaMakeParams.cs
Normal file
128
Glamourer/GameData/CharaMakeParams.cs
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
using Lumina.Data;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
|
||||
namespace Glamourer.GameData;
|
||||
|
||||
/// <summary>
|
||||
/// A custom version of CharaMakeParams that is easier to parse.
|
||||
/// </summary>
|
||||
[Sheet("CharaMakeParams")]
|
||||
public class CharaMakeParams : ExcelRow
|
||||
{
|
||||
public const int NumMenus = 28;
|
||||
public const int NumVoices = 12;
|
||||
public const int NumGraphics = 10;
|
||||
public const int MaxNumValues = 100;
|
||||
public const int NumFaces = 8;
|
||||
public const int NumFeatures = 7;
|
||||
public const int NumEquip = 3;
|
||||
|
||||
public enum MenuType
|
||||
{
|
||||
ListSelector = 0,
|
||||
IconSelector = 1,
|
||||
ColorPicker = 2,
|
||||
DoubleColorPicker = 3,
|
||||
IconCheckmark = 4,
|
||||
Percentage = 5,
|
||||
Checkmark = 6, // custom
|
||||
Nothing = 7, // custom
|
||||
List1Selector = 8, // custom, 1-indexed lists
|
||||
}
|
||||
|
||||
public struct Menu
|
||||
{
|
||||
public uint Id;
|
||||
public byte InitVal;
|
||||
public MenuType Type;
|
||||
public byte Size;
|
||||
public byte LookAt;
|
||||
public uint Mask;
|
||||
public uint Customize;
|
||||
public uint[] Values;
|
||||
public byte[] Graphic;
|
||||
}
|
||||
|
||||
public struct FacialFeatures
|
||||
{
|
||||
public uint[] Icons;
|
||||
}
|
||||
|
||||
public LazyRow<Race> Race { get; set; } = null!;
|
||||
public LazyRow<Tribe> Tribe { get; set; } = null!;
|
||||
public sbyte Gender { get; set; }
|
||||
|
||||
public Menu[] Menus { get; set; } = new Menu[NumMenus];
|
||||
public byte[] Voices { get; set; } = new byte[NumVoices];
|
||||
public FacialFeatures[] FacialFeatureByFace { get; set; } = new FacialFeatures[NumFaces];
|
||||
|
||||
public CharaMakeType.CharaMakeTypeUnkData3347Obj[] Equip { get; set; } = new CharaMakeType.CharaMakeTypeUnkData3347Obj[NumEquip];
|
||||
|
||||
public override void PopulateData(RowParser parser, Lumina.GameData gameData, Language language)
|
||||
{
|
||||
RowId = parser.RowId;
|
||||
SubRowId = parser.SubRowId;
|
||||
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);
|
||||
int currentOffset;
|
||||
for (var i = 0; i < NumMenus; ++i)
|
||||
{
|
||||
currentOffset = 3 + i;
|
||||
Menus[i].Id = parser.ReadColumn<uint>(0 * NumMenus + currentOffset);
|
||||
Menus[i].InitVal = parser.ReadColumn<byte>(1 * NumMenus + currentOffset);
|
||||
Menus[i].Type = (MenuType)parser.ReadColumn<byte>(2 * NumMenus + currentOffset);
|
||||
Menus[i].Size = parser.ReadColumn<byte>(3 * NumMenus + currentOffset);
|
||||
Menus[i].LookAt = parser.ReadColumn<byte>(4 * NumMenus + currentOffset);
|
||||
Menus[i].Mask = parser.ReadColumn<uint>(5 * NumMenus + currentOffset);
|
||||
Menus[i].Customize = parser.ReadColumn<uint>(6 * NumMenus + currentOffset);
|
||||
Menus[i].Values = new uint[Menus[i].Size];
|
||||
|
||||
switch (Menus[i].Type)
|
||||
{
|
||||
case MenuType.ColorPicker:
|
||||
case MenuType.DoubleColorPicker:
|
||||
case MenuType.Percentage:
|
||||
break;
|
||||
default:
|
||||
currentOffset += 7 * NumMenus;
|
||||
for (var j = 0; j < Menus[i].Size; ++j)
|
||||
Menus[i].Values[j] = parser.ReadColumn<uint>(j * NumMenus + currentOffset);
|
||||
break;
|
||||
}
|
||||
|
||||
Menus[i].Graphic = new byte[NumGraphics];
|
||||
currentOffset = 3 + (MaxNumValues + 7) * NumMenus + i;
|
||||
for (var j = 0; j < NumGraphics; ++j)
|
||||
Menus[i].Graphic[j] = parser.ReadColumn<byte>(j * NumMenus + currentOffset);
|
||||
}
|
||||
|
||||
currentOffset = 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus;
|
||||
for (var i = 0; i < NumVoices; ++i)
|
||||
Voices[i] = parser.ReadColumn<byte>(currentOffset++);
|
||||
|
||||
for (var i = 0; i < NumFaces; ++i)
|
||||
{
|
||||
currentOffset = 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + i;
|
||||
FacialFeatureByFace[i].Icons = new uint[NumFeatures];
|
||||
for (var j = 0; j < NumFeatures; ++j)
|
||||
FacialFeatureByFace[i].Icons[j] = (uint)parser.ReadColumn<int>(j * NumFaces + currentOffset);
|
||||
}
|
||||
|
||||
for (var i = 0; i < NumEquip; ++i)
|
||||
{
|
||||
currentOffset = 3 + (MaxNumValues + 7 + NumGraphics) * NumMenus + NumVoices + NumFaces * NumFeatures + i * 7;
|
||||
Equip[i] = new CharaMakeType.CharaMakeTypeUnkData3347Obj()
|
||||
{
|
||||
Helmet = parser.ReadColumn<ulong>(currentOffset + 0),
|
||||
Top = parser.ReadColumn<ulong>(currentOffset + 1),
|
||||
Gloves = parser.ReadColumn<ulong>(currentOffset + 2),
|
||||
Legs = parser.ReadColumn<ulong>(currentOffset + 3),
|
||||
Shoes = parser.ReadColumn<ulong>(currentOffset + 4),
|
||||
Weapon = parser.ReadColumn<ulong>(currentOffset + 5),
|
||||
SubWeapon = parser.ReadColumn<ulong>(currentOffset + 6),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
50
Glamourer/GameData/ColorParameters.cs
Normal file
50
Glamourer/GameData/ColorParameters.cs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Penumbra.String.Functions;
|
||||
|
||||
namespace Glamourer.GameData;
|
||||
|
||||
public class ColorParameters : IReadOnlyList<uint>
|
||||
{
|
||||
private readonly uint[] _rgbaColors;
|
||||
|
||||
public ReadOnlySpan<uint> GetSlice(int offset, int count)
|
||||
=> _rgbaColors.AsSpan(offset, count);
|
||||
|
||||
public unsafe ColorParameters(IDataManager gameData, IPluginLog log)
|
||||
{
|
||||
try
|
||||
{
|
||||
var file = gameData.GetFile("chara/xls/charamake/human.cmp")!;
|
||||
_rgbaColors = new uint[file.Data.Length >> 2];
|
||||
fixed (byte* ptr1 = file.Data)
|
||||
{
|
||||
fixed (uint* ptr2 = _rgbaColors)
|
||||
{
|
||||
MemoryUtility.MemCpyUnchecked(ptr2, ptr1, file.Data.Length);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
log.Error("READ THIS\n======== Could not obtain the human.cmp file which is necessary for color sets.\n"
|
||||
+ "======== This usually indicates an error with your index files caused by TexTools modifications.\n"
|
||||
+ "======== If you have used TexTools before, you will probably need to start over in it to use Glamourer.", e);
|
||||
_rgbaColors = Array.Empty<uint>();
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerator<uint> GetEnumerator()
|
||||
=> (IEnumerator<uint>)_rgbaColors.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
public int Count
|
||||
=> _rgbaColors.Length;
|
||||
|
||||
public uint this[int index]
|
||||
=> _rgbaColors[index];
|
||||
}
|
||||
38
Glamourer/GameData/CustomName.cs
Normal file
38
Glamourer/GameData/CustomName.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
namespace Glamourer.GameData;
|
||||
|
||||
/// <summary> For localization from the game files directly. </summary>
|
||||
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,
|
||||
}
|
||||
38
Glamourer/GameData/CustomizationManager.cs
Normal file
38
Glamourer/GameData/CustomizationManager.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
using System.Collections.Generic;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.GameData;
|
||||
|
||||
public class CustomizationManager : ICustomizationManager
|
||||
{
|
||||
private static CustomizationOptions? _options;
|
||||
|
||||
private CustomizationManager()
|
||||
{ }
|
||||
|
||||
public static ICustomizationManager Create(ITextureProvider textures, IDataManager gameData, IPluginLog log, NpcCustomizeSet npcCustomizeSet)
|
||||
{
|
||||
_options ??= new CustomizationOptions(textures, gameData, log, npcCustomizeSet);
|
||||
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);
|
||||
}
|
||||
56
Glamourer/GameData/CustomizationNpcOptions.cs
Normal file
56
Glamourer/GameData/CustomizationNpcOptions.cs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
using Penumbra.GameData.Enums;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.GameData;
|
||||
|
||||
public static class CustomizationNpcOptions
|
||||
{
|
||||
public static Dictionary<(SubRace, Gender), IReadOnlyList<(CustomizeIndex, CustomizeValue)>> CreateNpcData(CustomizationSet[] sets,
|
||||
NpcCustomizeSet npcCustomizeSet)
|
||||
{
|
||||
var dict = new Dictionary<(SubRace, Gender), HashSet<(CustomizeIndex, CustomizeValue)>>();
|
||||
var customizeIndices = new[]
|
||||
{
|
||||
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 npcCustomizeSet.Select(s => s.Customize))
|
||||
{
|
||||
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 = [(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());
|
||||
}
|
||||
}
|
||||
530
Glamourer/GameData/CustomizationOptions.cs
Normal file
530
Glamourer/GameData/CustomizationOptions.cs
Normal file
|
|
@ -0,0 +1,530 @@
|
|||
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 Penumbra.GameData.Structs;
|
||||
using Race = Penumbra.GameData.Enums.Race;
|
||||
|
||||
namespace Glamourer.GameData;
|
||||
|
||||
// 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, NpcCustomizeSet npcCustomizeSet)
|
||||
{
|
||||
var tmp = new TemporaryData(gameData, this, log);
|
||||
_icons = new IconStorage(textures, gameData);
|
||||
SetNames(gameData);
|
||||
foreach (var race in Clans)
|
||||
{
|
||||
foreach (var gender in Genders)
|
||||
_customizationSets[ToIndex(race, gender)] = tmp.GetSet(race, gender);
|
||||
}
|
||||
|
||||
tmp.SetNpcData(_customizationSets, npcCustomizeSet);
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
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 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, NpcCustomizeSet npcCustomizeSet)
|
||||
{
|
||||
var data = CustomizationNpcOptions.CreateNpcData(sets, npcCustomizeSet);
|
||||
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 ColorParameters(gameData, log);
|
||||
_customizeSheet = gameData.GetExcelSheet<CharaMakeCustomize>(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;
|
||||
public readonly ExcelSheet<Lobby> Lobby;
|
||||
private readonly ColorParameters _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)
|
||||
{
|
||||
var ret = new CustomizeData[num];
|
||||
var idx = 0;
|
||||
foreach (var value in _cmpFile.GetSlice(offset, num))
|
||||
{
|
||||
ret[idx] = new CustomizeData(index, (CustomizeValue)(light ? 128 + idx : idx), value, (ushort)(offset + idx));
|
||||
++idx;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
private void SetHairByFace(CustomizationSet set)
|
||||
{
|
||||
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 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);
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
// 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>();
|
||||
}
|
||||
}
|
||||
310
Glamourer/GameData/CustomizationSet.cs
Normal file
310
Glamourer/GameData/CustomizationSet.cs
Normal file
|
|
@ -0,0 +1,310 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using OtterGui;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
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
|
||||
{
|
||||
internal CustomizationSet(SubRace clan, Gender gender)
|
||||
{
|
||||
Gender = gender;
|
||||
Clan = clan;
|
||||
Race = clan.ToRace();
|
||||
SettingAvailable = 0;
|
||||
}
|
||||
|
||||
public Gender Gender { get; }
|
||||
public SubRace Clan { get; }
|
||||
public Race Race { get; }
|
||||
|
||||
public CustomizeFlag SettingAvailable { get; internal set; }
|
||||
|
||||
internal void SetAvailable(CustomizeIndex index)
|
||||
=> SettingAvailable |= index.ToFlag();
|
||||
|
||||
public bool IsAvailable(CustomizeIndex index)
|
||||
=> SettingAvailable.HasFlag(index.ToFlag());
|
||||
|
||||
// Meta
|
||||
public IReadOnlyList<string> OptionName { get; internal set; } = null!;
|
||||
|
||||
public string Option(CustomizeIndex index)
|
||||
=> OptionName[(int)index];
|
||||
|
||||
public IReadOnlyList<byte> Voices { get; internal init; } = null!;
|
||||
public IReadOnlyList<CharaMakeParams.MenuType> Types { get; internal set; } = null!;
|
||||
public IReadOnlyDictionary<CharaMakeParams.MenuType, CustomizeIndex[]> Order { get; internal set; } = null!;
|
||||
|
||||
|
||||
// Always list selector.
|
||||
public int NumEyebrows { get; internal init; }
|
||||
public int NumEyeShapes { get; internal init; }
|
||||
public int NumNoseShapes { get; internal init; }
|
||||
public int NumJawShapes { get; internal init; }
|
||||
public int NumMouthShapes { get; internal init; }
|
||||
|
||||
|
||||
// Always Icon Selector
|
||||
public IReadOnlyList<CustomizeData> Faces { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizeData> HairStyles { get; internal init; } = null!;
|
||||
public IReadOnlyList<IReadOnlyList<CustomizeData>> HairByFace { get; internal set; } = null!;
|
||||
public IReadOnlyList<CustomizeData> TailEarShapes { get; internal init; } = null!;
|
||||
public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature1 { get; internal set; } = null!;
|
||||
public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature2 { get; internal set; } = null!;
|
||||
public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature3 { get; internal set; } = null!;
|
||||
public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature4 { get; internal set; } = null!;
|
||||
public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature5 { get; internal set; } = null!;
|
||||
public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature6 { get; internal set; } = null!;
|
||||
public IReadOnlyList<(CustomizeData, CustomizeData)> FacialFeature7 { get; internal set; } = null!;
|
||||
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!;
|
||||
public IReadOnlyList<CustomizeData> HairColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizeData> HighlightColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizeData> EyeColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizeData> TattooColors { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizeData> FacePaintColorsLight { get; internal init; } = null!;
|
||||
public IReadOnlyList<CustomizeData> FacePaintColorsDark { get; internal init; } = null!;
|
||||
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)
|
||||
{
|
||||
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),
|
||||
CharaMakeParams.MenuType.List1Selector => GetInteger1(out custom),
|
||||
CharaMakeParams.MenuType.IconSelector => index switch
|
||||
{
|
||||
CustomizeIndex.Face => Get(Faces, HrothgarFaceHack(value), out custom),
|
||||
CustomizeIndex.Hairstyle => Get((face = HrothgarFaceHack(face)).Value < HairByFace.Count ? HairByFace[face.Value] : HairStyles,
|
||||
value, out custom),
|
||||
CustomizeIndex.TailShape => Get(TailEarShapes, value, out custom),
|
||||
CustomizeIndex.FacePaint => Get(FacePaints, value, out custom),
|
||||
CustomizeIndex.LipColor => Get(LipColorsDark, value, out custom),
|
||||
_ => Invalid(out custom),
|
||||
},
|
||||
CharaMakeParams.MenuType.ColorPicker => index switch
|
||||
{
|
||||
CustomizeIndex.SkinColor => Get(SkinColors, value, out custom),
|
||||
CustomizeIndex.EyeColorLeft => Get(EyeColors, value, out custom),
|
||||
CustomizeIndex.EyeColorRight => Get(EyeColors, value, out custom),
|
||||
CustomizeIndex.HairColor => Get(HairColors, value, out custom),
|
||||
CustomizeIndex.HighlightsColor => Get(HighlightColors, value, out custom),
|
||||
CustomizeIndex.TattooColor => Get(TattooColors, value, out custom),
|
||||
CustomizeIndex.LipColor => Get(LipColorsDark.Concat(LipColorsLight), value, out custom),
|
||||
CustomizeIndex.FacePaintColor => Get(FacePaintColorsDark.Concat(FacePaintColorsLight), value, out custom),
|
||||
_ => Invalid(out custom),
|
||||
},
|
||||
CharaMakeParams.MenuType.DoubleColorPicker => index switch
|
||||
{
|
||||
CustomizeIndex.LipColor => Get(LipColorsDark.Concat(LipColorsLight), value, out custom),
|
||||
CustomizeIndex.FacePaintColor => Get(FacePaintColorsDark.Concat(FacePaintColorsLight), value, out custom),
|
||||
_ => Invalid(out custom),
|
||||
},
|
||||
CharaMakeParams.MenuType.IconCheckmark => GetBool(index, value, out custom),
|
||||
CharaMakeParams.MenuType.Percentage => GetInteger0(out custom),
|
||||
CharaMakeParams.MenuType.Checkmark => GetBool(index, value, out custom),
|
||||
_ => Invalid(out custom),
|
||||
};
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public CustomizeData Data(CustomizeIndex index, int idx)
|
||||
=> Data(index, idx, CustomizeValue.Zero);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public CustomizeData Data(CustomizeIndex index, int idx, CustomizeValue face)
|
||||
{
|
||||
if (idx >= Count(index, face = HrothgarFaceHack(face)))
|
||||
throw new IndexOutOfRangeException();
|
||||
|
||||
switch (Types[(int)index])
|
||||
{
|
||||
case CharaMakeParams.MenuType.Percentage: return new CustomizeData(index, (CustomizeValue)idx, 0, (ushort)idx);
|
||||
case CharaMakeParams.MenuType.ListSelector: return new CustomizeData(index, (CustomizeValue)idx, 0, (ushort)idx);
|
||||
case CharaMakeParams.MenuType.List1Selector: return new CustomizeData(index, (CustomizeValue)(idx + 1), 0, (ushort)idx);
|
||||
case CharaMakeParams.MenuType.Checkmark: return new CustomizeData(index, CustomizeValue.Bool(idx != 0), 0, (ushort)idx);
|
||||
}
|
||||
|
||||
return index switch
|
||||
{
|
||||
CustomizeIndex.Face => Faces[idx],
|
||||
CustomizeIndex.Hairstyle => face < HairByFace.Count ? HairByFace[face.Value][idx] : HairStyles[idx],
|
||||
CustomizeIndex.TailShape => TailEarShapes[idx],
|
||||
CustomizeIndex.FacePaint => FacePaints[idx],
|
||||
CustomizeIndex.FacialFeature1 => idx == 0 ? FacialFeature1[face.Value].Item1 : FacialFeature1[face.Value].Item2,
|
||||
CustomizeIndex.FacialFeature2 => idx == 0 ? FacialFeature2[face.Value].Item1 : FacialFeature2[face.Value].Item2,
|
||||
CustomizeIndex.FacialFeature3 => idx == 0 ? FacialFeature3[face.Value].Item1 : FacialFeature3[face.Value].Item2,
|
||||
CustomizeIndex.FacialFeature4 => idx == 0 ? FacialFeature4[face.Value].Item1 : FacialFeature4[face.Value].Item2,
|
||||
CustomizeIndex.FacialFeature5 => idx == 0 ? FacialFeature5[face.Value].Item1 : FacialFeature5[face.Value].Item2,
|
||||
CustomizeIndex.FacialFeature6 => idx == 0 ? FacialFeature6[face.Value].Item1 : FacialFeature6[face.Value].Item2,
|
||||
CustomizeIndex.FacialFeature7 => idx == 0 ? FacialFeature7[face.Value].Item1 : FacialFeature7[face.Value].Item2,
|
||||
CustomizeIndex.LegacyTattoo => idx == 0 ? LegacyTattoo.Item1 : LegacyTattoo.Item2,
|
||||
CustomizeIndex.SkinColor => SkinColors[idx],
|
||||
CustomizeIndex.EyeColorLeft => EyeColors[idx],
|
||||
CustomizeIndex.EyeColorRight => EyeColors[idx],
|
||||
CustomizeIndex.HairColor => HairColors[idx],
|
||||
CustomizeIndex.HighlightsColor => HighlightColors[idx],
|
||||
CustomizeIndex.TattooColor => TattooColors[idx],
|
||||
CustomizeIndex.LipColor => idx < 96 ? LipColorsDark[idx] : LipColorsLight[idx - 96],
|
||||
CustomizeIndex.FacePaintColor => idx < 96 ? FacePaintColorsDark[idx] : FacePaintColorsLight[idx - 96],
|
||||
_ => new CustomizeData(0, CustomizeValue.Zero),
|
||||
};
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
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);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
public int Count(CustomizeIndex index, CustomizeValue face)
|
||||
{
|
||||
if (!IsAvailable(index))
|
||||
return 0;
|
||||
|
||||
return Type(index) switch
|
||||
{
|
||||
CharaMakeParams.MenuType.Percentage => 101,
|
||||
CharaMakeParams.MenuType.IconCheckmark => 2,
|
||||
CharaMakeParams.MenuType.Checkmark => 2,
|
||||
_ => index switch
|
||||
{
|
||||
CustomizeIndex.Face => Faces.Count,
|
||||
CustomizeIndex.Hairstyle => (face = HrothgarFaceHack(face)) < HairByFace.Count
|
||||
? HairByFace[face.Value].Count
|
||||
: HairStyles.Count,
|
||||
CustomizeIndex.SkinColor => SkinColors.Count,
|
||||
CustomizeIndex.EyeColorRight => EyeColors.Count,
|
||||
CustomizeIndex.HairColor => HairColors.Count,
|
||||
CustomizeIndex.HighlightsColor => HighlightColors.Count,
|
||||
CustomizeIndex.TattooColor => TattooColors.Count,
|
||||
CustomizeIndex.Eyebrows => NumEyebrows,
|
||||
CustomizeIndex.EyeColorLeft => EyeColors.Count,
|
||||
CustomizeIndex.EyeShape => NumEyeShapes,
|
||||
CustomizeIndex.Nose => NumNoseShapes,
|
||||
CustomizeIndex.Jaw => NumJawShapes,
|
||||
CustomizeIndex.Mouth => NumMouthShapes,
|
||||
CustomizeIndex.LipColor => LipColorsLight.Count + LipColorsDark.Count,
|
||||
CustomizeIndex.TailShape => TailEarShapes.Count,
|
||||
CustomizeIndex.FacePaint => FacePaints.Count,
|
||||
CustomizeIndex.FacePaintColor => FacePaintColorsLight.Count + FacePaintColorsDark.Count,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(index), index, null),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private CustomizeValue HrothgarFaceHack(CustomizeValue value)
|
||||
=> Race == Race.Hrothgar && value.Value is > 4 and < 9 ? value - 4 : value;
|
||||
}
|
||||
|
||||
public static class CustomizationSetExtensions
|
||||
{
|
||||
/// <summary> Return only the available customizations in this set and Clan or Gender. </summary>
|
||||
public static CustomizeFlag FixApplication(this CustomizeFlag flag, CustomizationSet set)
|
||||
=> flag & (set.SettingAvailable | CustomizeFlag.Clan | CustomizeFlag.Gender);
|
||||
}
|
||||
47
Glamourer/GameData/CustomizeData.cs
Normal file
47
Glamourer/GameData/CustomizeData.cs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
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.
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public readonly struct CustomizeData : IEquatable<CustomizeData>
|
||||
{
|
||||
[FieldOffset(0)]
|
||||
public readonly CustomizeIndex Index;
|
||||
|
||||
[FieldOffset(1)]
|
||||
public readonly CustomizeValue Value;
|
||||
|
||||
[FieldOffset(2)]
|
||||
public readonly ushort CustomizeId;
|
||||
|
||||
[FieldOffset(4)]
|
||||
public readonly uint IconId;
|
||||
|
||||
[FieldOffset(4)]
|
||||
public readonly uint Color;
|
||||
|
||||
public CustomizeData(CustomizeIndex index, CustomizeValue value, uint data = 0, ushort customizeId = 0)
|
||||
{
|
||||
Index = index;
|
||||
Value = value;
|
||||
IconId = data;
|
||||
Color = data;
|
||||
CustomizeId = customizeId;
|
||||
}
|
||||
|
||||
public bool Equals(CustomizeData other)
|
||||
=> Index == other.Index
|
||||
&& Value.Value == other.Value.Value
|
||||
&& CustomizeId == other.CustomizeId;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is CustomizeData other && Equals(other);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> HashCode.Combine((int)Index, Value.Value, CustomizeId);
|
||||
}
|
||||
17
Glamourer/GameData/ICustomizationManager.cs
Normal file
17
Glamourer/GameData/ICustomizationManager.cs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
using System.Collections.Generic;
|
||||
using Dalamud.Interface.Internal;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.GameData;
|
||||
|
||||
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);
|
||||
}
|
||||
282
Glamourer/GameData/NpcCustomizeSet.cs
Normal file
282
Glamourer/GameData/NpcCustomizeSet.cs
Normal file
|
|
@ -0,0 +1,282 @@
|
|||
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;
|
||||
|
||||
public class NpcCustomizeSet : IAsyncDataContainer, IReadOnlyList<NpcData>
|
||||
{
|
||||
public string Name
|
||||
=> nameof(NpcCustomizeSet);
|
||||
|
||||
private readonly List<NpcData> _data = [];
|
||||
|
||||
public long Time { get; private set; }
|
||||
public long Memory { get; private set; }
|
||||
public int TotalCount
|
||||
=> _data.Count;
|
||||
|
||||
public Task Awaiter { get; }
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
private static List<NpcData> CreateEnpcData(IDataManager data, DictENpc eNpcs)
|
||||
{
|
||||
var enpcSheet = data.GetExcelSheet<ENpcBase>()!;
|
||||
var list = new List<NpcData>(eNpcs.Count);
|
||||
|
||||
foreach (var (id, name) in eNpcs)
|
||||
{
|
||||
var row = enpcSheet.GetRow(id.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);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private static List<NpcData> CreateBnpcData(IDataManager data, DictBNpc bNpcs, DictBNpcNames bNpcNames)
|
||||
{
|
||||
var bnpcSheet = data.GetExcelSheet<BNpcBase>()!;
|
||||
var list = new List<NpcData>((int)bnpcSheet.RowCount);
|
||||
foreach (var baseRow in bnpcSheet)
|
||||
{
|
||||
if (baseRow.ModelChara.Value!.Type != 1)
|
||||
continue;
|
||||
|
||||
var bnpcNameIds = bNpcNames[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 (bNpcs.TryGetValue(bnpcNameId.Id, out var name) && !name.IsNullOrWhitespace())
|
||||
list.Add(ret with { Name = name });
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
private void FilterAndOrderNpcData(List<NpcData> eNpcEquip, List<NpcData> bNpcEquip)
|
||||
{
|
||||
_data.Clear();
|
||||
_data.EnsureCapacity(eNpcEquip.Count + bNpcEquip.Count);
|
||||
var groups = eNpcEquip.Concat(bNpcEquip).GroupBy(d => d.Name).ToDictionary(g => g.Key, g => g.ToList());
|
||||
foreach (var (name, duplicates) in groups.OrderBy(kvp => kvp.Key))
|
||||
{
|
||||
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 (duplicates.Count == 1)
|
||||
{
|
||||
_data.Add(duplicates[0]);
|
||||
Memory += 96;
|
||||
}
|
||||
else
|
||||
{
|
||||
_data.AddRange(duplicates
|
||||
.Select(duplicate => duplicate with
|
||||
{
|
||||
Name = $"{name} ({(duplicate.Kind is ObjectKind.BattleNpc ? 'B' : 'E')}{duplicate.Id})"
|
||||
}));
|
||||
Memory += 96 * duplicates.Count + duplicates.Sum(d => d.Name.Length * 2);
|
||||
}
|
||||
}
|
||||
|
||||
var lastWeird = _data.FindIndex(d => char.IsAsciiLetterOrDigit(d.Name[0]));
|
||||
if (lastWeird != -1)
|
||||
{
|
||||
_data.AddRange(_data.Take(lastWeird));
|
||||
_data.RemoveRange(0, lastWeird);
|
||||
}
|
||||
_data.TrimExcess();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 (customize.BodyType.Value != 1
|
||||
|| !CustomizationOptions.Races.Contains(customize.Race)
|
||||
|| !CustomizationOptions.Clans.Contains(customize.Clan)
|
||||
|| !CustomizationOptions.Genders.Contains(customize.Gender))
|
||||
return (false, CustomizeArray.Default);
|
||||
|
||||
return (true, customize);
|
||||
}
|
||||
|
||||
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 (customize.BodyType.Value != 1
|
||||
|| !CustomizationOptions.Races.Contains(customize.Race)
|
||||
|| !CustomizationOptions.Clans.Contains(customize.Clan)
|
||||
|| !CustomizationOptions.Genders.Contains(customize.Gender))
|
||||
return (false, CustomizeArray.Default);
|
||||
|
||||
return (true, customize);
|
||||
}
|
||||
|
||||
public IEnumerator<NpcData> GetEnumerator()
|
||||
=> _data.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
public int Count
|
||||
=> _data.Count;
|
||||
|
||||
public NpcData this[int index]
|
||||
=> _data[index];
|
||||
}
|
||||
89
Glamourer/GameData/NpcData.cs
Normal file
89
Glamourer/GameData/NpcData.cs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
using System;
|
||||
using System.Text;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.GameData;
|
||||
|
||||
public unsafe struct NpcData
|
||||
{
|
||||
public string Name;
|
||||
public CustomizeArray Customize;
|
||||
private fixed byte _equip[40];
|
||||
public CharacterWeapon Mainhand;
|
||||
public CharacterWeapon Offhand;
|
||||
public NpcId 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.Skeleton.Id.ToString("D4"));
|
||||
sb.Append('-');
|
||||
sb.Append(Mainhand.Weapon.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.Skeleton.Id.ToString("D4"));
|
||||
sb.Append('-');
|
||||
sb.Append(Offhand.Weapon.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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,7 +1,7 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Dalamud.Interface;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.GameData;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.GameData;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Gui.Customization;
|
||||
|
||||
|
|
@ -13,7 +15,7 @@ public partial class CustomizationDrawer
|
|||
|
||||
private void DrawIconSelector(CustomizeIndex index)
|
||||
{
|
||||
using var _ = SetId(index);
|
||||
using var id = SetId(index);
|
||||
using var bigGroup = ImRaii.Group();
|
||||
var label = _currentOption;
|
||||
|
||||
|
|
@ -28,7 +30,7 @@ public partial class CustomizationDrawer
|
|||
}
|
||||
|
||||
var icon = _service.Service.GetIcon(custom!.Value.IconId);
|
||||
using (var disabled = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw))
|
||||
using (_ = ImRaii.Disabled(_locked || _currentIndex is CustomizeIndex.Face && _lockedRedraw))
|
||||
{
|
||||
if (ImGui.ImageButton(icon.ImGuiHandle, _iconSize))
|
||||
ImGui.OpenPopup(IconSelectorPopup);
|
||||
|
|
@ -37,7 +39,7 @@ public partial class CustomizationDrawer
|
|||
ImGuiUtil.HoverIconTooltip(icon, _iconSize);
|
||||
|
||||
ImGui.SameLine();
|
||||
using (var group = ImRaii.Group())
|
||||
using (_ = ImRaii.Group())
|
||||
{
|
||||
DataInputInt(current, npc);
|
||||
if (_lockedRedraw && ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled))
|
||||
|
|
@ -118,16 +120,16 @@ public partial class CustomizationDrawer
|
|||
ImGui.Dummy(new Vector2(ImGui.GetFrameHeight()));
|
||||
}
|
||||
|
||||
var oldValue = _customize.Data.At(_currentIndex.ToByteAndMask().ByteIdx);
|
||||
var tmp = (int)oldValue;
|
||||
var oldValue = _customize.AtIndex(_currentIndex.ToByteAndMask().ByteIdx);
|
||||
var tmp = (int)oldValue.Value;
|
||||
ImGui.SetNextItemWidth(_inputIntSize);
|
||||
if (ImGui.InputInt("##text", ref tmp, 1, 1))
|
||||
{
|
||||
tmp = Math.Clamp(tmp, 0, byte.MaxValue);
|
||||
if (tmp != oldValue)
|
||||
if (tmp != oldValue.Value)
|
||||
{
|
||||
_customize.Data.Set(_currentIndex.ToByteAndMask().ByteIdx, (byte)tmp);
|
||||
var changes = (byte)tmp ^ oldValue;
|
||||
_customize.SetByIndex(_currentIndex.ToByteAndMask().ByteIdx, (CustomizeValue)tmp);
|
||||
var changes = (byte)tmp ^ oldValue.Value;
|
||||
Changed |= ((changes & 0x01) == 0x01 ? CustomizeFlag.FacialFeature1 : 0)
|
||||
| ((changes & 0x02) == 0x02 ? CustomizeFlag.FacialFeature2 : 0)
|
||||
| ((changes & 0x04) == 0x04 ? CustomizeFlag.FacialFeature3 : 0)
|
||||
|
|
|
|||
|
|
@ -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,7 +4,7 @@ using System.Reflection;
|
|||
using Dalamud.Interface.Internal;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Services;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
|
|
@ -22,13 +22,12 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio
|
|||
|
||||
private Exception? _terminate;
|
||||
|
||||
private Customize _customize = Customize.Default;
|
||||
private CustomizeArray _customize = CustomizeArray.Default;
|
||||
private CustomizationSet _set = null!;
|
||||
|
||||
public Customize Customize
|
||||
public CustomizeArray Customize
|
||||
=> _customize;
|
||||
|
||||
public CustomizeFlag CurrentFlag { get; private set; }
|
||||
public CustomizeFlag Changed { get; private set; }
|
||||
public CustomizeFlag ChangeApply { get; private set; }
|
||||
|
||||
|
|
@ -47,18 +46,16 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio
|
|||
public void Dispose()
|
||||
=> _legacyTattoo?.Dispose();
|
||||
|
||||
public bool Draw(Customize current, bool locked, bool lockedRedraw)
|
||||
public bool Draw(CustomizeArray current, bool locked, bool lockedRedraw)
|
||||
{
|
||||
CurrentFlag = CustomizeFlagExtensions.All;
|
||||
_withApply = false;
|
||||
Init(current, locked, lockedRedraw);
|
||||
|
||||
return DrawInternal();
|
||||
}
|
||||
|
||||
public bool Draw(Customize current, CustomizeFlag apply, bool locked, bool lockedRedraw)
|
||||
public bool Draw(CustomizeArray current, CustomizeFlag apply, bool locked, bool lockedRedraw)
|
||||
{
|
||||
CurrentFlag = CustomizeFlagExtensions.All;
|
||||
ChangeApply = apply;
|
||||
_initialApply = apply;
|
||||
_withApply = !_config.HideApplyCheckmarks;
|
||||
|
|
@ -66,12 +63,12 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio
|
|||
return DrawInternal();
|
||||
}
|
||||
|
||||
private void Init(Customize current, bool locked, bool lockedRedraw)
|
||||
private void Init(CustomizeArray current, bool locked, bool lockedRedraw)
|
||||
{
|
||||
UpdateSizes();
|
||||
_terminate = null;
|
||||
Changed = 0;
|
||||
_customize.Load(current);
|
||||
_terminate = null;
|
||||
Changed = 0;
|
||||
_customize = current;
|
||||
_locked = locked;
|
||||
_lockedRedraw = lockedRedraw;
|
||||
}
|
||||
|
|
@ -156,20 +153,20 @@ public partial class CustomizationDrawer(DalamudPluginInterface pi, Customizatio
|
|||
for (var i = 0; i < CustomizeArray.Size; ++i)
|
||||
{
|
||||
using var id = ImRaii.PushId(i);
|
||||
int value = _customize.Data.Data[i];
|
||||
int value = _customize.Data[i];
|
||||
ImGui.SetNextItemWidth(40 * ImGuiHelpers.GlobalScale);
|
||||
if (ImGui.InputInt(string.Empty, ref value, 0, 0))
|
||||
{
|
||||
var newValue = (byte)Math.Clamp(value, 0, byte.MaxValue);
|
||||
if (newValue != _customize.Data.Data[i])
|
||||
if (newValue != _customize.Data[i])
|
||||
foreach (var flag in Enum.GetValues<CustomizeIndex>())
|
||||
{
|
||||
var (j, mask) = flag.ToByteAndMask();
|
||||
var (j, _) = flag.ToByteAndMask();
|
||||
if (j == i)
|
||||
Changed |= flag.ToFlag();
|
||||
}
|
||||
|
||||
_customize.Data.Data[i] = newValue;
|
||||
_customize.Data[i] = newValue;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ using Dalamud.Interface.Internal.Notifications;
|
|||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game;
|
||||
using Glamourer.Automation;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Gui.Customization;
|
||||
|
|
@ -14,7 +13,6 @@ using Glamourer.Gui.Equipment;
|
|||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
|
|
@ -173,21 +171,21 @@ public class ActorPanel(
|
|||
|
||||
private void DrawEquipmentMetaToggles()
|
||||
{
|
||||
using (var _ = ImRaii.Group())
|
||||
using (_ = ImRaii.Group())
|
||||
{
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.HatState, _stateManager, _state!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Head, _stateManager, _state!));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
using (var _ = ImRaii.Group())
|
||||
using (_ = ImRaii.Group())
|
||||
{
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.VisorState, _stateManager, _state!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.Body, _stateManager, _state!));
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
using (var _ = ImRaii.Group())
|
||||
using (_ = ImRaii.Group())
|
||||
{
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.FromState(ActorState.MetaIndex.WeaponState, _stateManager, _state!));
|
||||
EquipmentDrawer.DrawMetaToggle(ToggleDrawData.CrestFromState(CrestFlag.OffHand, _stateManager, _state!));
|
||||
|
|
@ -199,7 +197,7 @@ public class ActorPanel(
|
|||
var names = _modelChara[_state!.ModelData.ModelId];
|
||||
var turnHuman = ImGui.Button("Turn Human");
|
||||
ImGui.Separator();
|
||||
using (var box = ImRaii.ListBox("##MonsterList",
|
||||
using (_ = ImRaii.ListBox("##MonsterList",
|
||||
new Vector2(ImGui.GetContentRegionAvail().X, 10 * ImGui.GetTextLineHeightWithSpacing())))
|
||||
{
|
||||
if (names.Count == 0)
|
||||
|
|
@ -211,14 +209,14 @@ public class ActorPanel(
|
|||
|
||||
ImGui.Separator();
|
||||
ImGui.TextUnformatted("Customization Data");
|
||||
using (var font = ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
using (_ = ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
{
|
||||
foreach (var b in _state.ModelData.Customize.Data)
|
||||
foreach (var b in _state.ModelData.Customize)
|
||||
{
|
||||
using (var g = ImRaii.Group())
|
||||
using (_ = ImRaii.Group())
|
||||
{
|
||||
ImGui.TextUnformatted($" {b:X2}");
|
||||
ImGui.TextUnformatted($"{b,3}");
|
||||
ImGui.TextUnformatted($" {b.Value:X2}");
|
||||
ImGui.TextUnformatted($"{b.Value,3}");
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
|
@ -232,11 +230,11 @@ public class ActorPanel(
|
|||
|
||||
ImGui.Separator();
|
||||
ImGui.TextUnformatted("Equipment Data");
|
||||
using (var font = ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
using (_ = ImRaii.PushFont(UiBuilder.MonoFont))
|
||||
{
|
||||
foreach (var b in _state.ModelData.GetEquipmentBytes())
|
||||
{
|
||||
using (var g = ImRaii.Group())
|
||||
using (_ = ImRaii.Group())
|
||||
{
|
||||
ImGui.TextUnformatted($" {b:X2}");
|
||||
ImGui.TextUnformatted($"{b,3}");
|
||||
|
|
@ -298,8 +296,8 @@ public class ActorPanel(
|
|||
BorderColor = ColorId.ActorUnavailable.Value(),
|
||||
};
|
||||
|
||||
private string _newName = string.Empty;
|
||||
private DesignBase? _newDesign = null;
|
||||
private string _newName = string.Empty;
|
||||
private DesignBase? _newDesign;
|
||||
|
||||
private void SaveDesignOpen()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,10 +6,8 @@ using System.Text;
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Glamourer.Automation;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using Glamourer.Unlocks;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
|
|
@ -17,44 +15,29 @@ using OtterGui.Log;
|
|||
using OtterGui.Raii;
|
||||
using OtterGui.Widgets;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Action = System.Action;
|
||||
using CustomizeIndex = Glamourer.Customization.CustomizeIndex;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.AutomationTab;
|
||||
|
||||
public class SetPanel
|
||||
public class SetPanel(
|
||||
SetSelector _selector,
|
||||
AutoDesignManager _manager,
|
||||
JobService _jobs,
|
||||
ItemUnlockManager _itemUnlocks,
|
||||
RevertDesignCombo _designCombo,
|
||||
CustomizeUnlockManager _customizeUnlocks,
|
||||
CustomizationService _customizations,
|
||||
IdentifierDrawer _identifierDrawer,
|
||||
Configuration _config)
|
||||
{
|
||||
private readonly AutoDesignManager _manager;
|
||||
private readonly SetSelector _selector;
|
||||
private readonly ItemUnlockManager _itemUnlocks;
|
||||
private readonly CustomizeUnlockManager _customizeUnlocks;
|
||||
private readonly CustomizationService _customizations;
|
||||
|
||||
private readonly Configuration _config;
|
||||
private readonly RevertDesignCombo _designCombo;
|
||||
private readonly JobGroupCombo _jobGroupCombo;
|
||||
private readonly IdentifierDrawer _identifierDrawer;
|
||||
private readonly JobGroupCombo _jobGroupCombo = new(_manager, _jobs, Glamourer.Log);
|
||||
|
||||
private string? _tempName;
|
||||
private int _dragIndex = -1;
|
||||
|
||||
private Action? _endAction;
|
||||
|
||||
public SetPanel(SetSelector selector, AutoDesignManager manager, JobService jobs, ItemUnlockManager itemUnlocks,
|
||||
RevertDesignCombo designCombo,
|
||||
CustomizeUnlockManager customizeUnlocks, CustomizationService customizations, IdentifierDrawer identifierDrawer, Configuration config)
|
||||
{
|
||||
_selector = selector;
|
||||
_manager = manager;
|
||||
_itemUnlocks = itemUnlocks;
|
||||
_customizeUnlocks = customizeUnlocks;
|
||||
_customizations = customizations;
|
||||
_identifierDrawer = identifierDrawer;
|
||||
_config = config;
|
||||
_designCombo = designCombo;
|
||||
_jobGroupCombo = new JobGroupCombo(manager, jobs, Glamourer.Log);
|
||||
}
|
||||
|
||||
private AutoDesignSet Selection
|
||||
=> _selector.Selection!;
|
||||
|
||||
|
|
@ -77,7 +60,7 @@ public class SetPanel
|
|||
|
||||
var spacing = ImGui.GetStyle().ItemInnerSpacing with { Y = ImGui.GetStyle().ItemSpacing.Y };
|
||||
|
||||
using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
|
||||
using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
|
||||
{
|
||||
var enabled = Selection.Enabled;
|
||||
if (ImGui.Checkbox("##Enabled", ref enabled))
|
||||
|
|
@ -87,7 +70,7 @@ public class SetPanel
|
|||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
|
||||
using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
|
||||
{
|
||||
var useGame = _selector.Selection!.BaseState is AutoDesignSet.Base.Game;
|
||||
if (ImGui.Checkbox("##gameState", ref useGame))
|
||||
|
|
@ -98,7 +81,7 @@ public class SetPanel
|
|||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
|
||||
using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, spacing))
|
||||
{
|
||||
var editing = _config.ShowAutomationSetEditing;
|
||||
if (ImGui.Checkbox("##Show Editing", ref editing))
|
||||
|
|
@ -230,7 +213,7 @@ public class SetPanel
|
|||
if (_config.ShowUnlockedItemWarnings)
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
DrawWarnings(design, idx);
|
||||
DrawWarnings(design);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -278,7 +261,7 @@ public class SetPanel
|
|||
}
|
||||
}
|
||||
|
||||
private void DrawWarnings(AutoDesign design, int idx)
|
||||
private void DrawWarnings(AutoDesign design)
|
||||
{
|
||||
if (design.Revert)
|
||||
return;
|
||||
|
|
@ -301,27 +284,6 @@ public class SetPanel
|
|||
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, new Vector2(2 * ImGuiHelpers.GlobalScale, 0));
|
||||
|
||||
|
||||
static void DrawWarning(StringBuilder sb, uint color, Vector2 size, string suffix, string good)
|
||||
{
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
sb.Append(suffix);
|
||||
using (var font = ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, color);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip(sb.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGuiUtil.DrawTextButton(string.Empty, size, 0);
|
||||
ImGuiUtil.HoverTooltip(good);
|
||||
}
|
||||
}
|
||||
|
||||
var tt = _config.UnlockedItemMode
|
||||
? "\nThese items will be skipped when applied automatically.\n\nTo change this, disable the Obtained Item Mode setting."
|
||||
: string.Empty;
|
||||
|
|
@ -355,6 +317,27 @@ public class SetPanel
|
|||
: string.Empty;
|
||||
DrawWarning(sb2, _config.UnlockedItemMode ? 0xA03030F0 : 0x0, size, tt, "All customizations to be applied are unlocked.");
|
||||
ImGui.SameLine();
|
||||
return;
|
||||
|
||||
static void DrawWarning(StringBuilder sb, uint color, Vector2 size, string suffix, string good)
|
||||
{
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
||||
if (sb.Length > 0)
|
||||
{
|
||||
sb.Append(suffix);
|
||||
using (_ = ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
ImGuiUtil.DrawTextButton(FontAwesomeIcon.ExclamationCircle.ToIconString(), size, color);
|
||||
}
|
||||
|
||||
ImGuiUtil.HoverTooltip(sb.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGuiUtil.DrawTextButton(string.Empty, size, 0);
|
||||
ImGuiUtil.HoverTooltip(good);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawDragDrop(AutoDesignSet set, int index)
|
||||
|
|
@ -394,7 +377,7 @@ public class SetPanel
|
|||
var newType = design.ApplicationType;
|
||||
var newTypeInt = (uint)newType;
|
||||
style.Push(ImGuiStyleVar.FrameBorderSize, ImGuiHelpers.GlobalScale);
|
||||
using (var c = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value()))
|
||||
using (_ = ImRaii.PushColor(ImGuiCol.Border, ColorId.FolderLine.Value()))
|
||||
{
|
||||
if (ImGui.CheckboxFlags("##all", ref newTypeInt, (uint)AutoDesign.Type.All))
|
||||
newType = (AutoDesign.Type)newTypeInt;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
using System;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Services;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DebugTab;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Unlocks;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DebugTab;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
using System.IO;
|
||||
using System.Numerics;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Interop;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Files;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DebugTab;
|
||||
|
||||
|
|
@ -35,7 +34,7 @@ public class DatFilePanel(ImportService _importService) : IDebugTabTree
|
|||
ImGui.TextUnformatted(_datFile.Value.Version.ToString());
|
||||
ImGui.TextUnformatted(_datFile.Value.Time.LocalDateTime.ToString("g"));
|
||||
ImGui.TextUnformatted(_datFile.Value.Voice.ToString());
|
||||
ImGui.TextUnformatted(_datFile.Value.Customize.Data.ToString());
|
||||
ImGui.TextUnformatted(_datFile.Value.Customize.ToString());
|
||||
ImGui.TextUnformatted(_datFile.Value.Description);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Dalamud.Interface;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Dalamud.Interface;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
|
|
@ -85,7 +83,7 @@ public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDe
|
|||
DrawDesignData(_parse64);
|
||||
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
ImGui.TextUnformatted(_base64);
|
||||
using (var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing with { X = 0 }))
|
||||
using (_ = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemSpacing with { X = 0 }))
|
||||
{
|
||||
foreach (var (c1, c2) in _restore.Zip(_base64))
|
||||
{
|
||||
|
|
@ -99,7 +97,7 @@ public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDe
|
|||
|
||||
foreach (var ((b1, b2), idx) in _base64Bytes.Zip(_restoreBytes).WithIndex())
|
||||
{
|
||||
using (var group = ImRaii.Group())
|
||||
using (_ = ImRaii.Group())
|
||||
{
|
||||
ImGui.TextUnformatted(idx.ToString("D2"));
|
||||
ImGui.TextUnformatted(b1.ToString("X2"));
|
||||
|
|
@ -121,7 +119,7 @@ public class DesignTesterPanel(ItemManager _items, HumanModelList _humans) : IDe
|
|||
using var font = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
foreach (var (b, idx) in _base64Bytes.WithIndex())
|
||||
{
|
||||
using (var group = ImRaii.Group())
|
||||
using (_ = ImRaii.Group())
|
||||
{
|
||||
ImGui.TextUnformatted(idx.ToString("D2"));
|
||||
ImGui.TextUnformatted(b.ToString("X2"));
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ public class JobPanel(JobService _jobs) : IDebugTabTree
|
|||
|
||||
foreach (var (id, job) in _jobs.Jobs)
|
||||
{
|
||||
ImGuiUtil.DrawTableColumn(id.ToString("D3"));
|
||||
ImGuiUtil.DrawTableColumn(id.Id.ToString("D3"));
|
||||
ImGuiUtil.DrawTableColumn(job.Name);
|
||||
ImGuiUtil.DrawTableColumn(job.Abbreviation);
|
||||
}
|
||||
|
|
@ -68,7 +68,7 @@ public class JobPanel(JobService _jobs) : IDebugTabTree
|
|||
|
||||
foreach (var (id, group) in _jobs.JobGroups)
|
||||
{
|
||||
ImGuiUtil.DrawTableColumn(id.ToString("D3"));
|
||||
ImGuiUtil.DrawTableColumn(id.Id.ToString("D3"));
|
||||
ImGuiUtil.DrawTableColumn(group.Name);
|
||||
ImGuiUtil.DrawTableColumn(group.Count.ToString());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
using System;
|
||||
using System.Numerics;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
|
|
@ -126,15 +124,14 @@ public unsafe class ModelEvaluationPanel(
|
|||
model.AsHuman->Head.Value == 0 ? actor.GetArmor(EquipSlot.Head) : CharacterArmor.Empty);
|
||||
}
|
||||
|
||||
private void DrawWeaponState(Actor actor, Model model)
|
||||
private static void DrawWeaponState(Actor actor, Model model)
|
||||
{
|
||||
using var id = ImRaii.PushId("WeaponState");
|
||||
ImGuiUtil.DrawTableColumn("Weapon State");
|
||||
ImGuiUtil.DrawTableColumn(actor.IsCharacter
|
||||
? actor.AsCharacter->DrawData.IsWeaponHidden ? "Hidden" : "Visible"
|
||||
: "No Character");
|
||||
var text = string.Empty;
|
||||
|
||||
string text;
|
||||
if (!model.IsHuman)
|
||||
{
|
||||
text = "No Model";
|
||||
|
|
@ -146,19 +143,14 @@ public unsafe class ModelEvaluationPanel(
|
|||
else
|
||||
{
|
||||
var weapon = (DrawObject*)model.AsDrawObject->Object.ChildObject;
|
||||
if ((weapon->Flags & 0x09) == 0x09)
|
||||
text = "Visible";
|
||||
else
|
||||
text = "Hidden";
|
||||
text = (weapon->Flags & 0x09) == 0x09 ? "Visible" : "Hidden";
|
||||
}
|
||||
|
||||
ImGuiUtil.DrawTableColumn(text);
|
||||
ImGui.TableNextColumn();
|
||||
if (!model.IsHuman)
|
||||
return;
|
||||
}
|
||||
|
||||
private void DrawWetness(Actor actor, Model model)
|
||||
private static void DrawWetness(Actor actor, Model model)
|
||||
{
|
||||
using var id = ImRaii.PushId("Wetness");
|
||||
ImGuiUtil.DrawTableColumn("Wetness");
|
||||
|
|
@ -212,12 +204,12 @@ public unsafe class ModelEvaluationPanel(
|
|||
private void DrawCustomize(Actor actor, Model model)
|
||||
{
|
||||
using var id = ImRaii.PushId("Customize");
|
||||
var actorCustomize = new Customize(actor.IsCharacter
|
||||
var actorCustomize = actor.IsCharacter
|
||||
? *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData
|
||||
: new CustomizeArray());
|
||||
var modelCustomize = new Customize(model.IsHuman
|
||||
: new CustomizeArray();
|
||||
var modelCustomize = model.IsHuman
|
||||
? *(CustomizeArray*)model.AsHuman->Customize.Data
|
||||
: new CustomizeArray());
|
||||
: new CustomizeArray();
|
||||
foreach (var type in Enum.GetValues<CustomizeIndex>())
|
||||
{
|
||||
using var id2 = ImRaii.PushId((int)type);
|
||||
|
|
@ -235,7 +227,7 @@ public unsafe class ModelEvaluationPanel(
|
|||
var shift = BitOperations.TrailingZeroCount(mask);
|
||||
var newValue = value + (1 << shift);
|
||||
modelCustomize.Set(type, (CustomizeValue)newValue);
|
||||
_changeCustomizeService.UpdateCustomize(model, modelCustomize.Data);
|
||||
_changeCustomizeService.UpdateCustomize(model, modelCustomize);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
|
|
@ -246,14 +238,14 @@ public unsafe class ModelEvaluationPanel(
|
|||
var shift = BitOperations.TrailingZeroCount(mask);
|
||||
var newValue = value - (1 << shift);
|
||||
modelCustomize.Set(type, (CustomizeValue)newValue);
|
||||
_changeCustomizeService.UpdateCustomize(model, modelCustomize.Data);
|
||||
_changeCustomizeService.UpdateCustomize(model, modelCustomize);
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (ImGui.SmallButton("Reset"))
|
||||
{
|
||||
modelCustomize.Set(type, actorCustomize[type]);
|
||||
_changeCustomizeService.UpdateCustomize(model, modelCustomize.Data);
|
||||
_changeCustomizeService.UpdateCustomize(model, modelCustomize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,15 @@ using System.Numerics;
|
|||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.State;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Raii;
|
||||
using Penumbra.GameData.Enums;
|
||||
using ImGuiClip = OtterGui.ImGuiClip;
|
||||
|
||||
namespace Glamourer.Gui.Tabs.DebugTab;
|
||||
|
|
@ -24,7 +25,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM
|
|||
=> false;
|
||||
|
||||
private string _npcFilter = string.Empty;
|
||||
private bool _customizeOrGear = false;
|
||||
private bool _customizeOrGear;
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
|
|
@ -48,19 +49,17 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM
|
|||
ImGui.TableNextRow();
|
||||
var idx = 0;
|
||||
var remainder = ImGuiClip.FilteredClippedDraw(_npcCombo.Items, skips,
|
||||
d => d.Name.Contains(_npcFilter, StringComparison.OrdinalIgnoreCase), Draw);
|
||||
d => d.Name.Contains(_npcFilter, StringComparison.OrdinalIgnoreCase), DrawData);
|
||||
ImGui.TableNextColumn();
|
||||
ImGuiClip.DrawEndDummy(remainder, ImGui.GetFrameHeightWithSpacing());
|
||||
|
||||
|
||||
return;
|
||||
|
||||
void Draw(NpcData data)
|
||||
void DrawData(NpcData data)
|
||||
{
|
||||
using var id = ImRaii.PushId(idx++);
|
||||
var disabled = !_state.GetOrCreate(_objectManager.Player, out var state);
|
||||
ImGui.TableNextColumn();
|
||||
if (ImGuiUtil.DrawDisabledButton("Apply", Vector2.Zero, string.Empty, disabled, false))
|
||||
if (ImGuiUtil.DrawDisabledButton("Apply", Vector2.Zero, string.Empty, disabled))
|
||||
{
|
||||
foreach (var (slot, item, stain) in _designConverter.FromDrawData(data.Equip.ToArray(), data.Mainhand, data.Offhand))
|
||||
_state.ChangeEquip(state!, slot, item, stain, StateChanged.Source.Manual);
|
||||
|
|
@ -76,7 +75,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM
|
|||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted(data.Kind is ObjectKind.BattleNpc ? "B" : "E");
|
||||
|
||||
using (var icon = ImRaii.PushFont(UiBuilder.IconFont))
|
||||
using (_ = ImRaii.PushFont(UiBuilder.IconFont))
|
||||
{
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
|
|
@ -86,7 +85,7 @@ public class NpcAppearancePanel(NpcCombo _npcCombo, StateManager _state, ObjectM
|
|||
using var mono = ImRaii.PushFont(UiBuilder.MonoFont);
|
||||
ImGui.TableNextColumn();
|
||||
ImGui.AlignTextToFramePadding();
|
||||
ImGui.TextUnformatted(_customizeOrGear ? data.Customize.Data.ToString() : data.WriteGear());
|
||||
ImGui.TextUnformatted(_customizeOrGear ? data.Customize.ToString() : data.WriteGear());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
using Glamourer.Customization;
|
||||
using Glamourer.GameData;
|
||||
using OtterGui.Widgets;
|
||||
|
||||
namespace Glamourer.Gui.Tabs;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ using Dalamud.Interface.Utility;
|
|||
using Glamourer.Events;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using Glamourer.Unlocks;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Gui;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
using System.Numerics;
|
||||
using Dalamud.Interface;
|
||||
using Dalamud.Interface.Utility;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using Glamourer.Unlocks;
|
||||
using ImGuiNET;
|
||||
using Lumina.Misc;
|
||||
|
|
@ -61,7 +59,7 @@ public static class UiHelpers
|
|||
{
|
||||
var flags = (sbyte)(currentApply ? currentValue ? 1 : -1 : 0);
|
||||
using var style = ImRaii.PushStyle(ImGuiStyleVar.ItemSpacing, ImGui.GetStyle().ItemInnerSpacing);
|
||||
using (var disabled = ImRaii.Disabled(locked))
|
||||
using (_ = ImRaii.Disabled(locked))
|
||||
{
|
||||
if (new TristateCheckbox(ColorId.TriStateCross.Value(), ColorId.TriStateCheck.Value(), ColorId.TriStateNeutral.Value()).Draw(
|
||||
"##" + label, flags, out flags))
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
using Dalamud.Hooking;
|
||||
using Dalamud.Plugin.Services;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Interop.Structs;
|
||||
using OtterGui.Classes;
|
||||
|
|
@ -15,7 +14,7 @@ namespace Glamourer.Interop;
|
|||
/// Changes in Race, body type or Gender are probably ignored.
|
||||
/// This operates on draw objects, not game objects.
|
||||
/// </summary>
|
||||
public unsafe class ChangeCustomizeService : EventWrapper<Action<Model, Ref<Customize>>, ChangeCustomizeService.Priority>
|
||||
public unsafe class ChangeCustomizeService : EventWrapper<Action<Model, Ref<CustomizeArray>>, ChangeCustomizeService.Priority>
|
||||
{
|
||||
private readonly PenumbraReloaded _penumbraReloaded;
|
||||
private readonly IGameInteropProvider _interop;
|
||||
|
|
@ -81,10 +80,11 @@ public unsafe class ChangeCustomizeService : EventWrapper<Action<Model, Ref<Cust
|
|||
{
|
||||
if (!InUpdate.InMethod)
|
||||
{
|
||||
var customize = new Ref<Customize>(new Customize(*(CustomizeArray*)data));
|
||||
var customize = new Ref<CustomizeArray>(*(CustomizeArray*)data);
|
||||
Invoke(this, (Model)human, customize);
|
||||
((Customize*)data)->Load(customize.Value);
|
||||
*(CustomizeArray*)data = customize.Value;
|
||||
}
|
||||
|
||||
return _changeCustomizeHook.Original(human, data, skipEquipment);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
using System;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
|
@ -70,7 +68,7 @@ public sealed class CharaFile
|
|||
return;
|
||||
|
||||
data.SetItem(slot, item);
|
||||
data.SetStain(slot, (StainId)dye);
|
||||
data.SetStain(slot, dye);
|
||||
flags |= slot.ToFlag();
|
||||
flags |= slot.ToStainFlag();
|
||||
}
|
||||
|
|
@ -94,7 +92,7 @@ public sealed class CharaFile
|
|||
flags |= slot.ToStainFlag();
|
||||
}
|
||||
|
||||
private static CustomizeFlag ParseCustomize(JObject jObj, ref Customize customize)
|
||||
private static CustomizeFlag ParseCustomize(JObject jObj, ref CustomizeArray customize)
|
||||
{
|
||||
CustomizeFlag ret = 0;
|
||||
customize.Race = ParseRace(jObj, ref ret);
|
||||
|
|
@ -149,7 +147,7 @@ public sealed class CharaFile
|
|||
return id;
|
||||
}
|
||||
|
||||
private static void ParseFacial(JObject jObj, ref Customize customize, ref CustomizeFlag application)
|
||||
private static void ParseFacial(JObject jObj, ref CustomizeArray customize, ref CustomizeFlag application)
|
||||
{
|
||||
var jTok = jObj["FacialFeatures"];
|
||||
if (jTok == null)
|
||||
|
|
@ -186,7 +184,7 @@ public sealed class CharaFile
|
|||
customize[CustomizeIndex.LegacyTattoo] = CustomizeValue.Max;
|
||||
}
|
||||
|
||||
private static void ParseHighlights(JObject jObj, ref Customize customize, ref CustomizeFlag application)
|
||||
private static void ParseHighlights(JObject jObj, ref CustomizeArray customize, ref CustomizeFlag application)
|
||||
{
|
||||
var jTok = jObj["EnableHighlights"];
|
||||
if (jTok == null)
|
||||
|
|
@ -242,15 +240,15 @@ public sealed class CharaFile
|
|||
throw new Exception($"Age {age} != Normal is not supported.");
|
||||
}
|
||||
|
||||
private static unsafe void ParseByte(JObject jObj, string property, CustomizeIndex idx, ref Customize customize,
|
||||
private static unsafe void ParseByte(JObject jObj, string property, CustomizeIndex idx, ref CustomizeArray customize,
|
||||
ref CustomizeFlag application)
|
||||
{
|
||||
var jTok = jObj[property];
|
||||
if (jTok == null)
|
||||
return;
|
||||
|
||||
customize.Data.Data[idx.ToByteAndMask().ByteIdx] = jTok.ToObject<byte>();
|
||||
application |= idx.ToFlag();
|
||||
customize.Data[idx.ToByteAndMask().ByteIdx] = jTok.ToObject<byte>();
|
||||
application |= idx.ToFlag();
|
||||
}
|
||||
|
||||
private static SubRace ParseTribe(JObject jObj, ref CustomizeFlag application)
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ public sealed class CmaFile
|
|||
var byteData = Convert.FromHexString(bytes);
|
||||
fixed (byte* ptr = byteData)
|
||||
{
|
||||
data.Customize.Data.Read(ptr);
|
||||
data.Customize.Read(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -64,7 +64,7 @@ public sealed class CmaFile
|
|||
data.SetStain(slot, armor.Stain);
|
||||
}
|
||||
|
||||
data.Customize.Data.Read(ptr);
|
||||
data.Customize.Read(ptr);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Structs;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
|
|
|
|||
|
|
@ -4,13 +4,14 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using Dalamud.Interface.DragDrop;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Interop.CharaFile;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Files;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
|
|
@ -139,7 +140,7 @@ public class ImportService(CustomizationService _customizations, IDragDropManage
|
|||
return true;
|
||||
}
|
||||
|
||||
public bool SaveDesignAsDat(string path, in Customize input, string description)
|
||||
public bool SaveDesignAsDat(string path, in CustomizeArray input, string description)
|
||||
{
|
||||
if (!Verify(input, out var voice))
|
||||
return false;
|
||||
|
|
@ -168,7 +169,7 @@ public class ImportService(CustomizationService _customizations, IDragDropManage
|
|||
}
|
||||
}
|
||||
|
||||
public bool Verify(in Customize input, out byte voice, byte? inputVoice = null)
|
||||
public bool Verify(in CustomizeArray input, out byte voice, byte? inputVoice = null)
|
||||
{
|
||||
voice = 0;
|
||||
if (_customizations.ValidateClan(input.Clan, input.Race, out _, out _).Length > 0)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,9 @@ using Dalamud.Plugin.Services;
|
|||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
|
|
@ -14,19 +16,20 @@ public class JobService : IDisposable
|
|||
{
|
||||
private readonly nint _characterDataOffset;
|
||||
|
||||
public readonly IReadOnlyDictionary<byte, Job> Jobs;
|
||||
public readonly IReadOnlyDictionary<ushort, JobGroup> JobGroups;
|
||||
public readonly IReadOnlyList<JobGroup> AllJobGroups;
|
||||
public readonly DictJob Jobs;
|
||||
public readonly DictJobGroup JobGroups;
|
||||
|
||||
public IReadOnlyList<JobGroup> AllJobGroups
|
||||
=> JobGroups.AllJobGroups;
|
||||
|
||||
public event Action<Actor, Job, Job>? JobChanged;
|
||||
|
||||
public JobService(IDataManager gameData, IGameInteropProvider interop)
|
||||
public JobService(DictJob jobs, DictJobGroup jobGroups, IDataManager gameData, IGameInteropProvider interop)
|
||||
{
|
||||
interop.InitializeFromAttributes(this);
|
||||
_characterDataOffset = Marshal.OffsetOf<Character>(nameof(Character.CharacterData));
|
||||
Jobs = GameData.Jobs(gameData);
|
||||
AllJobGroups = GameData.AllJobGroups(gameData);
|
||||
JobGroups = GameData.JobGroups(gameData);
|
||||
Jobs = jobs;
|
||||
JobGroups = jobGroups;
|
||||
_changeJobHook.Enable();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,6 @@
|
|||
using System;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.String;
|
||||
|
|
@ -116,8 +114,8 @@ public readonly unsafe struct Actor : IEquatable<Actor>
|
|||
public CharacterWeapon GetOffhand()
|
||||
=> new(AsCharacter->DrawData.Weapon(DrawDataContainer.WeaponSlot.OffHand).ModelId.Value);
|
||||
|
||||
public Customize GetCustomize()
|
||||
=> *(Customize*)&AsCharacter->DrawData.CustomizeData;
|
||||
public CustomizeArray GetCustomize()
|
||||
=> *(CustomizeArray*)&AsCharacter->DrawData.CustomizeData;
|
||||
|
||||
// TODO remove this when available in ClientStructs
|
||||
internal ref CrestFlag CrestBitfield
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Object = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.Object;
|
||||
|
|
@ -91,8 +90,8 @@ public readonly unsafe struct Model : IEquatable<Model>
|
|||
public CharacterArmor GetArmor(EquipSlot slot)
|
||||
=> ((CharacterArmor*)&AsHuman->Head)[slot.ToIndex()];
|
||||
|
||||
public Customize GetCustomize()
|
||||
=> *(Customize*)&AsHuman->Customize;
|
||||
public CustomizeArray GetCustomize()
|
||||
=> *(CustomizeArray*)&AsHuman->Customize;
|
||||
|
||||
public (Model Address, CharacterWeapon Data) GetMainhand()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ using Dalamud.Plugin.Services;
|
|||
using Dalamud.Utility.Signatures;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Penumbra.GameData;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ using Dalamud.Plugin.Services;
|
|||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
|
|
@ -14,18 +13,15 @@ namespace Glamourer.Interop;
|
|||
public unsafe class WeaponService : IDisposable
|
||||
{
|
||||
private readonly WeaponLoading _event;
|
||||
private readonly CrestService _crestService;
|
||||
private readonly ThreadLocal<bool> _inUpdate = new(() => false);
|
||||
|
||||
|
||||
private readonly delegate* unmanaged[Stdcall]<DrawDataContainer*, uint, ulong, byte, byte, byte, byte, void>
|
||||
_original;
|
||||
|
||||
|
||||
public WeaponService(WeaponLoading @event, IGameInteropProvider interop, CrestService crestService)
|
||||
public WeaponService(WeaponLoading @event, IGameInteropProvider interop)
|
||||
{
|
||||
_event = @event;
|
||||
_crestService = crestService;
|
||||
_event = @event;
|
||||
_loadWeaponHook =
|
||||
interop.HookFromAddress<LoadWeaponDelegate>((nint)DrawDataContainer.MemberFunctionPointers.LoadWeapon, LoadWeaponDetour);
|
||||
_original =
|
||||
|
|
@ -73,7 +69,7 @@ public unsafe class WeaponService : IDisposable
|
|||
_event.Invoke(actor, EquipSlot.MainHand, ref tmpWeapon);
|
||||
|
||||
_loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4);
|
||||
|
||||
|
||||
if (tmpWeapon.Value != weapon.Value)
|
||||
{
|
||||
if (tmpWeapon.Skeleton.Id == 0)
|
||||
|
|
|
|||
|
|
@ -5,13 +5,11 @@ using Dalamud.Game.Command;
|
|||
using Dalamud.Game.Text.SeStringHandling;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Glamourer.Automation;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Gui;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@ using System.Linq;
|
|||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.GameData;
|
||||
using OtterGui.Services;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Services;
|
||||
|
||||
|
|
@ -30,13 +31,12 @@ public sealed class CustomizationService(
|
|||
public Task Awaiter
|
||||
=> _task;
|
||||
|
||||
public (Customize NewValue, CustomizeFlag Applied, CustomizeFlag Changed) Combine(Customize oldValues, Customize newValues,
|
||||
public (CustomizeArray NewValue, CustomizeFlag Applied, CustomizeFlag Changed) Combine(CustomizeArray oldValues, CustomizeArray newValues,
|
||||
CustomizeFlag applyWhich, bool allowUnknown)
|
||||
{
|
||||
CustomizeFlag applied = 0;
|
||||
CustomizeFlag changed = 0;
|
||||
Customize ret = default;
|
||||
ret.Load(oldValues);
|
||||
var ret = oldValues;
|
||||
if (applyWhich.HasFlag(CustomizeFlag.Clan))
|
||||
{
|
||||
changed |= ChangeClan(ref ret, newValues.Clan);
|
||||
|
|
@ -247,7 +247,7 @@ public sealed class CustomizationService(
|
|||
}
|
||||
|
||||
/// <summary> Change a clan while keeping all other customizations valid. </summary>
|
||||
public CustomizeFlag ChangeClan(ref Customize customize, SubRace newClan)
|
||||
public CustomizeFlag ChangeClan(ref CustomizeArray customize, SubRace newClan)
|
||||
{
|
||||
if (customize.Clan == newClan)
|
||||
return 0;
|
||||
|
|
@ -271,7 +271,7 @@ public sealed class CustomizationService(
|
|||
}
|
||||
|
||||
/// <summary> Change a gender while keeping all other customizations valid. </summary>
|
||||
public CustomizeFlag ChangeGender(ref Customize customize, Gender newGender)
|
||||
public CustomizeFlag ChangeGender(ref CustomizeArray customize, Gender newGender)
|
||||
{
|
||||
if (customize.Gender == newGender)
|
||||
return 0;
|
||||
|
|
@ -288,7 +288,7 @@ public sealed class CustomizationService(
|
|||
return FixValues(set, ref customize) | CustomizeFlag.Gender;
|
||||
}
|
||||
|
||||
private static CustomizeFlag FixValues(CustomizationSet set, ref Customize customize)
|
||||
private static CustomizeFlag FixValues(CustomizationSet set, ref CustomizeArray customize)
|
||||
{
|
||||
CustomizeFlag flags = 0;
|
||||
foreach (var idx in CustomizationExtensions.AllBasic)
|
||||
|
|
|
|||
|
|
@ -15,7 +15,6 @@ using Glamourer.Gui.Tabs.UnlocksTab;
|
|||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using Glamourer.Unlocks;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using OtterGui.Classes;
|
||||
|
|
@ -24,6 +23,7 @@ using OtterGui.Services;
|
|||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Services;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,11 @@
|
|||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Enums;
|
||||
using System.Linq;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Plugin.Services;
|
||||
using CustomizeIndex = Glamourer.Customization.CustomizeIndex;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
||||
|
|
@ -34,7 +32,7 @@ public class ActorState
|
|||
public DesignData ModelData;
|
||||
|
||||
/// <summary> The last seen job. </summary>
|
||||
public byte LastJob;
|
||||
public JobId LastJob;
|
||||
|
||||
/// <summary> The Lock-Key locking this state. </summary>
|
||||
public uint Combination;
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
using System.Linq;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Interface.Internal.Notifications;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Gui;
|
||||
using Glamourer.Interop;
|
||||
|
|
@ -12,7 +11,7 @@ using ImGuiNET;
|
|||
using OtterGui.Classes;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using CustomizeIndex = Glamourer.Customization.CustomizeIndex;
|
||||
using CustomizeIndex = Penumbra.GameData.Enums.CustomizeIndex;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
||||
|
|
@ -107,7 +106,7 @@ public unsafe class FunModule : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
public void ApplyFun(Actor actor, Span<CharacterArmor> armor, ref Customize customize)
|
||||
public void ApplyFun(Actor actor, Span<CharacterArmor> armor, ref CustomizeArray customize)
|
||||
{
|
||||
if (!ValidFunTarget(actor))
|
||||
return;
|
||||
|
|
@ -181,7 +180,7 @@ public unsafe class FunModule : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
public void ApplyOops(ref Customize customize)
|
||||
public void ApplyOops(ref CustomizeArray customize)
|
||||
{
|
||||
if (_codes.EnabledOops == Race.Unknown)
|
||||
return;
|
||||
|
|
@ -193,7 +192,7 @@ public unsafe class FunModule : IDisposable
|
|||
_customizations.ChangeClan(ref customize, targetClan);
|
||||
}
|
||||
|
||||
public void ApplyIndividual(ref Customize customize)
|
||||
public void ApplyIndividual(ref CustomizeArray customize)
|
||||
{
|
||||
if (!_codes.EnabledIndividual)
|
||||
return;
|
||||
|
|
@ -209,7 +208,7 @@ public unsafe class FunModule : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
public void Apply63(ref Customize customize)
|
||||
public void Apply63(ref CustomizeArray customize)
|
||||
{
|
||||
if (!_codes.Enabled63 || customize.Race is Race.Hrothgar) // TODO Female Hrothgar
|
||||
return;
|
||||
|
|
@ -217,7 +216,7 @@ public unsafe class FunModule : IDisposable
|
|||
_customizations.ChangeGender(ref customize, customize.Gender is Gender.Male ? Gender.Female : Gender.Male);
|
||||
}
|
||||
|
||||
public void ApplySizing(Actor actor, ref Customize customize)
|
||||
public void ApplySizing(Actor actor, ref CustomizeArray customize)
|
||||
{
|
||||
if (_codes.EnabledSizing == CodeService.Sizing.None)
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
using System.Linq;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
|
@ -40,7 +38,7 @@ public class StateApplier(UpdateSlotService _updateSlot, VisorService _visor, We
|
|||
/// Change the customization values of actors either by applying them via update or redrawing,
|
||||
/// this depends on whether the changes include changes to Race, Gender, Body Type or Face.
|
||||
/// </summary>
|
||||
public unsafe void ChangeCustomize(ActorData data, in Customize customize, ActorState? state = null)
|
||||
public unsafe void ChangeCustomize(ActorData data, in CustomizeArray customize, ActorState? _ = null)
|
||||
{
|
||||
foreach (var actor in data.Objects)
|
||||
{
|
||||
|
|
@ -48,15 +46,15 @@ public class StateApplier(UpdateSlotService _updateSlot, VisorService _visor, We
|
|||
if (!mdl.IsCharacterBase)
|
||||
continue;
|
||||
|
||||
var flags = Customize.Compare(mdl.GetCustomize(), customize);
|
||||
var flags = CustomizeArray.Compare(mdl.GetCustomize(), customize);
|
||||
if (!flags.RequiresRedraw() || !mdl.IsHuman)
|
||||
{
|
||||
_changeCustomize.UpdateCustomize(mdl, customize.Data);
|
||||
_changeCustomize.UpdateCustomize(mdl, customize);
|
||||
}
|
||||
else if (data.Objects.Count > 1 && _objects.IsInGPose && !actor.IsGPoseOrCutscene)
|
||||
{
|
||||
var mdlCustomize = (Customize*)&mdl.AsHuman->Customize;
|
||||
mdlCustomize->Load(customize);
|
||||
var mdlCustomize = (CustomizeArray*)&mdl.AsHuman->Customize;
|
||||
*mdlCustomize = customize;
|
||||
_penumbra.RedrawObject(actor, RedrawType.AfterGPose);
|
||||
}
|
||||
else
|
||||
|
|
@ -66,7 +64,7 @@ public class StateApplier(UpdateSlotService _updateSlot, VisorService _visor, We
|
|||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="ChangeCustomize(ActorData, in Customize, ActorState?)"/>
|
||||
/// <inheritdoc cref="ChangeCustomize(ActorData, in CustomizeArray, ActorState?)"/>
|
||||
public ActorData ChangeCustomize(ActorState state, bool apply)
|
||||
{
|
||||
var data = GetData(state);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
|
@ -30,7 +28,7 @@ public class StateEditor
|
|||
|
||||
/// <summary> Change the model id. If the actor is changed from a human to another human, customize and equipData are unused. </summary>
|
||||
/// <remarks> We currently only allow changing things to humans, not humans to monsters. </remarks>
|
||||
public bool ChangeModelId(ActorState state, uint modelId, in Customize customize, nint equipData, StateChanged.Source source,
|
||||
public bool ChangeModelId(ActorState state, uint modelId, in CustomizeArray customize, nint equipData, StateChanged.Source source,
|
||||
out uint oldModelId, uint key = 0)
|
||||
{
|
||||
oldModelId = state.ModelData.ModelId;
|
||||
|
|
@ -57,7 +55,7 @@ public class StateEditor
|
|||
return false;
|
||||
|
||||
// Fix up everything else to make sure the result is a valid human.
|
||||
state.ModelData.Customize = Customize.Default;
|
||||
state.ModelData.Customize = CustomizeArray.Default;
|
||||
state.ModelData.SetDefaultEquipment(_items);
|
||||
state.ModelData.SetHatVisible(true);
|
||||
state.ModelData.SetWeaponVisible(true);
|
||||
|
|
@ -104,8 +102,8 @@ public class StateEditor
|
|||
}
|
||||
|
||||
/// <summary> Change an entire customization array according to flags. </summary>
|
||||
public bool ChangeHumanCustomize(ActorState state, in Customize customizeInput, CustomizeFlag applyWhich, StateChanged.Source source,
|
||||
out Customize old, out CustomizeFlag changed, uint key = 0)
|
||||
public bool ChangeHumanCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag applyWhich, StateChanged.Source source,
|
||||
out CustomizeArray old, out CustomizeFlag changed, uint key = 0)
|
||||
{
|
||||
old = state.ModelData.Customize;
|
||||
changed = 0;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
using Glamourer.Automation;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.Penumbra;
|
||||
|
|
@ -12,7 +11,6 @@ using Penumbra.GameData.Structs;
|
|||
using System;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
|
@ -114,7 +112,7 @@ public class StateListener : IDisposable
|
|||
_creatingIdentifier = actor.GetIdentifier(_actors);
|
||||
|
||||
ref var modelId = ref *(uint*)modelPtr;
|
||||
ref var customize = ref *(Customize*)customizePtr;
|
||||
ref var customize = ref *(CustomizeArray*)customizePtr;
|
||||
if (_autoDesignApplier.Reduce(actor, _creatingIdentifier, out _creatingState))
|
||||
{
|
||||
switch (UpdateBaseData(actor, _creatingState, modelId, customizePtr, equipDataPtr))
|
||||
|
|
@ -140,7 +138,7 @@ public class StateListener : IDisposable
|
|||
ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender);
|
||||
}
|
||||
|
||||
private unsafe void OnCustomizeChange(Model model, Ref<Customize> customize)
|
||||
private unsafe void OnCustomizeChange(Model model, Ref<CustomizeArray> customize)
|
||||
{
|
||||
if (!model.IsHuman)
|
||||
return;
|
||||
|
|
@ -156,7 +154,7 @@ public class StateListener : IDisposable
|
|||
UpdateCustomize(actor, state, ref customize.Value, false);
|
||||
}
|
||||
|
||||
private void UpdateCustomize(Actor actor, ActorState state, ref Customize customize, bool checkTransform)
|
||||
private void UpdateCustomize(Actor actor, ActorState state, ref CustomizeArray customize, bool checkTransform)
|
||||
{
|
||||
switch (UpdateBaseData(actor, state, customize, checkTransform))
|
||||
{
|
||||
|
|
@ -515,7 +513,7 @@ public class StateListener : IDisposable
|
|||
if (isHuman)
|
||||
state.BaseData = _manager.FromActor(actor, false, false);
|
||||
else
|
||||
state.BaseData.LoadNonHuman(modelId, *(Customize*)customizeData, equipData);
|
||||
state.BaseData.LoadNonHuman(modelId, *(CustomizeArray*)customizeData, equipData);
|
||||
|
||||
return UpdateState.Change;
|
||||
}
|
||||
|
|
@ -526,7 +524,7 @@ public class StateListener : IDisposable
|
|||
/// only if we kept track of state of someone who went to the aesthetician,
|
||||
/// or if they used other tools to change things.
|
||||
/// </summary>
|
||||
private UpdateState UpdateBaseData(Actor actor, ActorState state, Customize customize, bool checkTransform)
|
||||
private UpdateState UpdateBaseData(Actor actor, ActorState state, CustomizeArray customize, bool checkTransform)
|
||||
{
|
||||
// Customize array does not agree between game object and draw object => transformation.
|
||||
if (checkTransform && !actor.GetCustomize().Equals(customize))
|
||||
|
|
@ -537,7 +535,7 @@ public class StateListener : IDisposable
|
|||
return UpdateState.NoChange; // TODO: handle wrong base data.
|
||||
|
||||
// Update customize base state.
|
||||
state.BaseData.Customize.Load(customize);
|
||||
state.BaseData.Customize = customize;
|
||||
return UpdateState.Change;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,13 +4,11 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Interop;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.DataContainers;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
|
@ -18,8 +16,15 @@ using Penumbra.GameData.Structs;
|
|||
|
||||
namespace Glamourer.State;
|
||||
|
||||
public class StateManager(ActorManager _actors, ItemManager _items, StateChanged _event, StateApplier _applier, StateEditor _editor,
|
||||
HumanModelList _humans, ICondition _condition, IClientState _clientState)
|
||||
public class StateManager(
|
||||
ActorManager _actors,
|
||||
ItemManager _items,
|
||||
StateChanged _event,
|
||||
StateApplier _applier,
|
||||
StateEditor _editor,
|
||||
HumanModelList _humans,
|
||||
ICondition _condition,
|
||||
IClientState _clientState)
|
||||
: IReadOnlyDictionary<ActorIdentifier, ActorState>
|
||||
{
|
||||
private readonly Dictionary<ActorIdentifier, ActorState> _states = [];
|
||||
|
|
@ -102,7 +107,7 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged
|
|||
// TODO reverse search model data to get model id from model.
|
||||
if (!_humans.IsHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId))
|
||||
{
|
||||
ret.LoadNonHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId, *(Customize*)&actor.AsCharacter->DrawData.CustomizeData,
|
||||
ret.LoadNonHuman((uint)actor.AsCharacter->CharacterData.ModelCharaId, *(CustomizeArray*)&actor.AsCharacter->DrawData.CustomizeData,
|
||||
(nint)(&actor.AsCharacter->DrawData.Head));
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -194,9 +199,9 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged
|
|||
return;
|
||||
|
||||
var gauntlets = _items.Identify(EquipSlot.Hands, offhand.Skeleton, (Variant)offhand.Weapon.Id);
|
||||
offhand.Skeleton = (PrimaryId)(mainhand.Skeleton.Id + 50);
|
||||
offhand.Variant = mainhand.Variant;
|
||||
offhand.Weapon = mainhand.Weapon;
|
||||
offhand.Skeleton = (PrimaryId)(mainhand.Skeleton.Id + 50);
|
||||
offhand.Variant = mainhand.Variant;
|
||||
offhand.Weapon = mainhand.Weapon;
|
||||
ret.SetItem(EquipSlot.Hands, gauntlets);
|
||||
ret.SetStain(EquipSlot.Hands, mainhand.Stain);
|
||||
}
|
||||
|
|
@ -205,10 +210,10 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged
|
|||
|
||||
/// <summary> Turn an actor human. </summary>
|
||||
public void TurnHuman(ActorState state, StateChanged.Source source, uint key = 0)
|
||||
=> ChangeModelId(state, 0, Customize.Default, nint.Zero, source, key);
|
||||
=> ChangeModelId(state, 0, CustomizeArray.Default, nint.Zero, source, key);
|
||||
|
||||
/// <summary> Turn an actor to. </summary>
|
||||
public void ChangeModelId(ActorState state, uint modelId, Customize customize, nint equipData, StateChanged.Source source,
|
||||
public void ChangeModelId(ActorState state, uint modelId, CustomizeArray customize, nint equipData, StateChanged.Source source,
|
||||
uint key = 0)
|
||||
{
|
||||
if (!_editor.ChangeModelId(state, modelId, customize, equipData, source, out var old, key))
|
||||
|
|
@ -233,7 +238,8 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged
|
|||
}
|
||||
|
||||
/// <summary> Change an entire customization array according to flags. </summary>
|
||||
public void ChangeCustomize(ActorState state, in Customize customizeInput, CustomizeFlag apply, StateChanged.Source source, uint key = 0)
|
||||
public void ChangeCustomize(ActorState state, in CustomizeArray customizeInput, CustomizeFlag apply, StateChanged.Source source,
|
||||
uint key = 0)
|
||||
{
|
||||
if (!_editor.ChangeHumanCustomize(state, customizeInput, apply, source, out var old, out var applied, key))
|
||||
return;
|
||||
|
|
@ -447,7 +453,7 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged
|
|||
|
||||
var redraw = state.ModelData.ModelId != state.BaseData.ModelId
|
||||
|| !state.ModelData.IsHuman
|
||||
|| Customize.Compare(state.ModelData.Customize, state.BaseData.Customize).RequiresRedraw();
|
||||
|| CustomizeArray.Compare(state.ModelData.Customize, state.BaseData.Customize).RequiresRedraw();
|
||||
state.ModelData = state.BaseData;
|
||||
state.ModelData.SetIsWet(false);
|
||||
foreach (var index in Enum.GetValues<CustomizeIndex>())
|
||||
|
|
@ -458,7 +464,7 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged
|
|||
state[slot, true] = StateChanged.Source.Game;
|
||||
state[slot, false] = StateChanged.Source.Game;
|
||||
}
|
||||
|
||||
|
||||
foreach (var type in Enum.GetValues<ActorState.MetaIndex>())
|
||||
state[type] = StateChanged.Source.Game;
|
||||
|
||||
|
|
@ -470,7 +476,7 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged
|
|||
actors = ApplyAll(state, redraw, true);
|
||||
Glamourer.Log.Verbose(
|
||||
$"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]");
|
||||
_event.Invoke(StateChanged.Type.Reset, StateChanged.Source.Manual, state, actors, null);
|
||||
_event.Invoke(StateChanged.Type.Reset, StateChanged.Source.Manual, state, actors);
|
||||
}
|
||||
|
||||
public void ResetStateFixed(ActorState state, uint key = 0)
|
||||
|
|
@ -538,7 +544,7 @@ public class StateManager(ActorManager _actors, ItemManager _items, StateChanged
|
|||
if (!GetOrCreate(actor, out var state))
|
||||
return;
|
||||
|
||||
ApplyAll(state, !actor.Model.IsHuman || Customize.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(),
|
||||
ApplyAll(state, !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(),
|
||||
false);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,10 +8,11 @@ using Dalamud.Plugin.Services;
|
|||
using Dalamud.Utility;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.GameData;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Services;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Unlocks;
|
||||
|
||||
|
|
@ -172,7 +173,7 @@ public class CustomizeUnlockManager : IDisposable, ISavable
|
|||
=> UnlockDictionaryHelpers.Load(ToFilename(_saveService.FileNames), _unlocked, id => Unlockable.Any(c => c.Value.Data == id),
|
||||
"customization");
|
||||
|
||||
/// <summary> Create a list of all unlockable hairstyles and facepaints. </summary>
|
||||
/// <summary> Create a list of all unlockable hairstyles and face paints. </summary>
|
||||
private static Dictionary<CustomizeData, (uint Data, string Name)> CreateUnlockableCustomizations(CustomizationService customizations,
|
||||
IDataManager gameData)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue