mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2026-02-20 06:27:43 +01:00
.
This commit is contained in:
parent
7710cfadfa
commit
2d6fd6015d
88 changed files with 2304 additions and 383 deletions
|
|
@ -1,165 +0,0 @@
|
|||
using System;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.Types;
|
||||
using Glamourer.Customization;
|
||||
using Penumbra.GameData.Actors;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Penumbra.String;
|
||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
|
||||
{
|
||||
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 ?? IntPtr.Zero) };
|
||||
|
||||
public static implicit operator IntPtr(Actor actor)
|
||||
=> actor.Pointer == null ? IntPtr.Zero : (IntPtr)actor.Pointer;
|
||||
|
||||
public ActorIdentifier GetIdentifier(ActorManager actors)
|
||||
=> actors.FromObject((FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject*)Pointer, out _, true, true, false);
|
||||
|
||||
public bool Identifier(ActorManager actors, out ActorIdentifier ident)
|
||||
{
|
||||
if (Valid)
|
||||
{
|
||||
ident = GetIdentifier(actors);
|
||||
return true;
|
||||
}
|
||||
|
||||
ident = ActorIdentifier.Invalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> Pointer != null ? Utf8Name.ToString() : "Invalid";
|
||||
|
||||
public bool IsAvailable
|
||||
=> Pointer->GameObject.GetIsTargetable();
|
||||
|
||||
public bool IsHuman
|
||||
=> Pointer != null && Pointer->ModelCharaId == 0;
|
||||
|
||||
public ObjectKind ObjectKind
|
||||
{
|
||||
get => (ObjectKind)Pointer->GameObject.ObjectKind;
|
||||
set => Pointer->GameObject.ObjectKind = (byte)value;
|
||||
}
|
||||
|
||||
public ByteString Utf8Name
|
||||
=> new(Pointer->GameObject.Name);
|
||||
|
||||
public byte Job
|
||||
=> Pointer->ClassJob;
|
||||
|
||||
public DrawObject DrawObject
|
||||
=> (IntPtr)Pointer->GameObject.DrawObject;
|
||||
|
||||
public bool Valid
|
||||
=> Pointer != null;
|
||||
|
||||
public int Index
|
||||
=> Pointer->GameObject.ObjectIndex;
|
||||
|
||||
public uint ModelId
|
||||
{
|
||||
get => (uint)Pointer->ModelCharaId;
|
||||
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->DrawData.CustomizeData);
|
||||
|
||||
public CharacterEquip Equip
|
||||
=> new((CharacterArmor*)&Pointer->DrawData.Head);
|
||||
|
||||
public CharacterWeapon MainHand
|
||||
{
|
||||
get => *(CharacterWeapon*)&Pointer->DrawData.MainHandModel;
|
||||
set => *(CharacterWeapon*)&Pointer->DrawData.MainHandModel = value;
|
||||
}
|
||||
|
||||
public CharacterWeapon OffHand
|
||||
{
|
||||
get => *(CharacterWeapon*)&Pointer->DrawData.OffHandModel;
|
||||
set => *(CharacterWeapon*)&Pointer->DrawData.OffHandModel = value;
|
||||
}
|
||||
|
||||
public unsafe bool VisorEnabled
|
||||
{
|
||||
get => (*(byte*)(Address + Offsets.Character.VisorToggled) & Offsets.Character.Flags.IsVisorToggled) != 0;
|
||||
set => *(byte*)(Address + Offsets.Character.VisorToggled) = (byte)(value
|
||||
? *(byte*)(Address + Offsets.Character.VisorToggled) | Offsets.Character.Flags.IsVisorToggled
|
||||
: *(byte*)(Address + Offsets.Character.VisorToggled) & ~Offsets.Character.Flags.IsVisorToggled);
|
||||
}
|
||||
|
||||
public unsafe bool WeaponEnabled
|
||||
{
|
||||
get => (*(byte*)(Address + Offsets.Character.WeaponHidden1) & Offsets.Character.Flags.IsWeaponHidden1) == 0;
|
||||
set
|
||||
{
|
||||
ref var w1 = ref *(byte*)(Address + Offsets.Character.WeaponHidden1);
|
||||
ref var w2 = ref *(byte*)(Address + Offsets.Character.WeaponHidden2);
|
||||
if (value)
|
||||
{
|
||||
w1 = (byte)(w1 & ~Offsets.Character.Flags.IsWeaponHidden1);
|
||||
w2 = (byte)(w2 & ~Offsets.Character.Flags.IsWeaponHidden2);
|
||||
}
|
||||
else
|
||||
{
|
||||
w1 = (byte)(w1 | Offsets.Character.Flags.IsWeaponHidden1);
|
||||
w2 = (byte)(w2 | Offsets.Character.Flags.IsWeaponHidden2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsWet { get; set; }
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -1,24 +1,34 @@
|
|||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
/// <summary>
|
||||
/// Access the function the game uses to update customize data on the character screen.
|
||||
/// Changes in Race, body type or Gender are probably ignored.
|
||||
/// This operates on draw objects, not game objects.
|
||||
/// </summary>
|
||||
public unsafe class ChangeCustomizeService
|
||||
{
|
||||
public ChangeCustomizeService()
|
||||
=> SignatureHelper.Initialise(this);
|
||||
|
||||
public delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment);
|
||||
private delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment);
|
||||
|
||||
[Signature(Sigs.ChangeCustomize)]
|
||||
private readonly ChangeCustomizeDelegate _changeCustomize = null!;
|
||||
|
||||
public bool UpdateCustomize(Actor actor, CustomizeData customize)
|
||||
public bool UpdateCustomize(Model model, CustomizeData customize)
|
||||
{
|
||||
if (customize.Data == null || !actor.Valid || !actor.DrawObject.Valid)
|
||||
if (!model.IsHuman)
|
||||
return false;
|
||||
|
||||
return _changeCustomize(actor.DrawObject.Pointer, customize.Data, 1);
|
||||
Item.Log.Verbose($"[ChangeCustomize] Invoked on 0x{model.Address:X} with {customize}.");
|
||||
return _changeCustomize(model.AsHuman, customize.Data, 1);
|
||||
}
|
||||
|
||||
public bool UpdateCustomize(Actor actor, CustomizeData customize)
|
||||
=> UpdateCustomize(actor.Model, customize);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,100 +0,0 @@
|
|||
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 bool IsWet
|
||||
=> false;
|
||||
|
||||
public uint Type
|
||||
=> (*(delegate* unmanaged<Human*, uint>**)Pointer)[50](Pointer);
|
||||
|
||||
public Customize Customize
|
||||
=> *(Customize*)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;
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
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; }
|
||||
public bool IsWet { get; }
|
||||
}
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Data;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using Glamourer.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public class JobService : IDisposable
|
||||
{
|
||||
public readonly IReadOnlyDictionary<byte, Job> Jobs;
|
||||
public readonly IReadOnlyDictionary<ushort, JobGroup> JobGroups;
|
||||
|
||||
public event Action<Actor, Job>? JobChanged;
|
||||
|
||||
public JobService(DataManager gameData)
|
||||
{
|
||||
SignatureHelper.Initialise(this);
|
||||
Jobs = GameData.Jobs(gameData);
|
||||
JobGroups = GameData.JobGroups(gameData);
|
||||
_changeJobHook.Enable();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_changeJobHook.Dispose();
|
||||
}
|
||||
|
||||
private delegate void ChangeJobDelegate(nint data, uint job);
|
||||
|
||||
[Signature(Sigs.ChangeJob, DetourName = nameof(ChangeJobDetour))]
|
||||
private readonly Hook<ChangeJobDelegate> _changeJobHook = null!;
|
||||
|
||||
private void ChangeJobDetour(nint data, uint jobIndex)
|
||||
{
|
||||
_changeJobHook.Original(data, jobIndex);
|
||||
var actor = (Actor)(data - Offsets.Character.ClassJobContainer);
|
||||
var job = Jobs[(byte)jobIndex];
|
||||
Glamourer.Log.Excessive($"{actor} changed job to {job}");
|
||||
JobChanged?.Invoke(actor, job);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,160 +0,0 @@
|
|||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Dalamud.Game;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Game.ClientState.Objects;
|
||||
using Glamourer.Services;
|
||||
using Penumbra.GameData.Actors;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public readonly struct ActorData
|
||||
{
|
||||
public readonly List<Actor> Objects;
|
||||
public readonly string Label;
|
||||
|
||||
public bool Valid
|
||||
=> Objects.Count > 0;
|
||||
|
||||
public ActorData(Actor actor, string label)
|
||||
{
|
||||
Objects = new List<Actor> { actor };
|
||||
Label = label;
|
||||
}
|
||||
|
||||
public static readonly ActorData Invalid = new(false);
|
||||
|
||||
private ActorData(bool _)
|
||||
{
|
||||
Objects = new List<Actor>(0);
|
||||
Label = string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public class ObjectManager : IReadOnlyDictionary<ActorIdentifier, ActorData>
|
||||
{
|
||||
private readonly Framework _framework;
|
||||
private readonly ClientState _clientState;
|
||||
private readonly ObjectTable _objects;
|
||||
private readonly ActorService _actors;
|
||||
|
||||
public ObjectManager(Framework framework, ClientState clientState, ObjectTable objects, ActorService actors)
|
||||
{
|
||||
_framework = framework;
|
||||
_clientState = clientState;
|
||||
_objects = objects;
|
||||
_actors = actors;
|
||||
}
|
||||
|
||||
|
||||
public DateTime LastUpdate { get; private set; }
|
||||
|
||||
public bool IsInGPose { get; private set; }
|
||||
public ushort World { get; private set; }
|
||||
|
||||
private readonly Dictionary<ActorIdentifier, ActorData> _identifiers = new(200);
|
||||
|
||||
private void HandleIdentifier(ActorIdentifier identifier, Actor character)
|
||||
{
|
||||
if (!character.DrawObject || !identifier.IsValid)
|
||||
return;
|
||||
|
||||
if (!_identifiers.TryGetValue(identifier, out var data))
|
||||
{
|
||||
data = new ActorData(character, identifier.ToString());
|
||||
_identifiers[identifier] = data;
|
||||
}
|
||||
else
|
||||
{
|
||||
data.Objects.Add(character);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void Update()
|
||||
{
|
||||
var lastUpdate = _framework.LastUpdate;
|
||||
if (lastUpdate <= LastUpdate)
|
||||
return;
|
||||
|
||||
LastUpdate = lastUpdate;
|
||||
World = (ushort)(_clientState.LocalPlayer?.CurrentWorld.Id ?? 0u);
|
||||
_identifiers.Clear();
|
||||
|
||||
for (var i = 0; i < (int)ScreenActor.CutsceneStart; ++i)
|
||||
{
|
||||
Actor character = _objects.GetObjectAddress(i);
|
||||
if (character.Identifier(_actors.AwaitedService, out var identifier))
|
||||
HandleIdentifier(identifier, character);
|
||||
}
|
||||
|
||||
for (var i = (int)ScreenActor.CutsceneStart; i < (int)ScreenActor.CutsceneEnd; ++i)
|
||||
{
|
||||
Actor character = _objects.GetObjectAddress(i);
|
||||
if (!character.Identifier(_actors.AwaitedService, out var identifier))
|
||||
break;
|
||||
|
||||
HandleIdentifier(identifier, character);
|
||||
}
|
||||
|
||||
void AddSpecial(ScreenActor idx, string label)
|
||||
{
|
||||
Actor actor = _objects.GetObjectAddress((int)idx);
|
||||
if (actor.Identifier(_actors.AwaitedService, out var ident))
|
||||
{
|
||||
var data = new ActorData(actor, label);
|
||||
_identifiers.Add(ident, data);
|
||||
}
|
||||
}
|
||||
|
||||
AddSpecial(ScreenActor.CharacterScreen, "Character Screen Actor");
|
||||
AddSpecial(ScreenActor.ExamineScreen, "Examine Screen Actor");
|
||||
AddSpecial(ScreenActor.FittingRoom, "Fitting Room Actor");
|
||||
AddSpecial(ScreenActor.DyePreview, "Dye Preview Actor");
|
||||
AddSpecial(ScreenActor.Portrait, "Portrait Actor");
|
||||
AddSpecial(ScreenActor.Card6, "Card Actor 6");
|
||||
AddSpecial(ScreenActor.Card7, "Card Actor 7");
|
||||
AddSpecial(ScreenActor.Card8, "Card Actor 8");
|
||||
|
||||
for (var i = (int)ScreenActor.ScreenEnd; i < _objects.Length; ++i)
|
||||
{
|
||||
Actor character = _objects.GetObjectAddress(i);
|
||||
if (character.Identifier(_actors.AwaitedService, out var identifier))
|
||||
HandleIdentifier(identifier, character);
|
||||
}
|
||||
|
||||
var gPose = GPosePlayer;
|
||||
IsInGPose = gPose && gPose.Utf8Name.Length > 0;
|
||||
}
|
||||
|
||||
public Actor GPosePlayer
|
||||
=> _objects.GetObjectAddress((int)ScreenActor.GPosePlayer);
|
||||
|
||||
public Actor Player
|
||||
=> _objects.GetObjectAddress(0);
|
||||
|
||||
public IEnumerator<KeyValuePair<ActorIdentifier, ActorData>> GetEnumerator()
|
||||
=> _identifiers.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator()
|
||||
=> GetEnumerator();
|
||||
|
||||
public int Count
|
||||
=> _identifiers.Count;
|
||||
|
||||
public bool ContainsKey(ActorIdentifier key)
|
||||
=> _identifiers.ContainsKey(key);
|
||||
|
||||
public bool TryGetValue(ActorIdentifier key, out ActorData value)
|
||||
=> _identifiers.TryGetValue(key, out value);
|
||||
|
||||
public ActorData this[ActorIdentifier key]
|
||||
=> _identifiers[key];
|
||||
|
||||
public IEnumerable<ActorIdentifier> Keys
|
||||
=> _identifiers.Keys;
|
||||
|
||||
public IEnumerable<ActorData> Values
|
||||
=> _identifiers.Values;
|
||||
}
|
||||
142
Glamourer/Interop/Penumbra/PenumbraService.cs
Normal file
142
Glamourer/Interop/Penumbra/PenumbraService.cs
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
using System;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Plugin;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Penumbra.Api;
|
||||
using Penumbra.Api.Enums;
|
||||
using Penumbra.Api.Helpers;
|
||||
|
||||
namespace Glamourer.Interop.Penumbra;
|
||||
|
||||
public unsafe class PenumbraService : IDisposable
|
||||
{
|
||||
public const int RequiredPenumbraBreakingVersion = 4;
|
||||
public const int RequiredPenumbraFeatureVersion = 15;
|
||||
|
||||
private readonly DalamudPluginInterface _pluginInterface;
|
||||
private readonly EventSubscriber<ChangedItemType, uint> _tooltipSubscriber;
|
||||
private readonly EventSubscriber<MouseButton, ChangedItemType, uint> _clickSubscriber;
|
||||
private readonly EventSubscriber<nint, string, nint, nint, nint> _creatingCharacterBase;
|
||||
private readonly EventSubscriber<nint, string, nint> _createdCharacterBase;
|
||||
private ActionSubscriber<int, RedrawType> _redrawSubscriber;
|
||||
private FuncSubscriber<nint, (nint, string)> _drawObjectInfo;
|
||||
private FuncSubscriber<int, int> _cutsceneParent;
|
||||
|
||||
private readonly EventSubscriber _initializedEvent;
|
||||
private readonly EventSubscriber _disposedEvent;
|
||||
public bool Available { get; private set; }
|
||||
|
||||
public PenumbraService(DalamudPluginInterface pi)
|
||||
{
|
||||
_pluginInterface = pi;
|
||||
_initializedEvent = Ipc.Initialized.Subscriber(pi, Reattach);
|
||||
_disposedEvent = Ipc.Disposed.Subscriber(pi, Unattach);
|
||||
_tooltipSubscriber = Ipc.ChangedItemTooltip.Subscriber(pi);
|
||||
_clickSubscriber = Ipc.ChangedItemClick.Subscriber(pi);
|
||||
_createdCharacterBase = Ipc.CreatedCharacterBase.Subscriber(pi);
|
||||
_creatingCharacterBase = Ipc.CreatingCharacterBase.Subscriber(pi);
|
||||
Reattach();
|
||||
}
|
||||
|
||||
public event Action<MouseButton, ChangedItemType, uint> Click
|
||||
{
|
||||
add => _clickSubscriber.Event += value;
|
||||
remove => _clickSubscriber.Event -= value;
|
||||
}
|
||||
|
||||
public event Action<ChangedItemType, uint> Tooltip
|
||||
{
|
||||
add => _tooltipSubscriber.Event += value;
|
||||
remove => _tooltipSubscriber.Event -= value;
|
||||
}
|
||||
|
||||
|
||||
public event Action<nint, string, nint, nint, nint> CreatingCharacterBase
|
||||
{
|
||||
add => _creatingCharacterBase.Event += value;
|
||||
remove => _creatingCharacterBase.Event -= value;
|
||||
}
|
||||
|
||||
public event Action<nint, string, nint> CreatedCharacterBase
|
||||
{
|
||||
add => _createdCharacterBase.Event += value;
|
||||
remove => _createdCharacterBase.Event -= value;
|
||||
}
|
||||
|
||||
/// <summary> Obtain the game object corresponding to a draw object. </summary>
|
||||
public Actor GameObjectFromDrawObject(Model drawObject)
|
||||
=> Available ? _drawObjectInfo.Invoke(drawObject.Address).Item1 : Actor.Null;
|
||||
|
||||
/// <summary> Obtain the parent of a cutscene actor if it is known. </summary>
|
||||
public int CutsceneParent(int idx)
|
||||
=> Available ? _cutsceneParent.Invoke(idx) : -1;
|
||||
|
||||
/// <summary> Try to redraw the given actor. </summary>
|
||||
public void RedrawObject(Actor actor, RedrawType settings)
|
||||
{
|
||||
if (!actor || !Available)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_redrawSubscriber.Invoke(actor.AsObject->ObjectIndex, settings);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Debug($"Failure redrawing object:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Reattach to the currently running Penumbra IPC provider. Unattaches before if necessary. </summary>
|
||||
public void Reattach()
|
||||
{
|
||||
try
|
||||
{
|
||||
Unattach();
|
||||
|
||||
var (breaking, feature) = Ipc.ApiVersions.Subscriber(_pluginInterface).Invoke();
|
||||
if (breaking != RequiredPenumbraBreakingVersion || feature < RequiredPenumbraFeatureVersion)
|
||||
throw new Exception(
|
||||
$"Invalid Version {breaking}.{feature:D4}, required major Version {RequiredPenumbraBreakingVersion} with feature greater or equal to {RequiredPenumbraFeatureVersion}.");
|
||||
|
||||
_tooltipSubscriber.Enable();
|
||||
_clickSubscriber.Enable();
|
||||
_creatingCharacterBase.Enable();
|
||||
_createdCharacterBase.Enable();
|
||||
_drawObjectInfo = Ipc.GetDrawObjectInfo.Subscriber(_pluginInterface);
|
||||
_cutsceneParent = Ipc.GetCutsceneParentIndex.Subscriber(_pluginInterface);
|
||||
_redrawSubscriber = Ipc.RedrawObjectByIndex.Subscriber(_pluginInterface);
|
||||
Available = true;
|
||||
Item.Log.Debug("Glamourer attached to Penumbra.");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Item.Log.Debug($"Could not attach to Penumbra:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Unattach from the currently running Penumbra IPC provider. </summary>
|
||||
public void Unattach()
|
||||
{
|
||||
_tooltipSubscriber.Disable();
|
||||
_clickSubscriber.Disable();
|
||||
_creatingCharacterBase.Disable();
|
||||
_createdCharacterBase.Disable();
|
||||
if (Available)
|
||||
{
|
||||
Available = false;
|
||||
Item.Log.Debug("Glamourer detached from Penumbra.");
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Unattach();
|
||||
_tooltipSubscriber.Dispose();
|
||||
_clickSubscriber.Dispose();
|
||||
_creatingCharacterBase.Dispose();
|
||||
_createdCharacterBase.Dispose();
|
||||
_initializedEvent.Dispose();
|
||||
_disposedEvent.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using Glamourer.Api;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public unsafe partial class RedrawManager : IDisposable
|
||||
{
|
||||
private readonly ItemManager _items;
|
||||
private readonly ActorService _actors;
|
||||
private readonly FixedDesignManager _fixedDesignManager;
|
||||
private readonly ActiveDesign.Manager _stateManager;
|
||||
private readonly PenumbraAttach _penumbra;
|
||||
private readonly WeaponService _weapons;
|
||||
|
||||
public RedrawManager(FixedDesignManager fixedDesignManager, ActiveDesign.Manager stateManager, ItemManager items, ActorService actors,
|
||||
PenumbraAttach penumbra, WeaponService weapons)
|
||||
{
|
||||
SignatureHelper.Initialise(this);
|
||||
_fixedDesignManager = fixedDesignManager;
|
||||
_stateManager = stateManager;
|
||||
_items = items;
|
||||
_actors = actors;
|
||||
_penumbra = penumbra;
|
||||
_weapons = weapons;
|
||||
|
||||
_penumbra.CreatingCharacterBase += OnCharacterRedraw;
|
||||
_penumbra.CreatedCharacterBase += OnCharacterRedrawFinished;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
private void OnCharacterRedraw(Actor actor, uint* modelId, Customize customize, CharacterEquip equip)
|
||||
{
|
||||
// Do not apply anything if the game object model id does not correspond to the draw object model id.
|
||||
// This is the case if the actor is transformed to a different creature.
|
||||
if (actor.ModelId != *modelId)
|
||||
return;
|
||||
|
||||
// Check if we have a current design in use, or if not if the actor has a fixed design.
|
||||
var identifier = actor.GetIdentifier(_actors.AwaitedService);
|
||||
if (!_stateManager.TryGetValue(identifier, out var save))
|
||||
return;
|
||||
|
||||
// Compare game object customize data against draw object customize data for transformations.
|
||||
// Apply customization if they correspond and there is customization to apply.
|
||||
var gameObjectCustomize = new Customize(*(CustomizeData*)&actor.Pointer->DrawData.CustomizeData);
|
||||
if (gameObjectCustomize.Equals(customize))
|
||||
customize.Load(save.ModelData.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->DrawData.Head);
|
||||
if (gameObjectEquip.Equals(equip))
|
||||
{
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
(_, equip[slot]) =
|
||||
_items.ResolveRestrictedGear(save.ModelData.Armor(slot), slot, customize.Race, customize.Gender);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCharacterRedraw(IntPtr gameObject, string collection, IntPtr modelId, IntPtr customize, IntPtr equipData)
|
||||
{
|
||||
try
|
||||
{
|
||||
OnCharacterRedraw(gameObject, (uint*)modelId, new Customize(*(CustomizeData*)customize),
|
||||
new CharacterEquip((CharacterArmor*)equipData));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
PluginLog.Error($"Error on new draw object creation:\n{e}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnCharacterRedrawFinished(IntPtr gameObject, string collection, IntPtr drawObject)
|
||||
{
|
||||
//SetVisor((Human*)drawObject, true);
|
||||
}
|
||||
}
|
||||
97
Glamourer/Interop/Structs/Actor.cs
Normal file
97
Glamourer/Interop/Structs/Actor.cs
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
using Penumbra.GameData.Actors;
|
||||
using System;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Object;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop.Structs;
|
||||
|
||||
public readonly unsafe struct Actor : IEquatable<Actor>
|
||||
{
|
||||
private Actor(nint address)
|
||||
=> Address = address;
|
||||
|
||||
public static readonly Actor Null = new(nint.Zero);
|
||||
|
||||
public readonly nint Address;
|
||||
|
||||
public GameObject* AsObject
|
||||
=> (GameObject*)Address;
|
||||
|
||||
public Character* AsCharacter
|
||||
=> (Character*)Address;
|
||||
|
||||
public bool Valid
|
||||
=> Address != nint.Zero;
|
||||
|
||||
public bool IsCharacter
|
||||
=> Valid && AsObject->IsCharacter();
|
||||
|
||||
public static implicit operator Actor(nint? pointer)
|
||||
=> new(pointer ?? nint.Zero);
|
||||
|
||||
public static implicit operator Actor(GameObject* pointer)
|
||||
=> new((nint)pointer);
|
||||
|
||||
public static implicit operator Actor(Character* pointer)
|
||||
=> new((nint)pointer);
|
||||
|
||||
public static implicit operator nint(Actor actor)
|
||||
=> actor.Address;
|
||||
|
||||
public ActorIdentifier GetIdentifier(ActorManager actors)
|
||||
=> actors.FromObject(AsObject, out _, true, true, false);
|
||||
|
||||
public bool Identifier(ActorManager actors, out ActorIdentifier ident)
|
||||
{
|
||||
if (Valid)
|
||||
{
|
||||
ident = GetIdentifier(actors);
|
||||
return ident.IsValid;
|
||||
}
|
||||
|
||||
ident = ActorIdentifier.Invalid;
|
||||
return false;
|
||||
}
|
||||
|
||||
public Model Model
|
||||
=> Valid ? AsObject->DrawObject : null;
|
||||
|
||||
public static implicit operator bool(Actor actor)
|
||||
=> actor.Address != nint.Zero;
|
||||
|
||||
public static bool operator true(Actor actor)
|
||||
=> actor.Address != nint.Zero;
|
||||
|
||||
public static bool operator false(Actor actor)
|
||||
=> actor.Address == nint.Zero;
|
||||
|
||||
public static bool operator !(Actor actor)
|
||||
=> actor.Address == nint.Zero;
|
||||
|
||||
public bool Equals(Actor other)
|
||||
=> Address == other.Address;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is Actor other && Equals(other);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> Address.GetHashCode();
|
||||
|
||||
public static bool operator ==(Actor lhs, Actor rhs)
|
||||
=> lhs.Address == rhs.Address;
|
||||
|
||||
public static bool operator !=(Actor lhs, Actor rhs)
|
||||
=> lhs.Address != rhs.Address;
|
||||
|
||||
/// <summary> Only valid for characters. </summary>
|
||||
public CharacterArmor GetArmor(EquipSlot slot)
|
||||
=> ((CharacterArmor*)&AsCharacter->DrawData.Head)[slot.ToIndex()];
|
||||
|
||||
public CharacterWeapon GetMainhand()
|
||||
=> *(CharacterWeapon*)&AsCharacter->DrawData.MainHandModel;
|
||||
|
||||
public CharacterWeapon GetOffhand()
|
||||
=> *(CharacterWeapon*)&AsCharacter->DrawData.OffHandModel;
|
||||
}
|
||||
26
Glamourer/Interop/Structs/ActorData.cs
Normal file
26
Glamourer/Interop/Structs/ActorData.cs
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
using System.Collections.Generic;
|
||||
|
||||
namespace Glamourer.Interop.Structs;
|
||||
|
||||
public readonly struct ActorData
|
||||
{
|
||||
public readonly List<Actor> Objects;
|
||||
public readonly string Label;
|
||||
|
||||
public bool Valid
|
||||
=> Objects.Count > 0;
|
||||
|
||||
public ActorData(Actor actor, string label)
|
||||
{
|
||||
Objects = new List<Actor> { actor };
|
||||
Label = label;
|
||||
}
|
||||
|
||||
public static readonly ActorData Invalid = new(false);
|
||||
|
||||
private ActorData(bool _)
|
||||
{
|
||||
Objects = new List<Actor>(0);
|
||||
Label = string.Empty;
|
||||
}
|
||||
}
|
||||
92
Glamourer/Interop/Structs/Model.cs
Normal file
92
Glamourer/Interop/Structs/Model.cs
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
using System;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using ObjectType = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.ObjectType;
|
||||
|
||||
namespace Glamourer.Interop.Structs;
|
||||
|
||||
public readonly unsafe struct Model : IEquatable<Model>
|
||||
{
|
||||
private Model(nint address)
|
||||
=> Address = address;
|
||||
|
||||
public readonly nint Address;
|
||||
|
||||
public static readonly Model Null = new(0);
|
||||
|
||||
public DrawObject* AsDrawObject
|
||||
=> (DrawObject*)Address;
|
||||
|
||||
public CharacterBase* AsCharacterBase
|
||||
=> (CharacterBase*)Address;
|
||||
|
||||
public Human* AsHuman
|
||||
=> (Human*)Address;
|
||||
|
||||
public static implicit operator Model(nint? pointer)
|
||||
=> new(pointer ?? nint.Zero);
|
||||
|
||||
public static implicit operator Model(DrawObject* pointer)
|
||||
=> new((nint)pointer);
|
||||
|
||||
public static implicit operator Model(Human* pointer)
|
||||
=> new((nint)pointer);
|
||||
|
||||
public static implicit operator Model(CharacterBase* pointer)
|
||||
=> new((nint)pointer);
|
||||
|
||||
public static implicit operator nint(Model model)
|
||||
=> model.Address;
|
||||
|
||||
public bool Valid
|
||||
=> Address != nint.Zero;
|
||||
|
||||
public bool IsCharacterBase
|
||||
=> Valid && AsDrawObject->Object.GetObjectType() == ObjectType.CharacterBase;
|
||||
|
||||
public bool IsHuman
|
||||
=> IsCharacterBase && AsCharacterBase->GetModelType() == CharacterBase.ModelType.Human;
|
||||
|
||||
public static implicit operator bool(Model actor)
|
||||
=> actor.Address != nint.Zero;
|
||||
|
||||
public static bool operator true(Model actor)
|
||||
=> actor.Address != nint.Zero;
|
||||
|
||||
public static bool operator false(Model actor)
|
||||
=> actor.Address == nint.Zero;
|
||||
|
||||
public static bool operator !(Model actor)
|
||||
=> actor.Address == nint.Zero;
|
||||
|
||||
public bool Equals(Model other)
|
||||
=> Address == other.Address;
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is Model other && Equals(other);
|
||||
|
||||
public override int GetHashCode()
|
||||
=> Address.GetHashCode();
|
||||
|
||||
public static bool operator ==(Model lhs, Model rhs)
|
||||
=> lhs.Address == rhs.Address;
|
||||
|
||||
public static bool operator !=(Model lhs, Model rhs)
|
||||
=> lhs.Address != rhs.Address;
|
||||
|
||||
/// <summary> Only valid for humans. </summary>
|
||||
public CharacterArmor GetArmor(EquipSlot slot)
|
||||
=> ((CharacterArmor*)AsHuman->EquipSlotData)[slot.ToIndex()];
|
||||
|
||||
public CharacterWeapon GetMainhand()
|
||||
{
|
||||
var weapon = AsDrawObject->Object.ChildObject;
|
||||
if (weapon == null)
|
||||
return CharacterWeapon.Empty;
|
||||
weapon
|
||||
}
|
||||
|
||||
public CharacterWeapon GetOffhand()
|
||||
=> *(CharacterWeapon*)&AsCharacter->DrawData.OffHandModel;
|
||||
}
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
|
|
@ -9,8 +10,11 @@ namespace Glamourer.Interop;
|
|||
|
||||
public unsafe class UpdateSlotService : IDisposable
|
||||
{
|
||||
public UpdateSlotService()
|
||||
public readonly UpdatedSlot Event;
|
||||
|
||||
public UpdateSlotService(UpdatedSlot updatedSlot)
|
||||
{
|
||||
Event = updatedSlot;
|
||||
SignatureHelper.Initialise(this);
|
||||
_flagSlotForUpdateHook.Enable();
|
||||
}
|
||||
|
|
@ -19,59 +23,41 @@ public unsafe class UpdateSlotService : IDisposable
|
|||
=> _flagSlotForUpdateHook.Dispose();
|
||||
|
||||
private delegate ulong FlagSlotForUpdateDelegateIntern(nint drawObject, uint slot, CharacterArmor* data);
|
||||
public delegate void FlagSlotForUpdateDelegate(DrawObject drawObject, EquipSlot slot, ref CharacterArmor item);
|
||||
|
||||
// This gets called when one of the ten equip items of an existing draw object gets changed.
|
||||
[Signature(Sigs.FlagSlotForUpdate, DetourName = nameof(FlagSlotForUpdateDetour))]
|
||||
private readonly Hook<FlagSlotForUpdateDelegateIntern> _flagSlotForUpdateHook = null!;
|
||||
|
||||
public event FlagSlotForUpdateDelegate? EquipUpdate;
|
||||
|
||||
public ulong FlagSlotForUpdateInterop(DrawObject drawObject, EquipSlot slot, CharacterArmor armor)
|
||||
=> _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor);
|
||||
|
||||
public void UpdateSlot(DrawObject drawObject, EquipSlot slot, CharacterArmor data)
|
||||
public void UpdateSlot(Model drawObject, EquipSlot slot, CharacterArmor data)
|
||||
{
|
||||
InvokeFlagSlotEvent(drawObject, slot, ref data);
|
||||
if (!drawObject.IsCharacterBase)
|
||||
return;
|
||||
FlagSlotForUpdateInterop(drawObject, slot, data);
|
||||
}
|
||||
|
||||
public void UpdateStain(DrawObject drawObject, EquipSlot slot, StainId stain)
|
||||
public void UpdateArmor(Model drawObject, EquipSlot slot, CharacterArmor data)
|
||||
{
|
||||
var armor = drawObject.Equip[slot] with { Stain = stain };
|
||||
UpdateSlot(drawObject, slot, armor);
|
||||
if (!drawObject.IsCharacterBase)
|
||||
return;
|
||||
|
||||
FlagSlotForUpdateInterop(drawObject, slot, data.With(drawObject.GetArmor(slot).Stain));
|
||||
}
|
||||
|
||||
public void UpdateStain(Model drawObject, EquipSlot slot, StainId stain)
|
||||
{
|
||||
if (!drawObject.IsHuman)
|
||||
return;
|
||||
|
||||
FlagSlotForUpdateInterop(drawObject, slot, drawObject.GetArmor(slot).With(stain));
|
||||
}
|
||||
|
||||
private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data)
|
||||
{
|
||||
var slot = slotIdx.ToEquipSlot();
|
||||
InvokeFlagSlotEvent(drawObject, slot, ref *data);
|
||||
return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
|
||||
var slot = slotIdx.ToEquipSlot();
|
||||
var returnValue = ulong.MaxValue;
|
||||
Event.Invoke(drawObject, slot, ref *data, ref returnValue);
|
||||
return returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue;
|
||||
}
|
||||
|
||||
private void InvokeFlagSlotEvent(DrawObject drawObject, EquipSlot slot, ref CharacterArmor armor)
|
||||
{
|
||||
if (EquipUpdate == null)
|
||||
{
|
||||
Glamourer.Log.Excessive(
|
||||
$"{slot} updated on 0x{drawObject.Address:X} to {armor.Set.Value}-{armor.Variant} with stain {armor.Stain.Value}.");
|
||||
return;
|
||||
}
|
||||
|
||||
var iv = armor;
|
||||
foreach (var del in EquipUpdate.GetInvocationList().OfType<FlagSlotForUpdateDelegate>())
|
||||
{
|
||||
try
|
||||
{
|
||||
del(drawObject, slot, ref armor);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Log.Error($"Could not invoke {nameof(EquipUpdate)} Subscriber:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
Glamourer.Log.Excessive(
|
||||
$"{slot} updated on 0x{drawObject.Address:X} to {armor.Set.Value}-{armor.Variant} with stain {armor.Stain.Value}, initial armor was {iv.Set.Value}-{iv.Variant} with stain {iv.Stain.Value}.");
|
||||
}
|
||||
private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor)
|
||||
=> _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Penumbra.GameData.Structs;
|
||||
using Glamourer.Events;
|
||||
using Glamourer.Interop.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public class VisorService : IDisposable
|
||||
{
|
||||
public VisorService()
|
||||
public readonly VisorStateChanged Event;
|
||||
|
||||
public VisorService(VisorStateChanged visorStateChanged)
|
||||
{
|
||||
Event = visorStateChanged;
|
||||
SignatureHelper.Initialise(this);
|
||||
_setupVisorHook.Enable();
|
||||
}
|
||||
|
|
@ -18,61 +21,67 @@ public class VisorService : IDisposable
|
|||
public void Dispose()
|
||||
=> _setupVisorHook.Dispose();
|
||||
|
||||
public static unsafe bool GetVisorState(nint humanPtr)
|
||||
/// <summary> Obtain the current state of the Visor for the given draw object (true: toggled). </summary>
|
||||
public unsafe bool GetVisorState(Model characterBase)
|
||||
{
|
||||
if (humanPtr == IntPtr.Zero)
|
||||
if (!characterBase.IsCharacterBase)
|
||||
return false;
|
||||
|
||||
var data = (Human*)humanPtr;
|
||||
var flags = &data->CharacterBase.UnkFlags_01;
|
||||
return (*flags & Offsets.DrawObjectVisorStateFlag) != 0;
|
||||
// TODO: use client structs.
|
||||
return (characterBase.AsCharacterBase->UnkFlags_01 & Offsets.DrawObjectVisorStateFlag) != 0;
|
||||
}
|
||||
|
||||
public unsafe void SetVisorState(nint humanPtr, bool on)
|
||||
/// <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 unsafe bool SetVisorState(Model human, bool on)
|
||||
{
|
||||
if (humanPtr == IntPtr.Zero)
|
||||
return;
|
||||
if (!human.IsHuman)
|
||||
return false;
|
||||
|
||||
var data = (Human*)humanPtr;
|
||||
_setupVisorHook.Original(humanPtr, (ushort) data->HeadSetID, on);
|
||||
var oldState = GetVisorState(human);
|
||||
Item.Log.Verbose($"[SetVisorState] Invoked manually on 0x{human.Address:X} switching from {oldState} to {on}.");
|
||||
if (oldState == on)
|
||||
return false;
|
||||
|
||||
|
||||
SetupVisorHook(human, (ushort)human.AsHuman->HeadSetID, on);
|
||||
return true;
|
||||
}
|
||||
|
||||
private delegate void UpdateVisorDelegateInternal(nint humanPtr, ushort modelId, bool on);
|
||||
public delegate void UpdateVisorDelegate(DrawObject human, SetId modelId, ref bool on);
|
||||
|
||||
[Signature(Penumbra.GameData.Sigs.SetupVisor, DetourName = nameof(SetupVisorDetour))]
|
||||
[Signature(global::Penumbra.GameData.Sigs.SetupVisor, DetourName = nameof(SetupVisorDetour))]
|
||||
private readonly Hook<UpdateVisorDelegateInternal> _setupVisorHook = null!;
|
||||
|
||||
public event UpdateVisorDelegate? VisorUpdate;
|
||||
|
||||
private void SetupVisorDetour(nint humanPtr, ushort modelId, bool on)
|
||||
private void SetupVisorDetour(nint human, ushort modelId, bool on)
|
||||
{
|
||||
InvokeVisorEvent(humanPtr, modelId, ref on);
|
||||
_setupVisorHook.Original(humanPtr, modelId, on);
|
||||
var callOriginal = true;
|
||||
var originalOn = on;
|
||||
// Invoke an event that can change the requested value
|
||||
// and also control whether the function should be called at all.
|
||||
Event.Invoke(human, ref on, ref callOriginal);
|
||||
|
||||
Item.Log.Excessive(
|
||||
$"[SetVisorState] Invoked from game on 0x{human:X} switching to {on} (original {originalOn}, call original {callOriginal}).");
|
||||
|
||||
if (callOriginal)
|
||||
SetupVisorHook(human, modelId, on);
|
||||
}
|
||||
|
||||
private void InvokeVisorEvent(DrawObject drawObject, SetId modelId, ref bool 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 SetupVisorHook(Model human, ushort modelId, bool on)
|
||||
{
|
||||
if (VisorUpdate == null)
|
||||
{
|
||||
Glamourer.Log.Excessive($"Visor setup on 0x{drawObject.Address:X} with {modelId.Value}, setting to {on}.");
|
||||
return;
|
||||
}
|
||||
|
||||
var initialValue = on;
|
||||
foreach (var del in VisorUpdate.GetInvocationList().OfType<UpdateVisorDelegate>())
|
||||
{
|
||||
try
|
||||
{
|
||||
del(drawObject, modelId, ref on);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Glamourer.Log.Error($"Could not invoke {nameof(VisorUpdate)} Subscriber:\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
Glamourer.Log.Excessive(
|
||||
$"Visor setup on 0x{drawObject.Address:X} with {modelId.Value}, setting to {on}, initial call was {initialValue}.");
|
||||
// TODO: use client structs.
|
||||
human.AsCharacterBase->UnkFlags_01 = (byte)(on
|
||||
? human.AsCharacterBase->UnkFlags_01 | Offsets.DrawObjectVisorStateFlag
|
||||
: human.AsCharacterBase->UnkFlags_01 & ~Offsets.DrawObjectVisorStateFlag);
|
||||
_setupVisorHook.Original(human.Address, modelId, on);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
|
|
@ -13,6 +13,7 @@ public unsafe class WeaponService : IDisposable
|
|||
public WeaponService()
|
||||
{
|
||||
SignatureHelper.Initialise(this);
|
||||
_loadWeaponHook = Hook<LoadWeaponDelegate>.FromAddress((nint) DrawDataContainer.MemberFunctionPointers.LoadWeapon, LoadWeaponDetour);
|
||||
_loadWeaponHook.Enable();
|
||||
}
|
||||
|
||||
|
|
@ -21,68 +22,18 @@ public unsafe class WeaponService : IDisposable
|
|||
_loadWeaponHook.Dispose();
|
||||
}
|
||||
|
||||
public static readonly int CharacterWeaponOffset = (int)Marshal.OffsetOf<Character>("DrawData");
|
||||
private delegate void LoadWeaponDelegate(DrawDataContainer* drawData, uint slot, ulong weapon, byte redrawOnEquality, byte unk2, byte skipGameObject, byte unk4);
|
||||
|
||||
public delegate void LoadWeaponDelegate(nint offsetCharacter, uint slot, ulong weapon, byte redrawOnEquality, byte unk2,
|
||||
byte skipGameObject,
|
||||
byte unk4);
|
||||
private readonly Hook<LoadWeaponDelegate> _loadWeaponHook;
|
||||
|
||||
// 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(Penumbra.GameData.Sigs.WeaponReload, DetourName = nameof(LoadWeaponDetour))]
|
||||
private readonly Hook<LoadWeaponDelegate> _loadWeaponHook = null!;
|
||||
|
||||
private void LoadWeaponDetour(nint characterOffset, uint slot, ulong weapon, byte redrawOnEquality, byte unk2, byte skipGameObject,
|
||||
private void LoadWeaponDetour(DrawDataContainer* drawData, 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(_actors.AwaitedService);
|
||||
// if (_fixedDesignManager.TryGetDesign(identifier, out var save))
|
||||
// {
|
||||
// PluginLog.Information($"Loaded weapon from fixed design for {identifier}.");
|
||||
// weapon = slot switch
|
||||
// {
|
||||
// 0 => save.WeaponMain.Model.Value,
|
||||
// 1 => save.WeaponOff.Model.Value,
|
||||
// _ => weapon,
|
||||
// };
|
||||
// }
|
||||
// else if (redrawOnEquality == 1 && _stateManager.TryGetValue(identifier, out var save2))
|
||||
// {
|
||||
// PluginLog.Information($"Loaded weapon from current design for {identifier}.");
|
||||
// //switch (slot)
|
||||
// //{
|
||||
// // case 0:
|
||||
// // save2.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}");
|
||||
//}
|
||||
|
||||
var actor = (Actor) (nint)drawData->Unk8;
|
||||
|
||||
// First call the regular function.
|
||||
_loadWeaponHook.Original(characterOffset, slot, weapon, redrawOnEquality, unk2, skipGameObject, unk4);
|
||||
Glamourer.Log.Excessive($"Weapon reloaded for {(Actor)(characterOffset - CharacterWeaponOffset)} with attributes {slot} {weapon:X14}, {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);
|
||||
_loadWeaponHook.Original(drawData, slot, weapon, redrawOnEquality, unk2, skipGameObject, unk4);
|
||||
Item.Log.Information($"Weapon reloaded for 0x{actor.Address:X} with attributes {slot} {weapon:X14}, {redrawOnEquality}, {unk2}, {skipGameObject}, {unk4}");
|
||||
}
|
||||
|
||||
// Load a specific weapon for a character by its data and slot.
|
||||
|
|
@ -91,14 +42,14 @@ public unsafe class WeaponService : IDisposable
|
|||
switch (slot)
|
||||
{
|
||||
case EquipSlot.MainHand:
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 0, weapon.Value, 0, 0, 1, 0);
|
||||
LoadWeaponDetour(&character.AsCharacter->DrawData, 0, weapon.Value, 0, 0, 1, 0);
|
||||
return;
|
||||
case EquipSlot.OffHand:
|
||||
LoadWeaponDetour(character.Address + CharacterWeaponOffset, 1, weapon.Value, 0, 0, 1, 0);
|
||||
LoadWeaponDetour(&character.AsCharacter->DrawData, 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);
|
||||
LoadWeaponDetour(&character.AsCharacter->DrawData, 0, weapon.Value, 0, 0, 1, 0);
|
||||
LoadWeaponDetour(&character.AsCharacter->DrawData, 1, CharacterWeapon.Empty.Value, 0, 0, 1, 0);
|
||||
return;
|
||||
// function can also be called with '2', but does not seem to ever be.
|
||||
}
|
||||
|
|
@ -107,14 +58,14 @@ public unsafe class WeaponService : IDisposable
|
|||
// 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);
|
||||
LoadWeaponDetour(&character.AsCharacter->DrawData, 0, main.Value, 1, 0, 1, 0);
|
||||
LoadWeaponDetour(&character.AsCharacter->DrawData, 1, off.Value, 1, 0, 1, 0);
|
||||
}
|
||||
|
||||
public void LoadStain(Actor character, EquipSlot slot, StainId stain)
|
||||
{
|
||||
var weapon = slot == EquipSlot.OffHand ? character.OffHand : character.MainHand;
|
||||
weapon.Stain = stain;
|
||||
var value = slot == EquipSlot.OffHand ? character.AsCharacter->DrawData.OffHandModel : character.AsCharacter->DrawData.MainHandModel;
|
||||
var weapon = new CharacterWeapon(value.Value) { Stain = stain.Value };
|
||||
LoadWeapon(character, slot, weapon);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue