Some more reworking

This commit is contained in:
Ottermandias 2022-10-19 15:24:27 +02:00
parent 6a4b5fc3b2
commit dad146d043
41 changed files with 1714 additions and 1320 deletions

View file

@ -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();
}

View file

@ -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);

View 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;
}

View 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; }
}

View 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);
}
}

View 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);
}

View 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);
}
}

View file

@ -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);
}
}
}