mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2026-02-17 13:07:44 +01:00
Some more reworking
This commit is contained in:
parent
6a4b5fc3b2
commit
dad146d043
41 changed files with 1714 additions and 1320 deletions
|
|
@ -1,6 +1,7 @@
|
|||
using System;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Utility;
|
||||
using Lumina.Excel.GeneratedSheets;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using Penumbra.GameData.ByteString;
|
||||
|
|
@ -187,13 +188,26 @@ public unsafe partial struct Actor
|
|||
public bool IsValid
|
||||
=> true;
|
||||
|
||||
public OwnedIdentifier(Utf8String name, Utf8String ownerName, ushort ownerHomeWorld, uint dataId, ObjectKind kind)
|
||||
public OwnedIdentifier(Utf8String actorName, Utf8String ownerName, ushort ownerHomeWorld, uint dataId, ObjectKind kind)
|
||||
{
|
||||
Name = name;
|
||||
OwnerName = ownerName;
|
||||
OwnerHomeWorld = ownerHomeWorld;
|
||||
DataId = dataId;
|
||||
Kind = kind;
|
||||
Name = actorName;
|
||||
switch (Kind)
|
||||
{
|
||||
case ObjectKind.MountType:
|
||||
var mount = Dalamud.GameData.GetExcelSheet<Mount>()!.GetRow(dataId);
|
||||
if (mount != null)
|
||||
Name = Utf8String.FromSpanUnsafe(mount.Singular.RawData, false).AsciiToMixed();
|
||||
break;
|
||||
case ObjectKind.Companion:
|
||||
var companion = Dalamud.GameData.GetExcelSheet<Companion>()!.GetRow(dataId);
|
||||
if (companion != null)
|
||||
Name = Utf8String.FromSpanUnsafe(companion.Singular.RawData, false).AsciiToMixed();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(IIdentifier? other)
|
||||
|
|
@ -341,8 +355,19 @@ public unsafe partial struct Actor
|
|||
if (!owner)
|
||||
return new InvalidIdentifier();
|
||||
|
||||
return new OwnedIdentifier(actor.Utf8Name, owner.Utf8Name, owner.Pointer->HomeWorld,
|
||||
actor.Pointer->GameObject.DataID, actor.ObjectKind);
|
||||
var dataId = actor.ObjectKind switch
|
||||
{
|
||||
ObjectKind.MountType => owner.UsedMountId,
|
||||
ObjectKind.Companion => actor.CompanionId,
|
||||
_ => actor.Pointer->GameObject.DataID,
|
||||
};
|
||||
|
||||
var name = actor.Utf8Name;
|
||||
if (name.IsEmpty && dataId == 0)
|
||||
return new InvalidIdentifier();
|
||||
|
||||
return new OwnedIdentifier(name, owner.Utf8Name, owner.Pointer->HomeWorld,
|
||||
dataId, actor.ObjectKind);
|
||||
}
|
||||
default: return new InvalidIdentifier();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,115 +1,13 @@
|
|||
using System;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Penumbra.GameData.ByteString;
|
||||
using Penumbra.GameData.Structs;
|
||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public interface IDesignable
|
||||
{
|
||||
public bool Valid { get; }
|
||||
public uint ModelId { get; }
|
||||
public Customize Customize { get; }
|
||||
public CharacterEquip Equip { get; }
|
||||
public CharacterWeapon MainHand { get; }
|
||||
public CharacterWeapon OffHand { get; }
|
||||
public bool VisorEnabled { get; }
|
||||
public bool WeaponEnabled { get; }
|
||||
}
|
||||
|
||||
public unsafe partial struct DrawObject : IEquatable<DrawObject>, IDesignable
|
||||
{
|
||||
public Human* Pointer;
|
||||
|
||||
public IntPtr Address
|
||||
=> (IntPtr)Pointer;
|
||||
|
||||
public static implicit operator DrawObject(IntPtr? pointer)
|
||||
=> new() { Pointer = (Human*)(pointer ?? IntPtr.Zero) };
|
||||
|
||||
public static implicit operator IntPtr(DrawObject drawObject)
|
||||
=> drawObject.Pointer == null ? IntPtr.Zero : (IntPtr)drawObject.Pointer;
|
||||
|
||||
public bool Valid
|
||||
=> Pointer != null;
|
||||
|
||||
public uint ModelId
|
||||
=> 0;
|
||||
|
||||
public uint Type
|
||||
=> (*(delegate* unmanaged<Human*, uint>**)Pointer)[50](Pointer);
|
||||
|
||||
public Customize Customize
|
||||
=> new((CustomizeData*)Pointer->CustomizeData);
|
||||
|
||||
public CharacterEquip Equip
|
||||
=> new((CharacterArmor*)Pointer->EquipSlotData);
|
||||
|
||||
public CharacterWeapon MainHand
|
||||
{
|
||||
get
|
||||
{
|
||||
var child = (byte*)Pointer->CharacterBase.DrawObject.Object.ChildObject;
|
||||
if (child == null)
|
||||
return CharacterWeapon.Empty;
|
||||
|
||||
return *(CharacterWeapon*)(child + 0x8F0);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe CharacterWeapon OffHand
|
||||
{
|
||||
get
|
||||
{
|
||||
var child = Pointer->CharacterBase.DrawObject.Object.ChildObject;
|
||||
if (child == null)
|
||||
return CharacterWeapon.Empty;
|
||||
|
||||
var sibling = (byte*) child->NextSiblingObject;
|
||||
if (sibling == null)
|
||||
return CharacterWeapon.Empty;
|
||||
|
||||
return *(CharacterWeapon*)(child + 0x8F0);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe bool VisorEnabled
|
||||
=> (*(byte*)(Address + 0x90) & 0x40) != 0;
|
||||
|
||||
public unsafe bool WeaponEnabled
|
||||
=> false;
|
||||
|
||||
public static implicit operator bool(DrawObject actor)
|
||||
=> actor.Pointer != null;
|
||||
|
||||
public static bool operator true(DrawObject actor)
|
||||
=> actor.Pointer != null;
|
||||
|
||||
public static bool operator false(DrawObject actor)
|
||||
=> actor.Pointer == null;
|
||||
|
||||
public static bool operator !(DrawObject actor)
|
||||
=> actor.Pointer == null;
|
||||
|
||||
public bool Equals(DrawObject other)
|
||||
=> Pointer == other.Pointer;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is DrawObject other && Equals(other);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> unchecked((int)(long)Pointer);
|
||||
|
||||
public static bool operator ==(DrawObject lhs, DrawObject rhs)
|
||||
=> lhs.Pointer == rhs.Pointer;
|
||||
|
||||
public static bool operator !=(DrawObject lhs, DrawObject rhs)
|
||||
=> lhs.Pointer != rhs.Pointer;
|
||||
}
|
||||
|
||||
public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
|
||||
{
|
||||
public static readonly Actor Null = new() { Pointer = null };
|
||||
|
|
@ -173,6 +71,12 @@ public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
|
|||
set => Pointer->ModelCharaId = (int)value;
|
||||
}
|
||||
|
||||
public ushort UsedMountId
|
||||
=> !IsHuman ? (ushort)0 : *(ushort*)((byte*)Pointer + 0x668);
|
||||
|
||||
public ushort CompanionId
|
||||
=> ObjectKind == ObjectKind.Companion ? *(ushort*)((byte*)Pointer + 0x1AAC) : (ushort)0;
|
||||
|
||||
public Customize Customize
|
||||
=> new((CustomizeData*)Pointer->CustomizeData);
|
||||
|
||||
|
|
|
|||
97
Glamourer/Interop/DrawObject.cs
Normal file
97
Glamourer/Interop/DrawObject.cs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
using System;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Penumbra.GameData.Structs;
|
||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public unsafe partial struct DrawObject : IEquatable<DrawObject>, IDesignable
|
||||
{
|
||||
public Human* Pointer;
|
||||
|
||||
public IntPtr Address
|
||||
=> (IntPtr)Pointer;
|
||||
|
||||
public static implicit operator DrawObject(IntPtr? pointer)
|
||||
=> new() { Pointer = (Human*)(pointer ?? IntPtr.Zero) };
|
||||
|
||||
public static implicit operator IntPtr(DrawObject drawObject)
|
||||
=> drawObject.Pointer == null ? IntPtr.Zero : (IntPtr)drawObject.Pointer;
|
||||
|
||||
public bool Valid
|
||||
=> Pointer != null;
|
||||
|
||||
public uint ModelId
|
||||
=> 0;
|
||||
|
||||
public uint Type
|
||||
=> (*(delegate* unmanaged<Human*, uint>**)Pointer)[50](Pointer);
|
||||
|
||||
public Customize Customize
|
||||
=> new((CustomizeData*)Pointer->CustomizeData);
|
||||
|
||||
public CharacterEquip Equip
|
||||
=> new((CharacterArmor*)Pointer->EquipSlotData);
|
||||
|
||||
public CharacterWeapon MainHand
|
||||
{
|
||||
get
|
||||
{
|
||||
var child = (byte*)Pointer->CharacterBase.DrawObject.Object.ChildObject;
|
||||
if (child == null)
|
||||
return CharacterWeapon.Empty;
|
||||
|
||||
return *(CharacterWeapon*)(child + 0x8F0);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe CharacterWeapon OffHand
|
||||
{
|
||||
get
|
||||
{
|
||||
var child = Pointer->CharacterBase.DrawObject.Object.ChildObject;
|
||||
if (child == null)
|
||||
return CharacterWeapon.Empty;
|
||||
|
||||
var sibling = (byte*)child->NextSiblingObject;
|
||||
if (sibling == null)
|
||||
return CharacterWeapon.Empty;
|
||||
|
||||
return *(CharacterWeapon*)(sibling + 0x8F0);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe bool VisorEnabled
|
||||
=> (*(byte*)(Address + 0x90) & 0x40) != 0;
|
||||
|
||||
public unsafe bool WeaponEnabled
|
||||
=> false;
|
||||
|
||||
public static implicit operator bool(DrawObject actor)
|
||||
=> actor.Pointer != null;
|
||||
|
||||
public static bool operator true(DrawObject actor)
|
||||
=> actor.Pointer != null;
|
||||
|
||||
public static bool operator false(DrawObject actor)
|
||||
=> actor.Pointer == null;
|
||||
|
||||
public static bool operator !(DrawObject actor)
|
||||
=> actor.Pointer == null;
|
||||
|
||||
public bool Equals(DrawObject other)
|
||||
=> Pointer == other.Pointer;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is DrawObject other && Equals(other);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> unchecked((int)(long)Pointer);
|
||||
|
||||
public static bool operator ==(DrawObject lhs, DrawObject rhs)
|
||||
=> lhs.Pointer == rhs.Pointer;
|
||||
|
||||
public static bool operator !=(DrawObject lhs, DrawObject rhs)
|
||||
=> lhs.Pointer != rhs.Pointer;
|
||||
}
|
||||
16
Glamourer/Interop/IDesignable.cs
Normal file
16
Glamourer/Interop/IDesignable.cs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
using Glamourer.Customization;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public interface IDesignable
|
||||
{
|
||||
public bool Valid { get; }
|
||||
public uint ModelId { get; }
|
||||
public Customize Customize { get; }
|
||||
public CharacterEquip Equip { get; }
|
||||
public CharacterWeapon MainHand { get; }
|
||||
public CharacterWeapon OffHand { get; }
|
||||
public bool VisorEnabled { get; }
|
||||
public bool WeaponEnabled { get; }
|
||||
}
|
||||
52
Glamourer/Interop/RedrawManager.Customize.cs
Normal file
52
Glamourer/Interop/RedrawManager.Customize.cs
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.GameData.Enums;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public unsafe partial class RedrawManager
|
||||
{
|
||||
// Update
|
||||
public delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment);
|
||||
|
||||
[Signature("E8 ?? ?? ?? ?? 41 0F B6 C5 66 41 89 86")]
|
||||
private readonly ChangeCustomizeDelegate _changeCustomize = null!;
|
||||
|
||||
public bool UpdateCustomize(Actor actor, Customize customize)
|
||||
{
|
||||
if (!actor.Valid || !actor.DrawObject.Valid)
|
||||
return false;
|
||||
|
||||
var d = actor.DrawObject;
|
||||
if (NeedsRedraw(d.Customize, customize))
|
||||
{
|
||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw);
|
||||
return true;
|
||||
}
|
||||
|
||||
return _changeCustomize(d.Pointer, (byte*)customize.Data, 1);
|
||||
}
|
||||
|
||||
public static bool NeedsRedraw(Customize lhs, Customize rhs)
|
||||
=> lhs.Race != rhs.Race
|
||||
|| lhs.Gender != rhs.Gender
|
||||
|| lhs.BodyType != rhs.BodyType
|
||||
|| lhs.Face != rhs.Face
|
||||
|| lhs.Race == Race.Hyur && lhs.Clan != rhs.Clan;
|
||||
|
||||
|
||||
public static void SetVisor(Human* data, bool on)
|
||||
{
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
var flags = &data->CharacterBase.UnkFlags_01;
|
||||
var state = (*flags & 0x40) != 0;
|
||||
if (state == on)
|
||||
return;
|
||||
|
||||
*flags = (byte)((on ? *flags | 0x40 : *flags & 0xBF) | 0x80);
|
||||
}
|
||||
}
|
||||
68
Glamourer/Interop/RedrawManager.Equipment.cs
Normal file
68
Glamourer/Interop/RedrawManager.Equipment.cs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
using System;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public unsafe partial class RedrawManager
|
||||
{
|
||||
public delegate ulong FlagSlotForUpdateDelegate(Human* drawObject, uint slot, CharacterArmor* data);
|
||||
|
||||
// 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 = null!;
|
||||
|
||||
private ulong FlagSlotForUpdateDetour(Human* drawObject, uint slotIdx, CharacterArmor* data)
|
||||
{
|
||||
var slot = slotIdx.ToEquipSlot();
|
||||
try
|
||||
{
|
||||
var actor = Glamourer.Penumbra.GameObjectFromDrawObject((IntPtr)drawObject);
|
||||
var identifier = actor.GetIdentifier();
|
||||
|
||||
if (_fixedDesigns.TryGetDesign(identifier, out var save))
|
||||
{
|
||||
PluginLog.Information($"Loaded {slot} from fixed design for {identifier}.");
|
||||
(var replaced, *data) =
|
||||
Glamourer.RestrictedGear.ResolveRestricted(save.Equipment[slot], slot, (Race)drawObject->Race, (Gender)drawObject->Sex);
|
||||
}
|
||||
else if (_currentManipulations.TryGetDesign(identifier, out var save2))
|
||||
{
|
||||
PluginLog.Information($"Updated {slot} from current designs for {identifier}.");
|
||||
(var replaced, *data) =
|
||||
Glamourer.RestrictedGear.ResolveRestricted(*data, slot, (Race)drawObject->Race, (Gender)(drawObject->Sex + 1));
|
||||
save2.Data.Equipment[slot] = *data;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error($"Error on loading new gear:\n{e}");
|
||||
}
|
||||
|
||||
return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
|
||||
}
|
||||
|
||||
public bool ChangeEquip(DrawObject drawObject, uint slotIdx, CharacterArmor data)
|
||||
{
|
||||
if (!drawObject)
|
||||
return false;
|
||||
|
||||
if (slotIdx > 9)
|
||||
return false;
|
||||
|
||||
return FlagSlotForUpdateDetour(drawObject.Pointer, slotIdx, &data) != 0;
|
||||
}
|
||||
|
||||
public bool ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data)
|
||||
=> actor && ChangeEquip(actor.DrawObject, slot.ToIndex(), data);
|
||||
|
||||
public bool ChangeEquip(DrawObject drawObject, EquipSlot slot, CharacterArmor data)
|
||||
=> ChangeEquip(drawObject, slot.ToIndex(), data);
|
||||
|
||||
public bool ChangeEquip(Actor actor, uint slotIdx, CharacterArmor data)
|
||||
=> actor && ChangeEquip(actor.DrawObject, slotIdx, data);
|
||||
}
|
||||
102
Glamourer/Interop/RedrawManager.Weapons.cs
Normal file
102
Glamourer/Interop/RedrawManager.Weapons.cs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public unsafe partial class RedrawManager
|
||||
{
|
||||
public static readonly int CharacterWeaponOffset = (int)Marshal.OffsetOf<Character>("DrawData");
|
||||
|
||||
public delegate void LoadWeaponDelegate(IntPtr offsetCharacter, uint slot, ulong weapon, byte redrawOnEquality, byte unk2,
|
||||
byte skipGameObject,
|
||||
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.
|
||||
// redrawOnEquality controls whether the game does anything if the new weapon is identical to the old one.
|
||||
// skipGameObject seems to control whether the new weapons are written to the game object or just influence the draw object. (1 = skip, 0 = change)
|
||||
// unk4 seemed to be the same as unk1.
|
||||
[Signature("E8 ?? ?? ?? ?? 44 8B 9F", DetourName = nameof(LoadWeaponDetour))]
|
||||
private readonly Hook<LoadWeaponDelegate> _loadWeaponHook = null!;
|
||||
|
||||
private void LoadWeaponDetour(IntPtr characterOffset, uint slot, ulong weapon, byte redrawOnEquality, byte unk2, byte skipGameObject,
|
||||
byte unk4)
|
||||
{
|
||||
var oldWeapon = weapon;
|
||||
var character = (Actor)(characterOffset - CharacterWeaponOffset);
|
||||
try
|
||||
{
|
||||
var identifier = character.GetIdentifier();
|
||||
if (_fixedDesigns.TryGetDesign(identifier, out var save))
|
||||
{
|
||||
PluginLog.Information($"Loaded weapon from fixed design for {identifier}.");
|
||||
weapon = slot switch
|
||||
{
|
||||
0 => save.MainHand.Value,
|
||||
1 => save.OffHand.Value,
|
||||
_ => weapon,
|
||||
};
|
||||
}
|
||||
else if (redrawOnEquality == 1 && _currentManipulations.TryGetDesign(identifier, out var save2))
|
||||
{
|
||||
PluginLog.Information($"Loaded weapon from current design for {identifier}.");
|
||||
switch (slot)
|
||||
{
|
||||
case 0:
|
||||
save2.Data.MainHand = new CharacterWeapon(weapon);
|
||||
break;
|
||||
case 1:
|
||||
save2.Data.OffHand = new CharacterWeapon(weapon);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error($"Error on loading new weapon:\n{e}");
|
||||
}
|
||||
|
||||
// First call the regular function.
|
||||
_loadWeaponHook.Original(characterOffset, slot, oldWeapon, redrawOnEquality, unk2, skipGameObject, unk4);
|
||||
// If something changed the weapon, call it again with the actual change, not forcing redraws and skipping applying it to the game object.
|
||||
if (oldWeapon != weapon)
|
||||
_loadWeaponHook.Original(characterOffset, slot, weapon, 0 /* redraw */, unk2, 1 /* skip */, unk4);
|
||||
// If we're not actively changing the offhand and the game object has no offhand, redraw an empty offhand to fix animation problems.
|
||||
else if (slot != 1 && character.OffHand.Value == 0)
|
||||
_loadWeaponHook.Original(characterOffset, 1, 0, 1 /* redraw */, unk2, 1 /* skip */, unk4);
|
||||
}
|
||||
|
||||
// Load a specific weapon for a character by its data and slot.
|
||||
public void LoadWeapon(Actor character, EquipSlot slot, CharacterWeapon weapon)
|
||||
{
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0);
|
||||
return;
|
||||
case EquipSlot.OffHand:
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, weapon.Value, 0, 0, 1, 0);
|
||||
return;
|
||||
case EquipSlot.BothHand:
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0);
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, CharacterWeapon.Empty.Value, 0, 0, 1, 0);
|
||||
return;
|
||||
// function can also be called with '2', but does not seem to ever be.
|
||||
}
|
||||
}
|
||||
|
||||
// Load specific Main- and Offhand weapons.
|
||||
public void LoadWeapon(Actor character, CharacterWeapon main, CharacterWeapon off)
|
||||
{
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, main.Value, 1, 0, 1, 0);
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, off.Value, 1, 0, 1, 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +1,7 @@
|
|||
using System;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.State;
|
||||
|
|
@ -12,7 +9,6 @@ using Glamourer.Structs;
|
|||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||
using Race = Penumbra.GameData.Enums.Race;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
|
|
@ -32,157 +28,6 @@ public unsafe partial class RedrawManager
|
|||
public event Action<Actor, Job>? JobChanged;
|
||||
}
|
||||
|
||||
public unsafe partial class RedrawManager
|
||||
{
|
||||
public delegate ulong FlagSlotForUpdateDelegate(Human* drawObject, uint slot, CharacterArmor* data);
|
||||
|
||||
// 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 = null!;
|
||||
|
||||
private ulong FlagSlotForUpdateDetour(Human* drawObject, uint slotIdx, CharacterArmor* data)
|
||||
{
|
||||
var slot = slotIdx.ToEquipSlot();
|
||||
try
|
||||
{
|
||||
var actor = Glamourer.Penumbra.GameObjectFromDrawObject((IntPtr)drawObject);
|
||||
var identifier = actor.GetIdentifier();
|
||||
|
||||
if (_fixedDesigns.TryGetDesign(identifier, out var save))
|
||||
{
|
||||
PluginLog.Information($"Loaded {slot} from fixed design for {identifier}.");
|
||||
(var replaced, *data) =
|
||||
Glamourer.RestrictedGear.ResolveRestricted(save.Equipment[slot], slot, (Race)drawObject->Race, (Gender)drawObject->Sex);
|
||||
}
|
||||
else if (_currentManipulations.TryGetDesign(identifier, out var save2))
|
||||
{
|
||||
PluginLog.Information($"Updated {slot} from current designs for {identifier}.");
|
||||
(var replaced, *data) =
|
||||
Glamourer.RestrictedGear.ResolveRestricted(*data, slot, (Race)drawObject->Race, (Gender)(drawObject->Sex + 1));
|
||||
save2.Data.Equipment[slot] = *data;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error($"Error on loading new gear:\n{e}");
|
||||
}
|
||||
|
||||
return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
|
||||
}
|
||||
|
||||
public bool ChangeEquip(DrawObject drawObject, uint slotIdx, CharacterArmor data)
|
||||
{
|
||||
if (!drawObject)
|
||||
return false;
|
||||
|
||||
if (slotIdx > 9)
|
||||
return false;
|
||||
|
||||
return FlagSlotForUpdateDetour(drawObject.Pointer, slotIdx, &data) != 0;
|
||||
}
|
||||
|
||||
public bool ChangeEquip(Actor actor, EquipSlot slot, CharacterArmor data)
|
||||
=> actor && ChangeEquip(actor.DrawObject, slot.ToIndex(), data);
|
||||
|
||||
public bool ChangeEquip(DrawObject drawObject, EquipSlot slot, CharacterArmor data)
|
||||
=> ChangeEquip(drawObject, slot.ToIndex(), data);
|
||||
|
||||
public bool ChangeEquip(Actor actor, uint slotIdx, CharacterArmor data)
|
||||
=> actor && ChangeEquip(actor.DrawObject, slotIdx, data);
|
||||
}
|
||||
|
||||
public unsafe partial class RedrawManager
|
||||
{
|
||||
public static readonly int CharacterWeaponOffset = (int) Marshal.OffsetOf<Character>("DrawData");
|
||||
|
||||
public delegate void LoadWeaponDelegate(IntPtr offsetCharacter, uint slot, ulong weapon, byte redrawOnEquality, byte unk2,
|
||||
byte skipGameObject,
|
||||
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.
|
||||
// redrawOnEquality controls whether the game does anything if the new weapon is identical to the old one.
|
||||
// skipGameObject seems to control whether the new weapons are written to the game object or just influence the draw object. (1 = skip, 0 = change)
|
||||
// unk4 seemed to be the same as unk1.
|
||||
[Signature("E8 ?? ?? ?? ?? 44 8B 9F", DetourName = nameof(LoadWeaponDetour))]
|
||||
private readonly Hook<LoadWeaponDelegate> _loadWeaponHook = null!;
|
||||
|
||||
private void LoadWeaponDetour(IntPtr characterOffset, uint slot, ulong weapon, byte redrawOnEquality, byte unk2, byte skipGameObject,
|
||||
byte unk4)
|
||||
{
|
||||
var oldWeapon = weapon;
|
||||
var character = (Actor)(characterOffset - CharacterWeaponOffset);
|
||||
try
|
||||
{
|
||||
var identifier = character.GetIdentifier();
|
||||
if (_fixedDesigns.TryGetDesign(identifier, out var save))
|
||||
{
|
||||
PluginLog.Information($"Loaded weapon from fixed design for {identifier}.");
|
||||
weapon = slot switch
|
||||
{
|
||||
0 => save.MainHand.Value,
|
||||
1 => save.OffHand.Value,
|
||||
_ => weapon,
|
||||
};
|
||||
}
|
||||
else if (redrawOnEquality == 1 && _currentManipulations.TryGetDesign(identifier, out var save2))
|
||||
{
|
||||
PluginLog.Information($"Loaded weapon from current design for {identifier}.");
|
||||
switch (slot)
|
||||
{
|
||||
//case 0:
|
||||
// save2.Data.MainHand = new CharacterWeapon(weapon);
|
||||
// break;
|
||||
//case 1:
|
||||
// save.OffHand = new CharacterWeapon(weapon);
|
||||
// break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error($"Error on loading new weapon:\n{e}");
|
||||
}
|
||||
|
||||
// First call the regular function.
|
||||
_loadWeaponHook.Original(characterOffset, slot, oldWeapon, redrawOnEquality, unk2, skipGameObject, unk4);
|
||||
// If something changed the weapon, call it again with the actual change, not forcing redraws and skipping applying it to the game object.
|
||||
if (oldWeapon != weapon)
|
||||
_loadWeaponHook.Original(characterOffset, slot, weapon, 0 /* redraw */, unk2, 1 /* skip */, unk4);
|
||||
// If we're not actively changing the offhand and the game object has no offhand, redraw an empty offhand to fix animation problems.
|
||||
else if (slot != 1 && character.OffHand.Value == 0)
|
||||
_loadWeaponHook.Original(characterOffset, 1, 0, 1 /* redraw */, unk2, 1 /* skip */, unk4);
|
||||
}
|
||||
|
||||
// Load a specific weapon for a character by its data and slot.
|
||||
public void LoadWeapon(Actor character, EquipSlot slot, CharacterWeapon weapon)
|
||||
{
|
||||
switch (slot)
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0);
|
||||
return;
|
||||
case EquipSlot.OffHand:
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, weapon.Value, 0, 0, 1, 0);
|
||||
return;
|
||||
case EquipSlot.BothHand:
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0);
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, CharacterWeapon.Empty.Value, 0, 0, 1, 0);
|
||||
return;
|
||||
// function can also be called with '2', but does not seem to ever be.
|
||||
}
|
||||
}
|
||||
|
||||
// Load specific Main- and Offhand weapons.
|
||||
public void LoadWeapon(Actor character, CharacterWeapon main, CharacterWeapon off)
|
||||
{
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, main.Value, 0, 0, 1, 0);
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, off.Value, 0, 0, 1, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe partial class RedrawManager : IDisposable
|
||||
{
|
||||
private readonly FixedDesigns _fixedDesigns;
|
||||
|
|
@ -191,8 +36,8 @@ public unsafe partial class RedrawManager : IDisposable
|
|||
public RedrawManager(FixedDesigns fixedDesigns, CurrentManipulations currentManipulations)
|
||||
{
|
||||
SignatureHelper.Initialise(this);
|
||||
Glamourer.Penumbra.CreatingCharacterBase += OnCharacterRedraw;
|
||||
Glamourer.Penumbra.CreatedCharacterBase += OnCharacterRedrawFinished;
|
||||
Glamourer.Penumbra.CreatingCharacterBase.Event += OnCharacterRedraw;
|
||||
Glamourer.Penumbra.CreatedCharacterBase.Event += OnCharacterRedrawFinished;
|
||||
_fixedDesigns = fixedDesigns;
|
||||
_currentManipulations = currentManipulations;
|
||||
_flagSlotForUpdateHook.Enable();
|
||||
|
|
@ -205,8 +50,8 @@ public unsafe partial class RedrawManager : IDisposable
|
|||
_flagSlotForUpdateHook.Dispose();
|
||||
_loadWeaponHook.Dispose();
|
||||
_changeJobHook.Dispose();
|
||||
Glamourer.Penumbra.CreatingCharacterBase -= OnCharacterRedraw;
|
||||
Glamourer.Penumbra.CreatedCharacterBase -= OnCharacterRedrawFinished;
|
||||
Glamourer.Penumbra.CreatingCharacterBase.Event -= OnCharacterRedraw;
|
||||
Glamourer.Penumbra.CreatedCharacterBase.Event -= OnCharacterRedrawFinished;
|
||||
}
|
||||
|
||||
private void OnCharacterRedraw(Actor actor, uint* modelId, Customize customize, CharacterEquip equip)
|
||||
|
|
@ -225,23 +70,23 @@ public unsafe partial class RedrawManager : IDisposable
|
|||
// Apply customization if they correspond and there is customization to apply.
|
||||
var gameObjectCustomize = new Customize((CustomizeData*)actor.Pointer->CustomizeData);
|
||||
if (gameObjectCustomize.Equals(customize))
|
||||
customize.Load(save.Data.Customize);
|
||||
customize.Load(save!.Data.Customize);
|
||||
|
||||
// Compare game object equip data against draw object equip data for transformations.
|
||||
// Apply each piece of equip that should be applied if they correspond.
|
||||
var gameObjectEquip = new CharacterEquip((CharacterArmor*)actor.Pointer->EquipSlotData);
|
||||
if (gameObjectEquip.Equals(equip))
|
||||
{
|
||||
var saveEquip = save.Data.Equipment;
|
||||
var saveEquip = save!.Data.Equipment;
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
(var _, equip[slot]) =
|
||||
Glamourer.RestrictedGear.ResolveRestricted(true ? equip[slot] : saveEquip[slot], slot, customize.Race, customize.Gender);
|
||||
(_, equip[slot]) =
|
||||
Glamourer.RestrictedGear.ResolveRestricted(saveEquip[slot], slot, customize.Race, customize.Gender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCharacterRedraw(IntPtr gameObject, IntPtr modelId, IntPtr customize, IntPtr equipData)
|
||||
private void OnCharacterRedraw(IntPtr gameObject, string collection, IntPtr modelId, IntPtr customize, IntPtr equipData)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -254,7 +99,7 @@ public unsafe partial class RedrawManager : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
private static void OnCharacterRedrawFinished(IntPtr gameObject, IntPtr drawObject)
|
||||
private static void OnCharacterRedrawFinished(IntPtr gameObject, string collection, IntPtr drawObject)
|
||||
{
|
||||
//SetVisor((Human*)drawObject, true);
|
||||
if (Glamourer.Models.FromCharacterBase((CharacterBase*)drawObject, out var data))
|
||||
|
|
@ -262,42 +107,4 @@ public unsafe partial class RedrawManager : IDisposable
|
|||
else
|
||||
PluginLog.Information($"Key: {Glamourer.Models.KeyFromCharacterBase((CharacterBase*)drawObject):X16}");
|
||||
}
|
||||
|
||||
// Update
|
||||
public delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment);
|
||||
|
||||
[Signature("E8 ?? ?? ?? ?? 41 0F B6 C5 66 41 89 86")]
|
||||
private readonly ChangeCustomizeDelegate _changeCustomize = null!;
|
||||
|
||||
public bool UpdateCustomize(Actor actor, Customize customize)
|
||||
{
|
||||
if (!actor.Valid || !actor.DrawObject.Valid)
|
||||
return false;
|
||||
|
||||
var d = actor.DrawObject;
|
||||
if (NeedsRedraw(d.Customize, customize))
|
||||
{
|
||||
Glamourer.Penumbra.RedrawObject(actor.Character, RedrawType.Redraw, true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return _changeCustomize(d.Pointer, (byte*)customize.Data, 1);
|
||||
}
|
||||
|
||||
public static bool NeedsRedraw(Customize lhs, Customize rhs)
|
||||
=> lhs.Race != rhs.Race || lhs.Gender != rhs.Gender || lhs.Face != rhs.Face || lhs.Race == Race.Hyur && lhs.Clan != rhs.Clan;
|
||||
|
||||
|
||||
public static void SetVisor(Human* data, bool on)
|
||||
{
|
||||
if (data == null)
|
||||
return;
|
||||
|
||||
var flags = &data->CharacterBase.UnkFlags_01;
|
||||
var state = (*flags & 0x40) != 0;
|
||||
if (state == on)
|
||||
return;
|
||||
|
||||
*flags = (byte)((on ? *flags | 0x40 : *flags & 0xBF) | 0x80);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue