Shit's fucked, yo

This commit is contained in:
Ottermandias 2022-07-18 18:29:01 +02:00
parent 7af38aa2ce
commit a160276cc5
12 changed files with 507 additions and 429 deletions

View file

@ -16,6 +16,9 @@ public readonly unsafe struct CharacterCustomization
public CharacterCustomization(CustomizationData* data) public CharacterCustomization(CustomizationData* data)
=> _data = data; => _data = data;
public void Load(CharacterCustomization other)
=> *_data = *other._data;
public ref Race Race public ref Race Race
=> ref _data->Race; => ref _data->Race;

View file

@ -1,11 +1,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Linq; using System.Linq;
using Dalamud; using Dalamud;
using Dalamud.Data; using Dalamud.Data;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Glamourer.Structs; using Glamourer.Structs;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;

View file

@ -5,6 +5,7 @@ using Dalamud.Logging;
using Dalamud.Utility; using Dalamud.Utility;
using Lumina.Excel; using Lumina.Excel;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
using Newtonsoft.Json.Linq;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
using Race = Penumbra.GameData.Enums.Race; using Race = Penumbra.GameData.Enums.Race;
@ -33,16 +34,15 @@ public class RestrictedGear
} }
// Resolve a model given by its model id, variant and slot for your current race and gender. // Resolve a model given by its model id, variant and slot for your current race and gender.
public (bool Replaced, SetId ModelId, byte Variant) ResolveRestricted(SetId modelId, byte variant, EquipSlot slot, Race race, public (bool Replaced, CharacterArmor Armor) ResolveRestricted(CharacterArmor armor, EquipSlot slot, Race race, Gender gender)
Gender gender)
{ {
var quad = modelId.Value | ((uint)variant << 16); var quad = armor.Set.Value | ((uint)armor.Variant << 16);
// Check racial gear, this does not need slots. // Check racial gear, this does not need slots.
if (RaceGenderGroup.Contains(quad)) if (RaceGenderGroup.Contains(quad))
{ {
var idx = ((int)race - 1) * 2 + (gender is Gender.Female or Gender.FemaleNpc ? 1 : 0); var idx = ((int)race - 1) * 2 + (gender is Gender.Female or Gender.FemaleNpc ? 1 : 0);
var value = RaceGenderGroup[idx]; var value = RaceGenderGroup[idx];
return (value != quad, (ushort)value, (byte)(value >> 16)); return (value != quad, new CharacterArmor((ushort)value, (byte)(value >> 16), armor.Stain));
} }
// Check gender slots. If current gender is female, check if anything needs to be changed from male to female, // Check gender slots. If current gender is female, check if anything needs to be changed from male to female,
@ -52,10 +52,10 @@ public class RestrictedGear
var needle = quad | ((uint)slot.ToSlot() << 24); var needle = quad | ((uint)slot.ToSlot() << 24);
if (gender is Gender.Female or Gender.FemaleNpc && _maleToFemale.TryGetValue(needle, out var newValue) if (gender is Gender.Female or Gender.FemaleNpc && _maleToFemale.TryGetValue(needle, out var newValue)
|| gender is Gender.Male or Gender.MaleNpc && _femaleToMale.TryGetValue(needle, out newValue)) || gender is Gender.Male or Gender.MaleNpc && _femaleToMale.TryGetValue(needle, out newValue))
return (quad != newValue, (ushort)newValue, (byte)(newValue >> 16)); return (quad != newValue, new CharacterArmor((ushort)newValue, (byte)(newValue >> 16), armor.Stain));
// The gear is not restricted. // The gear is not restricted.
return (false, modelId, variant); return (false, armor);
} }
// Add all unknown restricted gear and pair it with emperor's new gear on start up. // Add all unknown restricted gear and pair it with emperor's new gear on start up.

87
Glamourer/Actor.cs Normal file
View file

@ -0,0 +1,87 @@
using System;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.Types;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Penumbra.GameData.ByteString;
namespace Glamourer;
public unsafe struct Actor : IEquatable<Actor>
{
public record struct Identifier(Utf8String Name, uint Id, ushort HomeWorld, ushort Index);
public static readonly Actor Null = new() { Pointer = null };
public FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Pointer;
public IntPtr Address
=> (IntPtr)Pointer;
public static implicit operator Actor(IntPtr? pointer)
=> new() { Pointer = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)pointer.GetValueOrDefault(IntPtr.Zero) };
public static implicit operator IntPtr(Actor actor)
=> actor.Pointer == null ? IntPtr.Zero : (IntPtr)actor.Pointer;
public Identifier GetIdentifier()
{
if (Pointer == null)
return new Identifier(Utf8String.Empty, 0, 0, 0);
return new Identifier(Utf8Name, Pointer->GameObject.ObjectID, Pointer->HomeWorld, Pointer->GameObject.ObjectIndex);
}
public Character? Character
=> Pointer == null ? null : Dalamud.Objects[Pointer->GameObject.ObjectIndex] as Character;
public bool IsAvailable
=> Pointer->GameObject.GetIsTargetable();
public bool IsHuman
=> Pointer != null && Pointer->ModelCharaId == 0;
public int ModelId
=> Pointer->ModelCharaId;
public ObjectKind ObjectKind
=> (ObjectKind)Pointer->GameObject.ObjectKind;
public Utf8String Utf8Name
=> new(Pointer->GameObject.Name);
public Human* DrawObject
=> (Human*)Pointer->GameObject.DrawObject;
public void SetModelId(int value)
{
if (Pointer != null)
Pointer->ModelCharaId = value;
}
public static implicit operator bool(Actor actor)
=> actor.Pointer != null;
public static bool operator true(Actor actor)
=> actor.Pointer != null;
public static bool operator false(Actor actor)
=> actor.Pointer == null;
public static bool operator !(Actor actor)
=> actor.Pointer == null;
public bool Equals(Actor other)
=> Pointer == other.Pointer;
public override bool Equals(object? obj)
=> obj is Actor other && Equals(other);
public override int GetHashCode()
=> ((ulong)Pointer).GetHashCode();
public static bool operator ==(Actor lhs, Actor rhs)
=> lhs.Pointer == rhs.Pointer;
public static bool operator !=(Actor lhs, Actor rhs)
=> lhs.Pointer != rhs.Pointer;
}

View file

@ -126,8 +126,8 @@ public class PenumbraAttach : IDisposable
//} //}
} }
public unsafe FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject* GameObjectFromDrawObject(IntPtr drawObject) public Actor GameObjectFromDrawObject(IntPtr drawObject)
=> (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)(_drawObjectInfo?.InvokeFunc(drawObject).Item1 ?? IntPtr.Zero); => _drawObjectInfo?.InvokeFunc(drawObject).Item1 ?? IntPtr.Zero;
public void RedrawObject(GameObject? actor, RedrawType settings, bool repeat) public void RedrawObject(GameObject? actor, RedrawType settings, bool repeat)
{ {

View file

@ -13,13 +13,13 @@ namespace Glamourer;
public class CharacterSaveConverter : JsonConverter<CharacterSave> public class CharacterSaveConverter : JsonConverter<CharacterSave>
{ {
public override void WriteJson(JsonWriter writer, CharacterSave value, JsonSerializer serializer) public override void WriteJson(JsonWriter writer, CharacterSave? value, JsonSerializer serializer)
{ {
var s = value.ToBase64(); var s = value?.ToBase64() ?? string.Empty;
serializer.Serialize(writer, s); serializer.Serialize(writer, s);
} }
public override CharacterSave ReadJson(JsonReader reader, Type objectType, CharacterSave existingValue, bool hasExistingValue, public override CharacterSave ReadJson(JsonReader reader, Type objectType, CharacterSave? existingValue, bool hasExistingValue,
JsonSerializer serializer) JsonSerializer serializer)
{ {
var token = JToken.Load(reader); var token = JToken.Load(reader);
@ -28,9 +28,8 @@ public class CharacterSaveConverter : JsonConverter<CharacterSave>
} }
} }
[JsonConverter(typeof(CharacterSaveConverter))]
[StructLayout(LayoutKind.Sequential, Pack = 1)] [StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct CharacterSave public unsafe struct CharacterData
{ {
[Flags] [Flags]
public enum SaveFlags : byte public enum SaveFlags : byte
@ -47,42 +46,27 @@ public unsafe struct CharacterSave
public const byte TotalSizeVersion1 = 1 + 1 + 2 + 56 + CustomizationData.CustomizationBytes; public const byte TotalSizeVersion1 = 1 + 1 + 2 + 56 + CustomizationData.CustomizationBytes;
public const byte TotalSizeVersion2 = 1 + 1 + 2 + 56 + CustomizationData.CustomizationBytes + 4 + 1; public const byte TotalSizeVersion2 = 1 + 1 + 2 + 56 + CustomizationData.CustomizationBytes + 4 + 1;
public const byte TotalSizeVersion3 = 1 + 1 + 2 + 7 + 7 + 2 + 40 + CustomizationData.CustomizationBytes + 4;
public const byte CurrentVersion = 3; public const byte CurrentVersion = 3;
public byte Version = CurrentVersion;
public SaveFlags Flags = 0;
public CharacterEquipMask Equip = 0;
public CharacterWeapon MainHand = default;
public CharacterWeapon OffHand = default;
public ushort Padding = 0;
public CharacterArmor Head = default;
public CharacterArmor Body = default;
public CharacterArmor Hands = default;
public CharacterArmor Legs = default;
public CharacterArmor Feet = default;
public CharacterArmor Ears = default;
public CharacterArmor Neck = default;
public CharacterArmor Wrist = default;
public CharacterArmor RFinger = default;
public CharacterArmor LFinger = default;
private CustomizationData CustomizationData = CustomizationData.Default;
public float Alpha = 1f;
public CharacterSave() public byte Version;
{ } public SaveFlags Flags;
public CharacterEquipMask Equip;
public void Load(Actor actor) public CharacterWeapon MainHand;
{ public CharacterWeapon OffHand;
if (!actor.IsHuman || actor.Pointer->GameObject.DrawObject == null) public ushort Padding;
return; public CharacterArmor Head;
public CharacterArmor Body;
var human = (Human*)actor.Pointer->GameObject.DrawObject; public CharacterArmor Hands;
CustomizationData = *(CustomizationData*)human->CustomizeData; public CharacterArmor Legs;
fixed (void* equip = &Head) public CharacterArmor Feet;
{ public CharacterArmor Ears;
Functions.MemCpyUnchecked(equip, human->EquipSlotData, sizeof(CharacterArmor) * 10); public CharacterArmor Neck;
} public CharacterArmor Wrist;
} public CharacterArmor RFinger;
public CharacterArmor LFinger;
private CustomizationData CustomizationData;
public float Alpha;
public CharacterCustomization Customize public CharacterCustomization Customize
{ {
@ -106,11 +90,47 @@ public unsafe struct CharacterSave
} }
} }
public static readonly CharacterData Default
= new()
{
Version = CurrentVersion,
Flags = SaveFlags.WriteCustomizations,
Equip = CharacterEquipMask.All,
MainHand = CharacterWeapon.Empty,
OffHand = CharacterWeapon.Empty,
Padding = 0,
Head = CharacterArmor.Empty,
Body = CharacterArmor.Empty,
Hands = CharacterArmor.Empty,
Legs = CharacterArmor.Empty,
Feet = CharacterArmor.Empty,
Ears = CharacterArmor.Empty,
Neck = CharacterArmor.Empty,
Wrist = CharacterArmor.Empty,
RFinger = CharacterArmor.Empty,
LFinger = CharacterArmor.Empty,
CustomizationData = CustomizationData.Default,
Alpha = 1f,
};
public void Load(Actor actor)
{
if (!actor.IsHuman || actor.Pointer->GameObject.DrawObject == null)
return;
var human = (Human*)actor.Pointer->GameObject.DrawObject;
CustomizationData = *(CustomizationData*)human->CustomizeData;
fixed (void* equip = &Head)
{
Functions.MemCpyUnchecked(equip, human->EquipSlotData, sizeof(CharacterArmor) * 10);
}
}
public string ToBase64() public string ToBase64()
{ {
fixed (void* ptr = &this) fixed (void* ptr = &this)
{ {
return Convert.ToBase64String(new ReadOnlySpan<byte>(ptr, sizeof(CharacterSave))); return Convert.ToBase64String(new ReadOnlySpan<byte>(ptr, sizeof(CharacterData)));
} }
} }
@ -128,10 +148,10 @@ public unsafe struct CharacterSave
$"Can not parse Base64 string into CharacterSave:\n\tInvalid value {value} in byte {idx}, should be in [{min},{max}]."); $"Can not parse Base64 string into CharacterSave:\n\tInvalid value {value} in byte {idx}, should be in [{min},{max}].");
} }
public static CharacterSave FromString(string data) public static CharacterData FromString(string data)
{ {
var bytes = Convert.FromBase64String(data); var bytes = Convert.FromBase64String(data);
var ret = new CharacterSave(); var ret = new CharacterData();
fixed (byte* ptr = bytes) fixed (byte* ptr = bytes)
{ {
switch (bytes[0]) switch (bytes[0])
@ -156,8 +176,8 @@ public unsafe struct CharacterSave
ret.Flags |= SaveFlags.VisorState; ret.Flags |= SaveFlags.VisorState;
break; break;
case 3: case 3:
CheckSize(bytes.Length, sizeof(CharacterSave)); CheckSize(bytes.Length, TotalSizeVersion3);
Functions.MemCpyUnchecked(&ret, ptr, sizeof(CharacterSave)); Functions.MemCpyUnchecked(&ret, ptr, TotalSizeVersion3);
break; break;
default: throw new Exception($"Can not parse Base64 string into CharacterSave:\n\tInvalid Version {bytes[0]}."); default: throw new Exception($"Can not parse Base64 string into CharacterSave:\n\tInvalid Version {bytes[0]}.");
} }
@ -166,3 +186,36 @@ public unsafe struct CharacterSave
return ret; return ret;
} }
} }
[JsonConverter(typeof(CharacterSaveConverter))]
public class CharacterSave
{
private CharacterData _data;
public CharacterSave()
=> _data = CharacterData.Default;
public CharacterSave(Actor actor)
=> _data.Load(actor);
public void Load(Actor actor)
=> _data.Load(actor);
public string ToBase64()
=> _data.ToBase64();
public CharacterCustomization Customization
=> _data.Customize;
public CharacterEquip Equipment
=> _data.Equipment;
public ref CharacterWeapon MainHand
=> ref _data.MainHand;
public ref CharacterWeapon OffHand
=> ref _data.OffHand;
public static CharacterSave FromString(string data)
=> new() { _data = CharacterData.FromString(data) };
}

View file

@ -54,71 +54,4 @@ public static unsafe class CustomizeExtensions
public static string ClanName(this CharacterCustomization customize) public static string ClanName(this CharacterCustomization customize)
=> ClanName(customize.Clan, customize.Gender); => ClanName(customize.Clan, customize.Gender);
// Change a gender and fix up all required customizations afterwards.
public static bool ChangeGender(this CharacterCustomization customize, Gender gender, CharacterEquip equip)
{
if (customize.Gender == gender)
return false;
customize.Gender = gender;
FixUpAttributes(customize, equip);
return true;
}
// Change a race and fix up all required customizations afterwards.
public static bool ChangeRace(this CharacterCustomization customize, SubRace clan, CharacterEquip equip)
{
if (customize.Clan == clan)
return false;
var race = clan.ToRace();
customize.Race = race;
customize.Clan = clan;
// TODO: Female Hrothgar
if (race == Race.Hrothgar)
customize.Gender = Gender.Male;
FixUpAttributes(customize, equip);
return true;
}
// Go through a whole customization struct and fix up all settings that need fixing.
private static void FixUpAttributes(CharacterCustomization customize, CharacterEquip equip)
{
var set = Glamourer.Customization.GetList(customize.Clan, customize.Gender);
foreach (CustomizationId id in Enum.GetValues(typeof(CustomizationId)))
{
switch (id)
{
case CustomizationId.Race: break;
case CustomizationId.Clan: break;
case CustomizationId.BodyType: break;
case CustomizationId.Gender: break;
case CustomizationId.FacialFeaturesTattoos: break;
case CustomizationId.HighlightsOnFlag: break;
case CustomizationId.Face: break;
default:
var count = set.Count(id);
if (set.DataByValue(id, customize[id], out _) < 0)
customize[id] = count == 0 ? (byte)0 : set.Data(id, 0).Value;
break;
}
}
if (!equip)
return;
foreach (var slot in EquipSlotExtensions.EqdpSlots)
{
var item = equip[slot];
var (replaced, newSet, newVariant) =
Glamourer.RestrictedGear.ResolveRestricted(item.Set, item.Variant, slot, customize.Race, customize.Gender);
if (replaced)
equip[slot] = new CharacterArmor(newSet, newVariant, item.Stain);
}
}
} }

View file

@ -48,7 +48,6 @@ public class Glamourer : IDalamudPlugin
public static ICustomizationManager Customization = null!; public static ICustomizationManager Customization = null!;
public static RedrawManager RedrawManager = null!; public static RedrawManager RedrawManager = null!;
public static RestrictedGear RestrictedGear = null!;
private readonly WindowSystem _windowSystem = new("Glamourer"); private readonly WindowSystem _windowSystem = new("Glamourer");
private readonly Interface _interface; private readonly Interface _interface;
@ -61,8 +60,6 @@ public class Glamourer : IDalamudPlugin
{ {
Dalamud.Initialize(pluginInterface); Dalamud.Initialize(pluginInterface);
Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData, Dalamud.ClientState.ClientLanguage); Customization = CustomizationManager.Create(Dalamud.PluginInterface, Dalamud.GameData, Dalamud.ClientState.ClientLanguage);
RestrictedGear = GameData.RestrictedGear(Dalamud.GameData);
var m = global::Penumbra.GameData.GameData.GetIdentifier(Dalamud.GameData, Dalamud.ClientState.ClientLanguage);
Config = GlamourerConfig.Load(); Config = GlamourerConfig.Load();
Penumbra = new PenumbraAttach(Config.AttachToPenumbra); Penumbra = new PenumbraAttach(Config.AttachToPenumbra);
@ -83,7 +80,6 @@ public class Glamourer : IDalamudPlugin
_interface = new Interface(this); _interface = new Interface(this);
_windowSystem.AddWindow(_interface); _windowSystem.AddWindow(_interface);
Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw; Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw;
var x = 0x00011000u;
//FixedDesignManager.Flag((Human*)((Actor)Dalamud.ClientState.LocalPlayer?.Address).Pointer->GameObject.DrawObject, 0, &x); //FixedDesignManager.Flag((Human*)((Actor)Dalamud.ClientState.LocalPlayer?.Address).Pointer->GameObject.DrawObject, 0, &x);
} }

View file

@ -1,16 +1,6 @@
using System; using System;
using System.Linq;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices;
using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Interface; using Dalamud.Interface;
using Dalamud.Logging;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Glamourer.Customization;
using Glamourer.Designs;
using Glamourer.Structs;
using ImGuiNET; using ImGuiNET;
using OtterGui; using OtterGui;
using OtterGui.Classes; using OtterGui.Classes;
@ -24,8 +14,7 @@ internal partial class Interface
{ {
private class ActorTab private class ActorTab
{ {
private ObjectManager.ActorData _data = new(string.Empty, string.Empty, Actor.Null, false, Actor.Null); private ObjectManager.ActorData _data = new(string.Empty, new Actor.Identifier(), Actor.Null, false, Actor.Null);
private CharacterSave _character = new();
private Actor _nextSelect = Actor.Null; private Actor _nextSelect = Actor.Null;
public void Draw() public void Draw()
@ -49,9 +38,12 @@ internal partial class Interface
private void DrawActorPanel() private void DrawActorPanel()
{ {
using var group = ImRaii.Group(); using var group = ImRaii.Group();
if (DrawCustomization(_character.Customize, _character.Equipment, !_data.Modifiable)) if (!Glamourer.RedrawManager.CurrentManipulations.GetSave(_data.Actor, out var save))
return;
if (DrawCustomization(save.Customization, save.Equipment, !_data.Modifiable))
{ {
Glamourer.RedrawManager.Set(_data.Actor.Address, _character); //Glamourer.RedrawManager.Set(_data.Actor.Address, _character);
Glamourer.Penumbra.RedrawObject(_data.Actor.Character, RedrawType.Redraw, true); Glamourer.Penumbra.RedrawObject(_data.Actor.Character, RedrawType.Redraw, true);
} }
@ -59,6 +51,11 @@ internal partial class Interface
{ {
Glamourer.RedrawManager.ChangeEquip(_data.Actor.Address, EquipSlot.Head, new CharacterArmor(265, 1, 0)); Glamourer.RedrawManager.ChangeEquip(_data.Actor.Address, EquipSlot.Head, new CharacterArmor(265, 1, 0));
} }
if (ImGui.Button("Set Weapon"))
{
Glamourer.RedrawManager.LoadWeapon(_data.Actor.Address, new CharacterWeapon(0x00C9, 0x004E, 0x0001, 0x00), new CharacterWeapon(0x0065, 0x003D, 0x0001, 0x00));
}
} }
private void DrawMonsterPanel() private void DrawMonsterPanel()
@ -115,7 +112,7 @@ internal partial class Interface
private void UpdateSelection(ObjectManager.ActorData data) private void UpdateSelection(ObjectManager.ActorData data)
{ {
_data = data; _data = data;
_character.Load(_data.Actor); //_character.Load(_data.Actor);
} }
private bool CheckFilter(ObjectManager.ActorData data) private bool CheckFilter(ObjectManager.ActorData data)

View file

@ -80,7 +80,7 @@ internal partial class Interface
return false; return false;
var gender = customize.Gender == Gender.Male ? Gender.Female : Gender.Male; var gender = customize.Gender == Gender.Male ? Gender.Female : Gender.Male;
return customize.ChangeGender(gender, locked ? CharacterEquip.Null : equip); return false; //customize.ChangeGender(gender, locked ? CharacterEquip.Null : equip);
} }
private static bool DrawRaceCombo(CharacterCustomization customize, CharacterEquip equip, bool locked) private static bool DrawRaceCombo(CharacterCustomization customize, CharacterEquip equip, bool locked)
@ -98,7 +98,7 @@ internal partial class Interface
foreach (var subRace in Enum.GetValues<SubRace>().Skip(1)) // Skip Unknown foreach (var subRace in Enum.GetValues<SubRace>().Skip(1)) // Skip Unknown
{ {
if (ImGui.Selectable(CustomizeExtensions.ClanName(subRace, customize.Gender), subRace == customize.Clan)) if (ImGui.Selectable(CustomizeExtensions.ClanName(subRace, customize.Gender), subRace == customize.Clan))
ret |= customize.ChangeRace(subRace, equip); ret |= false; //customize.ChangeRace(subRace, equip);
} }
return ret; return ret;

View file

@ -1,71 +1,10 @@
using System; using System.Collections.Generic;
using System.Collections.Generic;
using Dalamud.Game.ClientState.Objects.Enums; using Dalamud.Game.ClientState.Objects.Enums;
using Dalamud.Game.ClientState.Objects.Types; using Dalamud.Game.ClientState.Objects.Types;
using Penumbra.GameData.ByteString;
namespace Glamourer; namespace Glamourer;
public unsafe struct Actor : IEquatable<Actor>
{
public static readonly Actor Null = new() { Pointer = null };
public FFXIVClientStructs.FFXIV.Client.Game.Character.Character* Pointer;
public IntPtr Address
=> (IntPtr)Pointer;
public static implicit operator Actor(IntPtr? pointer)
=> new() { Pointer = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)pointer.GetValueOrDefault(IntPtr.Zero) };
public static implicit operator IntPtr(Actor actor)
=> actor.Pointer == null ? IntPtr.Zero : (IntPtr)actor.Pointer;
public Character? Character
=> Pointer == null ? null : Dalamud.Objects[Pointer->GameObject.ObjectIndex] as Character;
public bool IsAvailable
=> Pointer->GameObject.GetIsTargetable();
public bool IsHuman
=> Pointer != null && Pointer->ModelCharaId == 0;
public int ModelId
=> Pointer != null ? Pointer->ModelCharaId : 0;
public void SetModelId(int value)
{
if (Pointer != null)
Pointer->ModelCharaId = value;
}
public static implicit operator bool(Actor actor)
=> actor.Pointer != null;
public static bool operator true(Actor actor)
=> actor.Pointer != null;
public static bool operator false(Actor actor)
=> actor.Pointer == null;
public static bool operator !(Actor actor)
=> actor.Pointer == null;
public bool Equals(Actor other)
=> Pointer == other.Pointer;
public override bool Equals(object? obj)
=> obj is Actor other && Equals(other);
public override int GetHashCode()
=> ((ulong)Pointer).GetHashCode();
public static bool operator ==(Actor lhs, Actor rhs)
=> lhs.Pointer == rhs.Pointer;
public static bool operator !=(Actor lhs, Actor rhs)
=> lhs.Pointer != rhs.Pointer;
}
public static class ObjectManager public static class ObjectManager
{ {
private const int GPosePlayerIndex = 201; private const int GPosePlayerIndex = 201;
@ -73,8 +12,9 @@ public static class ObjectManager
private const int ExamineScreenIndex = 241; private const int ExamineScreenIndex = 241;
private const int FittingRoomIndex = 242; private const int FittingRoomIndex = 242;
private const int DyePreviewIndex = 243; private const int DyePreviewIndex = 243;
private static readonly Dictionary<string, int> _nameCounters = new();
private static readonly Dictionary<string, Actor> _gPoseActors = new(CharacterScreenIndex - GPosePlayerIndex); private static readonly Dictionary<Utf8String, int> NameCounters = new();
private static readonly Dictionary<Actor.Identifier, Actor> GPoseActors = new(CharacterScreenIndex - GPosePlayerIndex);
public static bool IsInGPose() public static bool IsInGPose()
=> Dalamud.Objects[GPosePlayerIndex] != null; => Dalamud.Objects[GPosePlayerIndex] != null;
@ -85,88 +25,88 @@ public static class ObjectManager
public static Actor Player public static Actor Player
=> Dalamud.ClientState.LocalPlayer?.Address; => Dalamud.ClientState.LocalPlayer?.Address;
public record struct ActorData(string Label, string Name, Actor Actor, bool Modifiable, Actor GPose); public record struct ActorData(string Label, Actor.Identifier Identifier, Actor Actor, bool Modifiable, Actor GPose);
public static IEnumerable<ActorData> GetEnumerator() public static IEnumerable<ActorData> GetEnumerator()
{ {
_nameCounters.Clear(); NameCounters.Clear();
_gPoseActors.Clear(); GPoseActors.Clear();
for (var i = GPosePlayerIndex; i < CharacterScreenIndex; ++i) for (var i = GPosePlayerIndex; i < CharacterScreenIndex; ++i)
{ {
var character = Dalamud.Objects[i]; Actor character = Dalamud.Objects[i]?.Address;
if (character == null) if (!character)
break; break;
var name = character.Name.TextValue; var identifier = character.GetIdentifier();
_gPoseActors[name] = character.Address; GPoseActors[identifier] = character.Address;
yield return new ActorData(GetLabel(character, name, 0, true), name, character.Address, true, Actor.Null); yield return new ActorData(GetLabel(character, identifier.Name.ToString(), 0, true), identifier, character.Address, true,
Actor.Null);
} }
var actor = Dalamud.Objects[CharacterScreenIndex]; Actor actor = Dalamud.Objects[CharacterScreenIndex]?.Address;
if (actor != null) if (actor)
yield return new ActorData("Character Screen Actor", string.Empty, actor.Address, false, Actor.Null); yield return new ActorData("Character Screen Actor", actor.GetIdentifier(), actor.Address, false, Actor.Null);
actor = Dalamud.Objects[ExamineScreenIndex]; actor = Dalamud.Objects[ExamineScreenIndex]?.Address;
if (actor != null) if (actor)
yield return new ActorData("Examine Screen Actor", string.Empty, actor.Address, false, Actor.Null); yield return new ActorData("Examine Screen Actor", actor.GetIdentifier(), actor.Address, false, Actor.Null);
actor = Dalamud.Objects[FittingRoomIndex]; actor = Dalamud.Objects[FittingRoomIndex]?.Address;
if (actor != null) if (actor)
yield return new ActorData("Fitting Room Actor", string.Empty, actor.Address, false, Actor.Null); yield return new ActorData("Fitting Room Actor", actor.GetIdentifier(), actor.Address, false, Actor.Null);
actor = Dalamud.Objects[DyePreviewIndex]; actor = Dalamud.Objects[DyePreviewIndex]?.Address;
if (actor != null) if (actor)
yield return new ActorData("Dye Preview Actor", string.Empty, actor.Address, false, Actor.Null); yield return new ActorData("Dye Preview Actor", actor.GetIdentifier(), actor.Address, false, Actor.Null);
for (var i = 0; i < GPosePlayerIndex; ++i) for (var i = 0; i < GPosePlayerIndex; ++i)
{ {
var character = Dalamud.Objects[i]; actor = Dalamud.Objects[i]?.Address;
if (character == null if (!actor
|| character.ObjectKind is not (ObjectKind.Player or ObjectKind.BattleNpc or ObjectKind.EventNpc or ObjectKind.Companion || actor.ObjectKind is not (ObjectKind.Player or ObjectKind.BattleNpc or ObjectKind.EventNpc or ObjectKind.Companion
or ObjectKind.Retainer)) or ObjectKind.Retainer))
continue; continue;
var name = character.Name.TextValue; var identifier = actor.GetIdentifier();
if (name.Length == 0) if (identifier.Name.Length == 0)
continue; continue;
if (_nameCounters.TryGetValue(name, out var num)) if (NameCounters.TryGetValue(identifier.Name, out var num))
_nameCounters[name] = ++num; NameCounters[identifier.Name] = ++num;
else else
_nameCounters[name] = num = 1; NameCounters[identifier.Name] = num = 1;
if (!_gPoseActors.TryGetValue(name, out var gPose)) if (!GPoseActors.TryGetValue(identifier, out var gPose))
gPose = Actor.Null; gPose = Actor.Null;
yield return new ActorData(GetLabel(character, name, num, false), name, character.Address, true, gPose); yield return new ActorData(GetLabel(actor, identifier.Name.ToString(), num, false), identifier, actor.Address, true, gPose);
} }
for (var i = DyePreviewIndex + 1; i < Dalamud.Objects.Length; ++i) for (var i = DyePreviewIndex + 1; i < Dalamud.Objects.Length; ++i)
{ {
var character = Dalamud.Objects[i]; actor = Dalamud.Objects[i]?.Address;
if (character == null if (!actor
|| !((Actor)character.Address).IsAvailable || actor.ObjectKind is not (ObjectKind.Player or ObjectKind.BattleNpc or ObjectKind.EventNpc or ObjectKind.Companion
|| character.ObjectKind is not (ObjectKind.Player or ObjectKind.BattleNpc or ObjectKind.EventNpc or ObjectKind.Companion
or ObjectKind.Retainer)) or ObjectKind.Retainer))
continue; continue;
var name = character.Name.TextValue; var identifier = actor.GetIdentifier();
if (name.Length == 0) if (identifier.Name.Length == 0)
continue; continue;
if (_nameCounters.TryGetValue(name, out var num)) if (NameCounters.TryGetValue(identifier.Name, out var num))
_nameCounters[name] = ++num; NameCounters[identifier.Name] = ++num;
else else
_nameCounters[name] = num = 1; NameCounters[identifier.Name] = num = 1;
if (!_gPoseActors.TryGetValue(name, out var gPose)) if (!GPoseActors.TryGetValue(identifier, out var gPose))
gPose = Actor.Null; gPose = Actor.Null;
yield return new ActorData(GetLabel(character, name, num, false), name, character.Address, true, gPose); yield return new ActorData(GetLabel(actor, identifier.Name.ToString(), num, false), identifier, actor.Address, true, gPose);
} }
} }
private static unsafe string GetLabel(GameObject player, string playerName, int num, bool gPose) private static unsafe string GetLabel(Actor player, string playerName, int num, bool gPose)
{ {
if (player.ObjectKind == ObjectKind.Player) if (player.ObjectKind == ObjectKind.Player)
return gPose ? $"{playerName} (GPose)" : num == 1 ? playerName : $"{playerName} #{num}"; return gPose ? $"{playerName} (GPose)" : num == 1 ? playerName : $"{playerName} #{num}";

View file

@ -1,211 +1,283 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Diagnostics.CodeAnalysis;
using Dalamud.Hooking; using Dalamud.Hooking;
using Dalamud.Logging;
using Dalamud.Utility.Signatures; using Dalamud.Utility.Signatures;
using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Glamourer.Customization; using Glamourer.Customization;
using Penumbra.GameData.ByteString;
using Penumbra.GameData.Enums; using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs; using Penumbra.GameData.Structs;
namespace Glamourer; namespace Glamourer;
public unsafe class RedrawManager : IDisposable public class CurrentManipulations
{
private readonly RestrictedGear _restrictedGear = GameData.RestrictedGear(Dalamud.GameData);
private readonly Dictionary<Actor.Identifier, CharacterSave> _characterSaves = new();
public CharacterSave CreateSave(Actor actor)
{
var id = actor.GetIdentifier();
if (_characterSaves.TryGetValue(actor.GetIdentifier(), out var save))
return save;
save = new CharacterSave(actor);
_characterSaves.Add(id with { Name = id.Name.Clone() }, save);
return save;
}
public bool GetSave(Actor actor, [NotNullWhen(true)] out CharacterSave? save)
{
save = null;
return actor && _characterSaves.TryGetValue(actor.GetIdentifier(), out save);
}
public bool GetSave(Actor.Identifier identifier, [NotNullWhen(true)] out CharacterSave? save)
=> _characterSaves.TryGetValue(identifier, out save);
public CharacterArmor? ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data)
{
var save = CreateSave(actor);
(_, data) = _restrictedGear.ResolveRestricted(data, slot, save.Customization.Race, save.Customization.Gender);
if (save.Equipment[slot] == data)
return null;
save.Equipment[slot] = data;
return data;
}
public bool ChangeWeapon(Actor actor, CharacterWeapon main)
{
var save = CreateSave(actor);
if (save.MainHand == main)
return false;
save.MainHand = main;
return true;
}
public bool ChangeWeapon(Actor actor, CharacterWeapon main, CharacterWeapon off)
{
var save = CreateSave(actor);
if (main == save.MainHand && off == save.OffHand)
return false;
save.MainHand = main;
save.OffHand = off;
return true;
}
public void ChangeCustomization(Actor actor, CharacterCustomization customize)
{
var save = CreateSave(actor);
FixRestrictedGear(save, customize.Gender, customize.Race);
save.Customization.Load(customize);
}
public bool ChangeCustomization(Actor actor, CustomizationId id, byte value)
{
if (id == CustomizationId.Race)
return ChangeRace(actor, (SubRace)value);
if (id == CustomizationId.Gender)
return ChangeGender(actor, (Gender)value);
var save = CreateSave(actor);
var customize = save.Customization;
if (customize[id] != value)
return false;
customize[id] = value;
return true;
}
// Change a gender and fix up all required customizations afterwards.
public bool ChangeGender(Actor actor, Gender gender)
{
var save = CreateSave(actor);
if (save.Customization.Gender == gender)
return false;
var customize = save.Customization;
FixRestrictedGear(save, gender, customize.Race);
FixUpAttributes(customize);
return true;
}
// Change a race and fix up all required customizations afterwards.
public bool ChangeRace(Actor actor, SubRace clan)
{
var save = CreateSave(actor);
if (save.Customization.Clan == clan)
return false;
var customize = save.Customization;
var race = clan.ToRace();
var gender = race == Race.Hrothgar ? Gender.Male : customize.Gender; // TODO Female Hrothgar
FixRestrictedGear(save, gender, race);
customize.Gender = gender;
customize.Race = race;
customize.Clan = clan;
FixUpAttributes(customize);
return true;
}
// Go through a whole customization struct and fix up all settings that need fixing.
private void FixUpAttributes(CharacterCustomization customize)
{
var set = Glamourer.Customization.GetList(customize.Clan, customize.Gender);
foreach (CustomizationId id in Enum.GetValues(typeof(CustomizationId)))
{
switch (id)
{
case CustomizationId.Race: break;
case CustomizationId.Clan: break;
case CustomizationId.BodyType: break;
case CustomizationId.Gender: break;
case CustomizationId.FacialFeaturesTattoos: break;
case CustomizationId.HighlightsOnFlag: break;
case CustomizationId.Face: break;
default:
var count = set.Count(id);
if (set.DataByValue(id, customize[id], out _) < 0)
customize[id] = count == 0 ? (byte)0 : set.Data(id, 0).Value;
break;
}
}
}
private void FixRestrictedGear(CharacterSave save, Gender gender, Race race)
{
if (race == save.Customization.Race && gender == save.Customization.Gender)
return;
var equip = save.Equipment;
foreach (var slot in EquipSlotExtensions.EqdpSlots)
(_, equip[slot]) = _restrictedGear.ResolveRestricted(equip[slot], slot, race, gender);
}
}
public unsafe partial class RedrawManager
{ {
public delegate ulong FlagSlotForUpdateDelegate(Human* drawObject, uint slot, CharacterArmor* data); public delegate ulong FlagSlotForUpdateDelegate(Human* drawObject, uint slot, CharacterArmor* data);
[Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A")] // This gets called when one of the ten equip items of an existing draw object gets changed.
public Hook<FlagSlotForUpdateDelegate> FlagSlotForUpdateHook = null!; [Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A", DetourName = nameof(FlagSlotForUpdateDetour))]
private readonly Hook<FlagSlotForUpdateDelegate>? _flagSlotForUpdateHook;
private ulong FlagSlotForUpdateDetour(Human* drawObject, uint slot, CharacterArmor* data) private ulong FlagSlotForUpdateDetour(Human* drawObject, uint slot, CharacterArmor* data)
{ {
return FlagSlotForUpdateHook.Original(drawObject, slot, data); //try
//{
// var actor = Glamourer.Penumbra.GameObjectFromDrawObject((IntPtr)drawObject);
// if (actor && CurrentManipulations.GetSave(actor, out _))
// // TODO fixed design
//
// *data = CurrentManipulations.ChangeEquip(actor, slot.ToEquipSlot(), *data) ?? *data;
//}
//catch
//{
// // ignored
//}
return _flagSlotForUpdateHook!.Original(drawObject, slot, data);
} }
public delegate void LoadWeaponDelegate(IntPtr characterOffset, uint slot, ulong data, byte unk); public bool ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data)
[Signature("E8 ?? ?? ?? ?? 44 8B 9F")]
public Hook<LoadWeaponDelegate> LoadWeaponHook = null!;
private void LoadWeaponDetour(IntPtr characterOffset, uint slot, ulong data, byte unk)
{ {
const int offset = 0xD8 * 8; if (actor && CurrentManipulations.ChangeEquip(actor, slot, data).HasValue && actor.DrawObject != null)
PluginLog.Information($"0x{characterOffset:X}, 0x{characterOffset - offset:X}, {slot}, {data:16X}, {unk}"); return _flagSlotForUpdateHook?.Original(actor.DrawObject, slot.ToIndex(), &data) != 0;
LoadWeaponHook.Original(characterOffset, slot, data, unk);
return false;
}
} }
//[Signature("48 89 5C 24 ?? 48 89 74 24 ?? 57 48 83 EC 20 8B DA 49 8B F0 48 8B F9 83 FA 0A", public unsafe partial class RedrawManager
// DetourName = nameof(FlagSlotForUpdateDetour))] {
//public Hook<FlagSlotForUpdateDelegate>? FlagSlotForUpdateHook; // The character weapon object manipulated is inside the actual character.
// public const int CharacterWeaponOffset = 0xD8 * 8;
// public readonly FixedDesigns FixedDesigns;
//
private readonly Dictionary<string, CharacterSave> _currentRedraws = new(32); public delegate void LoadWeaponDelegate(IntPtr offsetCharacter, uint slot, CharacterWeapon weapon, byte unk1, byte unk2, byte unk3,
byte unk4);
// Weapons for a specific character are reloaded with this function.
// The first argument is a pointer to the game object but shifted a bit inside.
// slot is 0 for main hand, 1 for offhand, 2 for unknown (always called with empty data.
// weapon argument is the new weapon data.
// unk1 seems to be 0 when re-equipping and 1 when redrawing the entire actor.
// unk2 seemed to always be 1.
// unk3 seemed to always be 0.
// unk4 seemed to be the same as unk1.
[Signature("E8 ?? ?? ?? ?? 44 8B 9F", DetourName = nameof(LoadWeaponDetour))]
private readonly Hook<LoadWeaponDelegate>? _loadWeaponHook;
private void LoadWeaponDetour(IntPtr characterOffset, uint slot, CharacterWeapon weapon, byte unk1, byte unk2, byte unk3, byte unk4)
{
_loadWeaponHook!.Original(characterOffset, slot, weapon, unk1, unk2, unk3, unk4);
}
// Load a specific weapon for a character by its data and slot.
public void LoadWeapon(IntPtr character, EquipSlot slot, CharacterWeapon weapon)
{
switch (slot)
{
case EquipSlot.MainHand:
LoadWeaponDetour(character + CharacterWeaponOffset, 0, weapon, 1, 1, 0, 1);
return;
case EquipSlot.OffHand:
LoadWeaponDetour(character + CharacterWeaponOffset, 1, weapon, 1, 1, 0, 1);
return;
case EquipSlot.BothHand:
LoadWeaponDetour(character + CharacterWeaponOffset, 0, weapon, 1, 1, 0, 1);
LoadWeaponDetour(character + CharacterWeaponOffset, 1, CharacterWeapon.Empty, 1, 1, 0, 1);
return;
// function can also be called with '2', but does not seem to ever be.
}
}
public void LoadWeapon(Character* character, EquipSlot slot, CharacterWeapon weapon)
=> LoadWeapon((IntPtr)character, slot, weapon);
// Load specific Main- and Offhand weapons.
public void LoadWeapon(IntPtr character, CharacterWeapon main, CharacterWeapon off)
{
LoadWeaponDetour(character + CharacterWeaponOffset, 0, main, 1, 1, 0, 1);
LoadWeaponDetour(character + CharacterWeaponOffset, 1, off, 1, 1, 0, 1);
}
public void LoadWeapon(Character* character, CharacterWeapon main, CharacterWeapon off)
=> LoadWeapon((IntPtr)character, main, off);
}
public unsafe partial class RedrawManager : IDisposable
{
internal readonly CurrentManipulations CurrentManipulations = new();
public RedrawManager() public RedrawManager()
{ {
SignatureHelper.Initialise(this); SignatureHelper.Initialise(this);
// FixedDesigns = new FixedDesigns(designs);
Glamourer.Penumbra.CreatingCharacterBase += OnCharacterRedraw; Glamourer.Penumbra.CreatingCharacterBase += OnCharacterRedraw;
FlagSlotForUpdateHook.Enable(); //_flagSlotForUpdateHook?.Enable();
LoadWeaponHook.Enable(); //_loadWeaponHook?.Enable();
//
// if (Glamourer.Config.ApplyFixedDesigns)
// Enable();
} }
public void Dispose() public void Dispose()
{ {
FlagSlotForUpdateHook.Dispose(); _flagSlotForUpdateHook?.Dispose();
LoadWeaponHook.Dispose(); _loadWeaponHook?.Dispose();
Glamourer.Penumbra.CreatingCharacterBase -= OnCharacterRedraw; Glamourer.Penumbra.CreatingCharacterBase -= OnCharacterRedraw;
//FlagSlotForUpdateHook?.Dispose();
} }
public void Set(Character* actor, CharacterSave save)
{
var name = GetName(actor);
if (name.Length == 0)
return;
_currentRedraws[name] = save;
}
public void Set(IntPtr actor, CharacterSave save)
=> Set((Character*)actor, save);
public void Revert(Character* actor)
=> _currentRedraws.Remove(GetName(actor));
public void Revert(IntPtr actor)
=> Revert((Character*)actor);
private static string GetName(Character* actor)
{
return string.Concat(new Utf8String(actor->GameObject.Name)
.Select(c => (char)c)
.Append(actor->GameObject.ObjectKind == (byte)ObjectKind.Pc ? (char)actor->HomeWorld : (char)actor->GameObject.ObjectIndex));
}
private void Cleanup(object? _, ushort _1)
=> _currentRedraws.Clear();
public void ChangeEquip(Human* actor, EquipSlot slot, CharacterArmor item)
=> Flag(actor, slot.ToIndex(), &item);
public void ChangeEquip(Character* character, EquipSlot slot, CharacterArmor item)
=> ChangeEquip((Human*)character->GameObject.DrawObject, slot, item);
public void ChangeEquip(IntPtr character, EquipSlot slot, CharacterArmor item)
=> ChangeEquip((Character*)character, slot, item);
private void OnCharacterRedraw(IntPtr addr, IntPtr modelId, IntPtr customize, IntPtr equipData) private void OnCharacterRedraw(IntPtr addr, IntPtr modelId, IntPtr customize, IntPtr equipData)
{ {
var name = GetName((Character*)addr); //if (CurrentManipulations.GetSave(addr, out var save))
if (_currentRedraws.TryGetValue(name, out var save))
{
*(CustomizationData*)customize = *(CustomizationData*)save.Customize.Address;
var equip = (CharacterEquip)equipData;
var newEquip = save.Equipment;
for (var i = 0; i < 10; ++i)
equip[i] = newEquip[i];
}
//*(uint*)modelId = 0;
//var human = (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)addr;
//if (human->GameObject.ObjectKind is (byte)ObjectKind.EventNpc or (byte)ObjectKind.BattleNpc or (byte)ObjectKind.Player
// && human->ModelCharaId == 0)
//{ //{
// var name = new Utf8String(human->GameObject.Name).ToString(); // *(CustomizationData*)customize = *(CustomizationData*)save.Customization.Address;
// if (FixedDesigns.EnabledDesigns.TryGetValue(name, out var designs)) // var equip = (CharacterEquip)equipData;
// { // var newEquip = save.Equipment;
// var design = designs.OrderBy(d => d.Jobs.Count).FirstOrDefault(d => d.Jobs.Fits(human->ClassJob)); // for (var i = 0; i < 10; ++i)
// if (design != null) // equip[i] = newEquip[i];
// {
// if (design.Design.Data.WriteCustomizations)
// *(CharacterCustomization*)customize = design.Design.Data.Customizations;
//
// var data = (uint*)equipData;
// for (var i = 0u; i < 10; ++i)
// {
// var slot = i.ToEquipSlot();
// if (design.Design.Data.WriteEquipment.Fits(slot))
// data[i] = slot switch
// {
// EquipSlot.Head => design.Design.Data.Equipment.Head.Value,
// EquipSlot.Body => design.Design.Data.Equipment.Body.Value,
// EquipSlot.Hands => design.Design.Data.Equipment.Hands.Value,
// EquipSlot.Legs => design.Design.Data.Equipment.Legs.Value,
// EquipSlot.Feet => design.Design.Data.Equipment.Feet.Value,
// EquipSlot.Ears => design.Design.Data.Equipment.Ears.Value,
// EquipSlot.Neck => design.Design.Data.Equipment.Neck.Value,
// EquipSlot.Wrists => design.Design.Data.Equipment.Wrists.Value,
// EquipSlot.RFinger => design.Design.Data.Equipment.RFinger.Value,
// EquipSlot.LFinger => design.Design.Data.Equipment.LFinger.Value,
// _ => 0,
// };
// }
// }
// }
//} //}
} }
//
// private ulong FlagSlotForUpdateDetour(Human* drawObject, uint slotIdx, uint* data)
// {
// ulong ret;
// var slot = slotIdx.ToEquipSlot();
// try
// {
// if (slot != EquipSlot.Unknown)
// {
// var gameObject =
// (FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)Glamourer.Penumbra.GameObjectFromDrawObject((IntPtr)drawObject);
// if (gameObject != null)
// {
// var name = new Utf8String(gameObject->GameObject.Name).ToString();
// if (FixedDesigns.EnabledDesigns.TryGetValue(name, out var designs))
// {
// var design = designs.OrderBy(d => d.Jobs.Count).FirstOrDefault(d => d.Jobs.Fits(gameObject->ClassJob));
// if (design != null && design.Design.Data.WriteEquipment.Fits(slot))
// *data = slot switch
// {
// EquipSlot.Head => design.Design.Data.Equipment.Head.Value,
// EquipSlot.Body => design.Design.Data.Equipment.Body.Value,
// EquipSlot.Hands => design.Design.Data.Equipment.Hands.Value,
// EquipSlot.Legs => design.Design.Data.Equipment.Legs.Value,
// EquipSlot.Feet => design.Design.Data.Equipment.Feet.Value,
// EquipSlot.Ears => design.Design.Data.Equipment.Ears.Value,
// EquipSlot.Neck => design.Design.Data.Equipment.Neck.Value,
// EquipSlot.Wrists => design.Design.Data.Equipment.Wrists.Value,
// EquipSlot.RFinger => design.Design.Data.Equipment.RFinger.Value,
// EquipSlot.LFinger => design.Design.Data.Equipment.LFinger.Value,
// _ => 0,
// };
// }
// }
// }
// }
// finally
// {
// ret = FlagSlotForUpdateHook!.Original(drawObject, slotIdx, data);
// }
//
// return ret;
// }
//
// public void UpdateSlot(Human* drawObject, EquipSlot slot, CharacterArmor data)
// {
// var idx = slot.ToIndex();
// if (idx >= 10)
// return;
//
// FlagSlotForUpdateDetour(drawObject, idx, (uint*)&data);
// }
} }