mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-29 03:49:22 +01:00
95 lines
3.6 KiB
C#
95 lines
3.6 KiB
C#
using Dalamud.Hooking;
|
|
using Dalamud.Plugin.Services;
|
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
|
using Glamourer.Events;
|
|
using Penumbra.GameData.Enums;
|
|
using Penumbra.GameData.Interop;
|
|
|
|
namespace Glamourer.Interop;
|
|
|
|
public class VisorService : IDisposable
|
|
{
|
|
private readonly PenumbraReloaded _penumbra;
|
|
private readonly IGameInteropProvider _interop;
|
|
public readonly VisorStateChanged Event;
|
|
|
|
public VisorService(VisorStateChanged visorStateChanged, IGameInteropProvider interop, PenumbraReloaded penumbra)
|
|
{
|
|
_interop = interop;
|
|
_penumbra = penumbra;
|
|
Event = visorStateChanged;
|
|
_setupVisorHook = Create();
|
|
_penumbra.Subscribe(Restore, PenumbraReloaded.Priority.VisorService);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
_setupVisorHook.Dispose();
|
|
_penumbra.Unsubscribe(Restore);
|
|
}
|
|
|
|
/// <summary> Obtain the current state of the Visor for the given draw object (true: toggled). </summary>
|
|
public static unsafe bool GetVisorState(Model characterBase)
|
|
=> characterBase.IsCharacterBase && characterBase.AsCharacterBase->VisorToggled;
|
|
|
|
/// <summary> Manually set the state of the Visor for the given draw object. </summary>
|
|
/// <param name="human"> The draw object. </param>
|
|
/// <param name="on"> The desired state (true: toggled). </param>
|
|
/// <returns> Whether the state was changed. </returns>
|
|
public bool SetVisorState(Model human, bool on)
|
|
{
|
|
if (!human.IsHuman)
|
|
return false;
|
|
|
|
var oldState = GetVisorState(human);
|
|
Glamourer.Log.Verbose($"[SetVisorState] Invoked manually on 0x{human.Address:X} switching from {oldState} to {on}.");
|
|
if (oldState == on)
|
|
return false;
|
|
|
|
SetupVisorDetour(human, human.GetArmor(EquipSlot.Head).Set.Id, on);
|
|
return true;
|
|
}
|
|
|
|
private delegate void UpdateVisorDelegateInternal(nint humanPtr, ushort modelId, byte on);
|
|
|
|
private Hook<UpdateVisorDelegateInternal> _setupVisorHook;
|
|
|
|
private void SetupVisorDetour(nint human, ushort modelId, byte value)
|
|
{
|
|
var originalOn = value != 0;
|
|
var on = originalOn;
|
|
// Invoke an event that can change the requested value
|
|
// and also control whether the function should be called at all.
|
|
Event.Invoke(human, false, ref on);
|
|
|
|
Glamourer.Log.Verbose(
|
|
$"[SetVisorState] Invoked from game on 0x{human:X} switching to {on} (original {originalOn} from {value} with {modelId}).");
|
|
|
|
SetupVisorDetour(human, modelId, on);
|
|
}
|
|
|
|
/// <summary>
|
|
/// The SetupVisor function does not set the visor state for the draw object itself,
|
|
/// it only sets the "visor is changing" state to false.
|
|
/// So we wrap a manual change of that flag with the function call.
|
|
/// </summary>
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
|
private unsafe void SetupVisorDetour(Model human, ushort modelId, bool on)
|
|
{
|
|
human.AsCharacterBase->VisorToggled = on;
|
|
_setupVisorHook.Original(human.Address, modelId, on ? (byte)1 : (byte)0);
|
|
}
|
|
|
|
private unsafe Hook<UpdateVisorDelegateInternal> Create()
|
|
{
|
|
var hook = _interop.HookFromAddress<UpdateVisorDelegateInternal>((nint)Human.MemberFunctionPointers.SetupVisor, SetupVisorDetour);
|
|
hook.Enable();
|
|
return hook;
|
|
}
|
|
|
|
private void Restore()
|
|
{
|
|
_setupVisorHook.Dispose();
|
|
_setupVisorHook = Create();
|
|
}
|
|
}
|