mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2026-02-23 07:57:46 +01:00
.
This commit is contained in:
parent
85adc3626e
commit
10631341cb
31 changed files with 1883 additions and 1507 deletions
|
|
@ -39,6 +39,9 @@ public unsafe partial struct Actor : IEquatable<Actor>, IDesignable
|
|||
return false;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
=> Pointer != null ? Utf8Name.ToString() : "Invalid";
|
||||
|
||||
public bool IsAvailable
|
||||
=> Pointer->GameObject.GetIsTargetable();
|
||||
|
||||
|
|
|
|||
24
Glamourer/Interop/ChangeCustomizeService.cs
Normal file
24
Glamourer/Interop/ChangeCustomizeService.cs
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public unsafe class ChangeCustomizeService
|
||||
{
|
||||
public ChangeCustomizeService()
|
||||
=> SignatureHelper.Initialise(this);
|
||||
|
||||
public delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment);
|
||||
|
||||
[Signature(Sigs.ChangeCustomize)]
|
||||
private readonly ChangeCustomizeDelegate _changeCustomize = null!;
|
||||
|
||||
public bool UpdateCustomize(Actor actor, CustomizeData customize)
|
||||
{
|
||||
if (customize.Data == null || !actor.Valid || !actor.DrawObject.Valid)
|
||||
return false;
|
||||
|
||||
return _changeCustomize(actor.DrawObject.Pointer, customize.Data, 1);
|
||||
}
|
||||
}
|
||||
43
Glamourer/Interop/JobService.cs
Normal file
43
Glamourer/Interop/JobService.cs
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
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,14 +0,0 @@
|
|||
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
|
||||
{
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
using System;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using Glamourer.Designs;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public unsafe partial class RedrawManager
|
||||
{
|
||||
private delegate ulong FlagSlotForUpdateDelegate(nint drawObject, uint slot, CharacterArmor* data);
|
||||
|
||||
// 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<FlagSlotForUpdateDelegate> _flagSlotForUpdateHook = null!;
|
||||
|
||||
public void UpdateSlot(DrawObject drawObject, EquipSlot slot, CharacterArmor data)
|
||||
=> FlagSlotForUpdateDetourBase(drawObject.Address, slot.ToIndex(), &data, true);
|
||||
|
||||
public void UpdateStain(DrawObject drawObject, EquipSlot slot, StainId stain)
|
||||
{
|
||||
var armor = drawObject.Equip[slot] with { Stain = stain};
|
||||
UpdateSlot(drawObject, slot, armor);
|
||||
}
|
||||
|
||||
private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data)
|
||||
=> FlagSlotForUpdateDetourBase(drawObject, slotIdx, data, false);
|
||||
|
||||
private ulong FlagSlotForUpdateDetourBase(nint drawObject, uint slotIdx, CharacterArmor* data, bool manual)
|
||||
{
|
||||
// try
|
||||
// {
|
||||
// var slot = slotIdx.ToEquipSlot();
|
||||
// Glamourer.Log.Verbose(
|
||||
// $"Flagged slot {slot} of 0x{(ulong)drawObject:X} for update with {data->Set.Value}-{data->Variant} (Stain {data->Stain.Value}).");
|
||||
// HandleEquipUpdate(drawObject, slot, ref *data, manual);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// Glamourer.Log.Error($"Error invoking SlotUpdate:\n{ex}");
|
||||
// }
|
||||
//
|
||||
return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
|
||||
|
||||
//try
|
||||
//{
|
||||
// var actor = Glamourer.Penumbra.GameObjectFromDrawObject((IntPtr)drawObject);
|
||||
// var identifier = actor.GetIdentifier();
|
||||
//
|
||||
// if (_fixedDesigns.TryGetDesign(identifier, out var design))
|
||||
// {
|
||||
// PluginLog.Information($"Loaded {slot} from fixed design for {identifier}.");
|
||||
// (var replaced, *data) =
|
||||
// Glamourer.Items.RestrictedGear.ResolveRestricted(design.Armor(slot).Model, 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.Items.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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
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(Penumbra.GameData.Sigs.WeaponReload, 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(_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}");
|
||||
}
|
||||
|
||||
// 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,224 +1,45 @@
|
|||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Game.ClientState;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Glamourer.Api;
|
||||
using Glamourer.Customization;
|
||||
using Glamourer.Services;
|
||||
using Glamourer.State;
|
||||
using Glamourer.Structs;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public partial class Interop : IDisposable
|
||||
{
|
||||
private readonly JobService _jobService;
|
||||
|
||||
public Interop(JobService jobService)
|
||||
{
|
||||
_jobService = jobService;
|
||||
SignatureHelper.Initialise(this);
|
||||
_changeJobHook.Enable();
|
||||
_flagSlotForUpdateHook.Enable();
|
||||
_setupVisorHook.Enable();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_changeJobHook.Dispose();
|
||||
_flagSlotForUpdateHook.Dispose();
|
||||
_setupVisorHook.Dispose();
|
||||
}
|
||||
|
||||
public static unsafe bool GetVisorState(nint humanPtr)
|
||||
{
|
||||
if (humanPtr == IntPtr.Zero)
|
||||
return false;
|
||||
|
||||
var data = (Human*)humanPtr;
|
||||
var flags = &data->CharacterBase.UnkFlags_01;
|
||||
return (*flags & Offsets.DrawObjectVisorStateFlag) != 0;
|
||||
}
|
||||
|
||||
public static unsafe void SetVisorState(nint humanPtr, bool on)
|
||||
{
|
||||
if (humanPtr == IntPtr.Zero)
|
||||
return;
|
||||
|
||||
var data = (Human*)humanPtr;
|
||||
var flags = &data->CharacterBase.UnkFlags_01;
|
||||
var state = (*flags & Offsets.DrawObjectVisorStateFlag) != 0;
|
||||
if (state == on)
|
||||
return;
|
||||
|
||||
var newFlag = (byte)(on ? *flags | Offsets.DrawObjectVisorStateFlag : *flags & ~Offsets.DrawObjectVisorStateFlag);
|
||||
*flags = (byte)(newFlag | Offsets.DrawObjectVisorToggleFlag);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class Interop
|
||||
{
|
||||
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))]
|
||||
private readonly Hook<UpdateVisorDelegateInternal> _setupVisorHook = null!;
|
||||
|
||||
public event UpdateVisorDelegate? VisorUpdate;
|
||||
|
||||
private void SetupVisorDetour(nint humanPtr, ushort modelId, bool on)
|
||||
{
|
||||
InvokeVisorEvent(humanPtr, modelId, ref on);
|
||||
_setupVisorHook.Original(humanPtr, modelId, on);
|
||||
}
|
||||
|
||||
private void InvokeVisorEvent(DrawObject drawObject, SetId modelId, ref bool on)
|
||||
{
|
||||
if (VisorUpdate == null)
|
||||
{
|
||||
Glamourer.Log.Verbose($"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.Verbose(
|
||||
$"Visor setup on 0x{drawObject.Address:X} with {modelId.Value}, setting to {on}, initial call was {initialValue}.");
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe partial class Interop
|
||||
{
|
||||
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)
|
||||
{
|
||||
InvokeFlagSlotEvent(drawObject, slot, ref data);
|
||||
FlagSlotForUpdateInterop(drawObject, slot, data);
|
||||
}
|
||||
|
||||
public void UpdateStain(DrawObject drawObject, EquipSlot slot, StainId stain)
|
||||
{
|
||||
var armor = drawObject.Equip[slot] with { Stain = stain };
|
||||
UpdateSlot(drawObject, slot, armor);
|
||||
}
|
||||
|
||||
private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data)
|
||||
{
|
||||
var slot = slotIdx.ToEquipSlot();
|
||||
InvokeFlagSlotEvent(drawObject, slot, ref *data);
|
||||
return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
|
||||
}
|
||||
|
||||
private void InvokeFlagSlotEvent(DrawObject drawObject, EquipSlot slot, ref CharacterArmor armor)
|
||||
{
|
||||
if (EquipUpdate == null)
|
||||
{
|
||||
Glamourer.Log.Verbose(
|
||||
$"{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.Verbose(
|
||||
$"{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}.");
|
||||
}
|
||||
}
|
||||
|
||||
public unsafe partial class Interop
|
||||
{
|
||||
public delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment);
|
||||
|
||||
[Signature(Sigs.ChangeCustomize)]
|
||||
private readonly ChangeCustomizeDelegate _changeCustomize = null!;
|
||||
|
||||
public bool UpdateCustomize(Actor actor, CustomizeData customize)
|
||||
{
|
||||
Debug.Assert(customize.Data != null, "Customize was invalid.");
|
||||
|
||||
if (!actor.Valid || !actor.DrawObject.Valid)
|
||||
return false;
|
||||
|
||||
return _changeCustomize(actor.DrawObject.Pointer, customize.Data, 1);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class Interop
|
||||
{
|
||||
private delegate void ChangeJobDelegate(IntPtr data, uint job);
|
||||
|
||||
[Signature(Sigs.ChangeJob, DetourName = nameof(ChangeJobDetour))]
|
||||
private readonly Hook<ChangeJobDelegate> _changeJobHook = null!;
|
||||
|
||||
private void ChangeJobDetour(IntPtr data, uint job)
|
||||
{
|
||||
_changeJobHook.Original(data, job);
|
||||
JobChanged?.Invoke(data - Offsets.Character.ClassJobContainer, _jobService.Jobs[(byte)job]);
|
||||
}
|
||||
|
||||
public event Action<Actor, Job>? JobChanged;
|
||||
}
|
||||
|
||||
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)
|
||||
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;
|
||||
_flagSlotForUpdateHook.Enable();
|
||||
_loadWeaponHook.Enable();
|
||||
_penumbra = penumbra;
|
||||
_weapons = weapons;
|
||||
|
||||
_penumbra.CreatingCharacterBase += OnCharacterRedraw;
|
||||
_penumbra.CreatedCharacterBase += OnCharacterRedrawFinished;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_flagSlotForUpdateHook.Dispose();
|
||||
_loadWeaponHook.Dispose();
|
||||
}
|
||||
|
||||
private void OnCharacterRedraw(Actor actor, uint* modelId, Customize customize, CharacterEquip equip)
|
||||
|
|
@ -230,21 +51,21 @@ public unsafe partial class RedrawManager : IDisposable
|
|||
|
||||
// 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) || _fixedDesignManager.TryGetDesign(identifier, out var save2)))
|
||||
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->CustomizeData);
|
||||
if (gameObjectCustomize.Equals(customize))
|
||||
customize.Load(save!.Customize());
|
||||
customize.Load(save!.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!.Equipment();
|
||||
var saveEquip = save!.Equipment;
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
{
|
||||
(_, equip[slot]) =
|
||||
|
|
|
|||
77
Glamourer/Interop/UpdateSlotService.cs
Normal file
77
Glamourer/Interop/UpdateSlotService.cs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public unsafe class UpdateSlotService : IDisposable
|
||||
{
|
||||
public UpdateSlotService()
|
||||
{
|
||||
SignatureHelper.Initialise(this);
|
||||
_flagSlotForUpdateHook.Enable();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
=> _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)
|
||||
{
|
||||
InvokeFlagSlotEvent(drawObject, slot, ref data);
|
||||
FlagSlotForUpdateInterop(drawObject, slot, data);
|
||||
}
|
||||
|
||||
public void UpdateStain(DrawObject drawObject, EquipSlot slot, StainId stain)
|
||||
{
|
||||
var armor = drawObject.Equip[slot] with { Stain = stain };
|
||||
UpdateSlot(drawObject, slot, armor);
|
||||
}
|
||||
|
||||
private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data)
|
||||
{
|
||||
var slot = slotIdx.ToEquipSlot();
|
||||
InvokeFlagSlotEvent(drawObject, slot, ref *data);
|
||||
return _flagSlotForUpdateHook.Original(drawObject, slotIdx, data);
|
||||
}
|
||||
|
||||
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}.");
|
||||
}
|
||||
}
|
||||
78
Glamourer/Interop/VisorService.cs
Normal file
78
Glamourer/Interop/VisorService.cs
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
using System;
|
||||
using System.Linq;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public class VisorService : IDisposable
|
||||
{
|
||||
public VisorService()
|
||||
{
|
||||
SignatureHelper.Initialise(this);
|
||||
_setupVisorHook.Enable();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
=> _setupVisorHook.Dispose();
|
||||
|
||||
public static unsafe bool GetVisorState(nint humanPtr)
|
||||
{
|
||||
if (humanPtr == IntPtr.Zero)
|
||||
return false;
|
||||
|
||||
var data = (Human*)humanPtr;
|
||||
var flags = &data->CharacterBase.UnkFlags_01;
|
||||
return (*flags & Offsets.DrawObjectVisorStateFlag) != 0;
|
||||
}
|
||||
|
||||
public unsafe void SetVisorState(nint humanPtr, bool on)
|
||||
{
|
||||
if (humanPtr == IntPtr.Zero)
|
||||
return;
|
||||
|
||||
var data = (Human*)humanPtr;
|
||||
_setupVisorHook.Original(humanPtr, (ushort) data->HeadSetID, on);
|
||||
}
|
||||
|
||||
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))]
|
||||
private readonly Hook<UpdateVisorDelegateInternal> _setupVisorHook = null!;
|
||||
|
||||
public event UpdateVisorDelegate? VisorUpdate;
|
||||
|
||||
private void SetupVisorDetour(nint humanPtr, ushort modelId, bool on)
|
||||
{
|
||||
InvokeVisorEvent(humanPtr, modelId, ref on);
|
||||
_setupVisorHook.Original(humanPtr, modelId, on);
|
||||
}
|
||||
|
||||
private void InvokeVisorEvent(DrawObject drawObject, SetId modelId, ref 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}.");
|
||||
}
|
||||
}
|
||||
120
Glamourer/Interop/WeaponService.cs
Normal file
120
Glamourer/Interop/WeaponService.cs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using Dalamud.Hooking;
|
||||
using Dalamud.Utility.Signatures;
|
||||
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
public unsafe class WeaponService : IDisposable
|
||||
{
|
||||
public WeaponService()
|
||||
{
|
||||
SignatureHelper.Initialise(this);
|
||||
_loadWeaponHook.Enable();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_loadWeaponHook.Dispose();
|
||||
}
|
||||
|
||||
public static readonly int CharacterWeaponOffset = (int)Marshal.OffsetOf<Character>("DrawData");
|
||||
|
||||
public delegate void LoadWeaponDelegate(nint 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(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,
|
||||
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}");
|
||||
//}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
public void LoadStain(Actor character, EquipSlot slot, StainId stain)
|
||||
{
|
||||
var weapon = slot == EquipSlot.OffHand ? character.OffHand : character.MainHand;
|
||||
weapon.Stain = stain;
|
||||
LoadWeapon(character, slot, weapon);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue