Glamourer/Glamourer/Interop/WeaponService.cs
Ottermandias 648b3d4515 Huh.
2023-12-21 17:05:24 +01:00

125 lines
5.3 KiB
C#

using System;
using System.Threading;
using Dalamud.Hooking;
using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using Glamourer.Events;
using Glamourer.Interop.Structs;
using Glamourer.Structs;
using Penumbra.GameData.Enums;
using Penumbra.GameData.Structs;
namespace Glamourer.Interop;
public unsafe class WeaponService : IDisposable
{
private readonly WeaponLoading _event;
private readonly CrestService _crestService;
private readonly ThreadLocal<bool> _inUpdate = new(() => false);
private readonly delegate* unmanaged[Stdcall]<DrawDataContainer*, uint, ulong, byte, byte, byte, byte, void>
_original;
public WeaponService(WeaponLoading @event, IGameInteropProvider interop, CrestService crestService)
{
_event = @event;
_crestService = crestService;
_loadWeaponHook =
interop.HookFromAddress<LoadWeaponDelegate>((nint)DrawDataContainer.MemberFunctionPointers.LoadWeapon, LoadWeaponDetour);
_original =
(delegate* unmanaged[Stdcall] < DrawDataContainer*, uint, ulong, byte, byte, byte, byte, void >)
DrawDataContainer.MemberFunctionPointers.LoadWeapon;
_loadWeaponHook.Enable();
}
public void Dispose()
=> _loadWeaponHook.Dispose();
// Weapons for a specific character are reloaded with this function.
// slot is 0 for main hand, 1 for offhand, 2 for combat effects.
// 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.
private delegate void LoadWeaponDelegate(DrawDataContainer* drawData, uint slot, ulong weapon, byte redrawOnEquality, byte unk2,
byte skipGameObject, byte unk4);
private readonly Hook<LoadWeaponDelegate> _loadWeaponHook;
private void LoadWeaponDetour(DrawDataContainer* drawData, uint slot, ulong weaponValue, byte redrawOnEquality, byte unk2,
byte skipGameObject, byte unk4)
{
if (!_inUpdate.Value)
{
var actor = (Actor)((nint*)drawData)[1];
var weapon = new CharacterWeapon(weaponValue);
var equipSlot = slot switch
{
0 => EquipSlot.MainHand,
1 => EquipSlot.OffHand,
_ => EquipSlot.Unknown,
};
var tmpWeapon = weapon;
// First call the regular function.
if (equipSlot is not EquipSlot.Unknown)
_event.Invoke(actor, equipSlot, ref tmpWeapon);
// Sage hack for weapons appearing in animations?
// Check for weapon value 0 for certain cases (e.g. carbuncles transforming to humans) because that breaks some stuff (weapon hiding?) otherwise.
else if (weaponValue == actor.GetMainhand().Value && weaponValue != 0)
_event.Invoke(actor, EquipSlot.MainHand, ref tmpWeapon);
_loadWeaponHook.Original(drawData, slot, weapon.Value, redrawOnEquality, unk2, skipGameObject, unk4);
if (tmpWeapon.Value != weapon.Value)
{
if (tmpWeapon.Skeleton.Id == 0)
tmpWeapon.Stain = 0;
_loadWeaponHook.Original(drawData, slot, tmpWeapon.Value, 1, unk2, 1, unk4);
}
Glamourer.Log.Excessive(
$"Weapon reloaded for 0x{actor.Address:X} ({actor.Utf8Name}) with attributes {slot} {weapon.Value:X14}, {redrawOnEquality}, {unk2}, {skipGameObject}, {unk4}");
}
else
{
_original(drawData, slot, weaponValue, redrawOnEquality, unk2, skipGameObject, 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:
_inUpdate.Value = true;
_loadWeaponHook.Original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0);
_inUpdate.Value = false;
return;
case EquipSlot.OffHand:
_inUpdate.Value = true;
_loadWeaponHook.Original(&character.AsCharacter->DrawData, 1, weapon.Value, 1, 0, 1, 0);
_inUpdate.Value = false;
return;
case EquipSlot.BothHand:
_inUpdate.Value = true;
_loadWeaponHook.Original(&character.AsCharacter->DrawData, 0, weapon.Value, 1, 0, 1, 0);
_loadWeaponHook.Original(&character.AsCharacter->DrawData, 1, CharacterWeapon.Empty.Value, 1, 0, 1, 0);
_inUpdate.Value = false;
return;
}
}
public void LoadStain(Actor character, EquipSlot slot, StainId stain)
{
var mdl = character.Model;
var (_, _, mh, oh) = mdl.GetWeapons(character);
var value = slot == EquipSlot.OffHand ? oh : mh;
var weapon = value.With(value.Skeleton.Id == 0 ? 0 : stain);
LoadWeapon(character, slot, weapon);
}
}