mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 10:17:23 +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)
|
||||
=> _data = data;
|
||||
|
||||
public void Load(CharacterCustomization other)
|
||||
=> *_data = *other._data;
|
||||
|
||||
public ref Race Race
|
||||
=> ref _data->Race;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using Dalamud;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Glamourer.Structs;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ using Dalamud.Logging;
|
|||
using Dalamud.Utility;
|
||||
using Lumina.Excel;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Race = Penumbra.GameData.Enums.Race;
|
||||
|
|
@ -20,9 +21,9 @@ public class RestrictedGear
|
|||
private readonly ExcelSheet<Item> _items;
|
||||
private readonly ExcelSheet<EquipRaceCategory> _categories;
|
||||
|
||||
private readonly HashSet<uint> _raceGenderSet = RaceGenderGroup.Where(c => c != 0).ToHashSet();
|
||||
private readonly Dictionary<uint, uint> _maleToFemale = new();
|
||||
private readonly Dictionary<uint, uint> _femaleToMale = new();
|
||||
private readonly HashSet<uint> _raceGenderSet = RaceGenderGroup.Where(c => c != 0).ToHashSet();
|
||||
private readonly Dictionary<uint, uint> _maleToFemale = new();
|
||||
private readonly Dictionary<uint, uint> _femaleToMale = new();
|
||||
|
||||
internal RestrictedGear(DataManager gameData)
|
||||
{
|
||||
|
|
@ -33,16 +34,15 @@ public class RestrictedGear
|
|||
}
|
||||
|
||||
// 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,
|
||||
Gender gender)
|
||||
public (bool Replaced, CharacterArmor Armor) ResolveRestricted(CharacterArmor armor, EquipSlot slot, Race race, 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.
|
||||
if (RaceGenderGroup.Contains(quad))
|
||||
{
|
||||
var idx = ((int)race - 1) * 2 + (gender is Gender.Female or Gender.FemaleNpc ? 1 : 0);
|
||||
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,
|
||||
|
|
@ -52,10 +52,10 @@ public class RestrictedGear
|
|||
var needle = quad | ((uint)slot.ToSlot() << 24);
|
||||
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))
|
||||
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.
|
||||
return (false, modelId, variant);
|
||||
return (false, armor);
|
||||
}
|
||||
|
||||
// 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)
|
||||
=> (FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)(_drawObjectInfo?.InvokeFunc(drawObject).Item1 ?? IntPtr.Zero);
|
||||
public Actor GameObjectFromDrawObject(IntPtr drawObject)
|
||||
=> _drawObjectInfo?.InvokeFunc(drawObject).Item1 ?? IntPtr.Zero;
|
||||
|
||||
public void RedrawObject(GameObject? actor, RedrawType settings, bool repeat)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -13,13 +13,13 @@ namespace Glamourer;
|
|||
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var token = JToken.Load(reader);
|
||||
|
|
@ -28,9 +28,8 @@ public class CharacterSaveConverter : JsonConverter<CharacterSave>
|
|||
}
|
||||
}
|
||||
|
||||
[JsonConverter(typeof(CharacterSaveConverter))]
|
||||
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
||||
public unsafe struct CharacterSave
|
||||
public unsafe struct CharacterData
|
||||
{
|
||||
[Flags]
|
||||
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 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 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 byte Version;
|
||||
public SaveFlags Flags;
|
||||
public CharacterEquipMask Equip;
|
||||
public CharacterWeapon MainHand;
|
||||
public CharacterWeapon OffHand;
|
||||
public ushort Padding;
|
||||
public CharacterArmor Head;
|
||||
public CharacterArmor Body;
|
||||
public CharacterArmor Hands;
|
||||
public CharacterArmor Legs;
|
||||
public CharacterArmor Feet;
|
||||
public CharacterArmor Ears;
|
||||
public CharacterArmor Neck;
|
||||
public CharacterArmor Wrist;
|
||||
public CharacterArmor RFinger;
|
||||
public CharacterArmor LFinger;
|
||||
private CustomizationData CustomizationData;
|
||||
public float Alpha;
|
||||
|
||||
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()
|
||||
{
|
||||
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}].");
|
||||
}
|
||||
|
||||
public static CharacterSave FromString(string data)
|
||||
public static CharacterData FromString(string data)
|
||||
{
|
||||
var bytes = Convert.FromBase64String(data);
|
||||
var ret = new CharacterSave();
|
||||
var ret = new CharacterData();
|
||||
fixed (byte* ptr = bytes)
|
||||
{
|
||||
switch (bytes[0])
|
||||
|
|
@ -156,8 +176,8 @@ public unsafe struct CharacterSave
|
|||
ret.Flags |= SaveFlags.VisorState;
|
||||
break;
|
||||
case 3:
|
||||
CheckSize(bytes.Length, sizeof(CharacterSave));
|
||||
Functions.MemCpyUnchecked(&ret, ptr, sizeof(CharacterSave));
|
||||
CheckSize(bytes.Length, TotalSizeVersion3);
|
||||
Functions.MemCpyUnchecked(&ret, ptr, TotalSizeVersion3);
|
||||
break;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
[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)
|
||||
=> 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 RedrawManager RedrawManager = null!;
|
||||
public static RestrictedGear RestrictedGear = null!;
|
||||
private readonly WindowSystem _windowSystem = new("Glamourer");
|
||||
|
||||
private readonly Interface _interface;
|
||||
|
|
@ -61,8 +60,6 @@ public class Glamourer : IDalamudPlugin
|
|||
{
|
||||
Dalamud.Initialize(pluginInterface);
|
||||
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();
|
||||
Penumbra = new PenumbraAttach(Config.AttachToPenumbra);
|
||||
|
||||
|
|
@ -83,7 +80,6 @@ public class Glamourer : IDalamudPlugin
|
|||
_interface = new Interface(this);
|
||||
_windowSystem.AddWindow(_interface);
|
||||
Dalamud.PluginInterface.UiBuilder.Draw += _windowSystem.Draw;
|
||||
var x = 0x00011000u;
|
||||
//FixedDesignManager.Flag((Human*)((Actor)Dalamud.ClientState.LocalPlayer?.Address).Pointer->GameObject.DrawObject, 0, &x);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,6 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
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.Logging;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Designs;
|
||||
using Glamourer.Structs;
|
||||
using ImGuiNET;
|
||||
using OtterGui;
|
||||
using OtterGui.Classes;
|
||||
|
|
@ -24,8 +14,7 @@ internal partial class Interface
|
|||
{
|
||||
private class ActorTab
|
||||
{
|
||||
private ObjectManager.ActorData _data = new(string.Empty, string.Empty, Actor.Null, false, Actor.Null);
|
||||
private CharacterSave _character = new();
|
||||
private ObjectManager.ActorData _data = new(string.Empty, new Actor.Identifier(), Actor.Null, false, Actor.Null);
|
||||
private Actor _nextSelect = Actor.Null;
|
||||
|
||||
public void Draw()
|
||||
|
|
@ -49,9 +38,12 @@ internal partial class Interface
|
|||
private void DrawActorPanel()
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
|
|
@ -59,6 +51,11 @@ internal partial class Interface
|
|||
{
|
||||
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()
|
||||
|
|
@ -115,7 +112,7 @@ internal partial class Interface
|
|||
private void UpdateSelection(ObjectManager.ActorData data)
|
||||
{
|
||||
_data = data;
|
||||
_character.Load(_data.Actor);
|
||||
//_character.Load(_data.Actor);
|
||||
}
|
||||
|
||||
private bool CheckFilter(ObjectManager.ActorData data)
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ internal partial class Interface
|
|||
return false;
|
||||
|
||||
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)
|
||||
|
|
@ -98,7 +98,7 @@ internal partial class Interface
|
|||
foreach (var subRace in Enum.GetValues<SubRace>().Skip(1)) // Skip Unknown
|
||||
{
|
||||
if (ImGui.Selectable(CustomizeExtensions.ClanName(subRace, customize.Gender), subRace == customize.Clan))
|
||||
ret |= customize.ChangeRace(subRace, equip);
|
||||
ret |= false; //customize.ChangeRace(subRace, equip);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -1,80 +1,20 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Penumbra.GameData.ByteString;
|
||||
|
||||
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
|
||||
{
|
||||
private const int GPosePlayerIndex = 201;
|
||||
private const int CharacterScreenIndex = 240;
|
||||
private const int ExamineScreenIndex = 241;
|
||||
private const int FittingRoomIndex = 242;
|
||||
private const int DyePreviewIndex = 243;
|
||||
private static readonly Dictionary<string, int> _nameCounters = new();
|
||||
private static readonly Dictionary<string, Actor> _gPoseActors = new(CharacterScreenIndex - GPosePlayerIndex);
|
||||
private const int GPosePlayerIndex = 201;
|
||||
private const int CharacterScreenIndex = 240;
|
||||
private const int ExamineScreenIndex = 241;
|
||||
private const int FittingRoomIndex = 242;
|
||||
private const int DyePreviewIndex = 243;
|
||||
|
||||
private static readonly Dictionary<Utf8String, int> NameCounters = new();
|
||||
private static readonly Dictionary<Actor.Identifier, Actor> GPoseActors = new(CharacterScreenIndex - GPosePlayerIndex);
|
||||
|
||||
public static bool IsInGPose()
|
||||
=> Dalamud.Objects[GPosePlayerIndex] != null;
|
||||
|
|
@ -85,88 +25,88 @@ public static class ObjectManager
|
|||
public static Actor Player
|
||||
=> 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()
|
||||
{
|
||||
_nameCounters.Clear();
|
||||
_gPoseActors.Clear();
|
||||
NameCounters.Clear();
|
||||
GPoseActors.Clear();
|
||||
for (var i = GPosePlayerIndex; i < CharacterScreenIndex; ++i)
|
||||
{
|
||||
var character = Dalamud.Objects[i];
|
||||
if (character == null)
|
||||
Actor character = Dalamud.Objects[i]?.Address;
|
||||
if (!character)
|
||||
break;
|
||||
|
||||
var name = character.Name.TextValue;
|
||||
_gPoseActors[name] = character.Address;
|
||||
yield return new ActorData(GetLabel(character, name, 0, true), name, character.Address, true, Actor.Null);
|
||||
var identifier = character.GetIdentifier();
|
||||
GPoseActors[identifier] = character.Address;
|
||||
yield return new ActorData(GetLabel(character, identifier.Name.ToString(), 0, true), identifier, character.Address, true,
|
||||
Actor.Null);
|
||||
}
|
||||
|
||||
var actor = Dalamud.Objects[CharacterScreenIndex];
|
||||
if (actor != null)
|
||||
yield return new ActorData("Character Screen Actor", string.Empty, actor.Address, false, Actor.Null);
|
||||
Actor actor = Dalamud.Objects[CharacterScreenIndex]?.Address;
|
||||
if (actor)
|
||||
yield return new ActorData("Character Screen Actor", actor.GetIdentifier(), actor.Address, false, Actor.Null);
|
||||
|
||||
actor = Dalamud.Objects[ExamineScreenIndex];
|
||||
if (actor != null)
|
||||
yield return new ActorData("Examine Screen Actor", string.Empty, actor.Address, false, Actor.Null);
|
||||
actor = Dalamud.Objects[ExamineScreenIndex]?.Address;
|
||||
if (actor)
|
||||
yield return new ActorData("Examine Screen Actor", actor.GetIdentifier(), actor.Address, false, Actor.Null);
|
||||
|
||||
actor = Dalamud.Objects[FittingRoomIndex];
|
||||
if (actor != null)
|
||||
yield return new ActorData("Fitting Room Actor", string.Empty, actor.Address, false, Actor.Null);
|
||||
actor = Dalamud.Objects[FittingRoomIndex]?.Address;
|
||||
if (actor)
|
||||
yield return new ActorData("Fitting Room Actor", actor.GetIdentifier(), actor.Address, false, Actor.Null);
|
||||
|
||||
actor = Dalamud.Objects[DyePreviewIndex];
|
||||
if (actor != null)
|
||||
yield return new ActorData("Dye Preview Actor", string.Empty, actor.Address, false, Actor.Null);
|
||||
actor = Dalamud.Objects[DyePreviewIndex]?.Address;
|
||||
if (actor)
|
||||
yield return new ActorData("Dye Preview Actor", actor.GetIdentifier(), actor.Address, false, Actor.Null);
|
||||
|
||||
for (var i = 0; i < GPosePlayerIndex; ++i)
|
||||
{
|
||||
var character = Dalamud.Objects[i];
|
||||
if (character == null
|
||||
|| character.ObjectKind is not (ObjectKind.Player or ObjectKind.BattleNpc or ObjectKind.EventNpc or ObjectKind.Companion
|
||||
actor = Dalamud.Objects[i]?.Address;
|
||||
if (!actor
|
||||
|| actor.ObjectKind is not (ObjectKind.Player or ObjectKind.BattleNpc or ObjectKind.EventNpc or ObjectKind.Companion
|
||||
or ObjectKind.Retainer))
|
||||
continue;
|
||||
|
||||
var name = character.Name.TextValue;
|
||||
if (name.Length == 0)
|
||||
var identifier = actor.GetIdentifier();
|
||||
if (identifier.Name.Length == 0)
|
||||
continue;
|
||||
|
||||
if (_nameCounters.TryGetValue(name, out var num))
|
||||
_nameCounters[name] = ++num;
|
||||
if (NameCounters.TryGetValue(identifier.Name, out var num))
|
||||
NameCounters[identifier.Name] = ++num;
|
||||
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;
|
||||
|
||||
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)
|
||||
{
|
||||
var character = Dalamud.Objects[i];
|
||||
if (character == null
|
||||
|| !((Actor)character.Address).IsAvailable
|
||||
|| character.ObjectKind is not (ObjectKind.Player or ObjectKind.BattleNpc or ObjectKind.EventNpc or ObjectKind.Companion
|
||||
actor = Dalamud.Objects[i]?.Address;
|
||||
if (!actor
|
||||
|| actor.ObjectKind is not (ObjectKind.Player or ObjectKind.BattleNpc or ObjectKind.EventNpc or ObjectKind.Companion
|
||||
or ObjectKind.Retainer))
|
||||
continue;
|
||||
|
||||
var name = character.Name.TextValue;
|
||||
if (name.Length == 0)
|
||||
var identifier = actor.GetIdentifier();
|
||||
if (identifier.Name.Length == 0)
|
||||
continue;
|
||||
|
||||
if (_nameCounters.TryGetValue(name, out var num))
|
||||
_nameCounters[name] = ++num;
|
||||
if (NameCounters.TryGetValue(identifier.Name, out var num))
|
||||
NameCounters[identifier.Name] = ++num;
|
||||
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;
|
||||
|
||||
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)
|
||||
return gPose ? $"{playerName} (GPose)" : num == 1 ? playerName : $"{playerName} #{num}";
|
||||
|
|
|
|||
|
|
@ -1,211 +1,283 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
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);
|
||||
|
||||
[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 Hook<FlagSlotForUpdateDelegate> FlagSlotForUpdateHook = null!;
|
||||
// This gets called when one of the ten equip items of an existing draw object gets changed.
|
||||
[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)
|
||||
{
|
||||
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);
|
||||
|
||||
[Signature("E8 ?? ?? ?? ?? 44 8B 9F")]
|
||||
public Hook<LoadWeaponDelegate> LoadWeaponHook = null!;
|
||||
|
||||
private void LoadWeaponDetour(IntPtr characterOffset, uint slot, ulong data, byte unk)
|
||||
public bool ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data)
|
||||
{
|
||||
const int offset = 0xD8 * 8;
|
||||
PluginLog.Information($"0x{characterOffset:X}, 0x{characterOffset - offset:X}, {slot}, {data:16X}, {unk}");
|
||||
LoadWeaponHook.Original(characterOffset, slot, data, unk);
|
||||
if (actor && CurrentManipulations.ChangeEquip(actor, slot, data).HasValue && actor.DrawObject != null)
|
||||
return _flagSlotForUpdateHook?.Original(actor.DrawObject, slot.ToIndex(), &data) != 0;
|
||||
|
||||
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",
|
||||
// DetourName = nameof(FlagSlotForUpdateDetour))]
|
||||
//public Hook<FlagSlotForUpdateDelegate>? FlagSlotForUpdateHook;
|
||||
//
|
||||
// public readonly FixedDesigns FixedDesigns;
|
||||
//
|
||||
// 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.
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
SignatureHelper.Initialise(this);
|
||||
// FixedDesigns = new FixedDesigns(designs);
|
||||
Glamourer.Penumbra.CreatingCharacterBase += OnCharacterRedraw;
|
||||
FlagSlotForUpdateHook.Enable();
|
||||
LoadWeaponHook.Enable();
|
||||
//
|
||||
// if (Glamourer.Config.ApplyFixedDesigns)
|
||||
// Enable();
|
||||
//_flagSlotForUpdateHook?.Enable();
|
||||
//_loadWeaponHook?.Enable();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
FlagSlotForUpdateHook.Dispose();
|
||||
LoadWeaponHook.Dispose();
|
||||
_flagSlotForUpdateHook?.Dispose();
|
||||
_loadWeaponHook?.Dispose();
|
||||
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)
|
||||
{
|
||||
var name = GetName((Character*)addr);
|
||||
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)
|
||||
//if (CurrentManipulations.GetSave(addr, out var save))
|
||||
//{
|
||||
// var name = new Utf8String(human->GameObject.Name).ToString();
|
||||
// if (FixedDesigns.EnabledDesigns.TryGetValue(name, out var designs))
|
||||
// {
|
||||
// var design = designs.OrderBy(d => d.Jobs.Count).FirstOrDefault(d => d.Jobs.Fits(human->ClassJob));
|
||||
// if (design != null)
|
||||
// {
|
||||
// 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,
|
||||
// };
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// *(CustomizationData*)customize = *(CustomizationData*)save.Customization.Address;
|
||||
// var equip = (CharacterEquip)equipData;
|
||||
// var newEquip = save.Equipment;
|
||||
// for (var i = 0; i < 10; ++i)
|
||||
// equip[i] = newEquip[i];
|
||||
//}
|
||||
}
|
||||
//
|
||||
// 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