mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +01:00
Shit's fucked, yo
This commit is contained in:
parent
7af38aa2ce
commit
a160276cc5
12 changed files with 507 additions and 429 deletions
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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
87
Glamourer/Actor.cs
Normal 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;
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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) };
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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}";
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public unsafe partial class RedrawManager
|
||||||
|
{
|
||||||
|
// The character weapon object manipulated is inside the actual character.
|
||||||
|
public const int CharacterWeaponOffset = 0xD8 * 8;
|
||||||
|
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
//[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",
|
// Load a specific weapon for a character by its data and slot.
|
||||||
// DetourName = nameof(FlagSlotForUpdateDetour))]
|
public void LoadWeapon(IntPtr character, EquipSlot slot, CharacterWeapon weapon)
|
||||||
//public Hook<FlagSlotForUpdateDelegate>? FlagSlotForUpdateHook;
|
{
|
||||||
//
|
switch (slot)
|
||||||
// public readonly FixedDesigns FixedDesigns;
|
{
|
||||||
//
|
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.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly Dictionary<string, CharacterSave> _currentRedraws = new(32);
|
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);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue