mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +01:00
More state-keeping for when the game object changes an item but with identical model information...
This commit is contained in:
parent
9c42872456
commit
c6d24d83da
4 changed files with 111 additions and 36 deletions
31
Glamourer/Events/EquipmentLoading.cs
Normal file
31
Glamourer/Events/EquipmentLoading.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
using System;
|
||||||
|
using Glamourer.Interop.Structs;
|
||||||
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.GameData.Enums;
|
||||||
|
using Penumbra.GameData.Structs;
|
||||||
|
|
||||||
|
namespace Glamourer.Events;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Triggered when a game object updates an equipment piece in its model data.
|
||||||
|
/// <list type="number">
|
||||||
|
/// <item>Parameter is the character updating. </item>
|
||||||
|
/// <item>Parameter is the equipment slot changed. </item>
|
||||||
|
/// <item>Parameter is the model values to change the equipment piece to. </item>
|
||||||
|
/// </list>
|
||||||
|
/// </summary>
|
||||||
|
public sealed class EquipmentLoading : EventWrapper<Action<Actor, EquipSlot, CharacterArmor>, EquipmentLoading.Priority>
|
||||||
|
{
|
||||||
|
public enum Priority
|
||||||
|
{
|
||||||
|
/// <seealso cref="State.StateListener.OnEquipmentLoading"/>
|
||||||
|
StateListener = 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
public EquipmentLoading()
|
||||||
|
: base(nameof(EquipmentLoading))
|
||||||
|
{ }
|
||||||
|
|
||||||
|
public void Invoke(Actor actor, EquipSlot slot, CharacterArmor armor)
|
||||||
|
=> Invoke(this, actor, slot, armor);
|
||||||
|
}
|
||||||
|
|
@ -1,32 +1,33 @@
|
||||||
using System;
|
using System;
|
||||||
using Dalamud.Hooking;
|
using Dalamud.Hooking;
|
||||||
using Dalamud.Utility.Signatures;
|
using Dalamud.Utility.Signatures;
|
||||||
|
using FFXIVClientStructs.FFXIV.Client.Game.Character;
|
||||||
using Glamourer.Events;
|
using Glamourer.Events;
|
||||||
using Glamourer.Interop.Structs;
|
using Glamourer.Interop.Structs;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
|
||||||
|
|
||||||
namespace Glamourer.Interop;
|
namespace Glamourer.Interop;
|
||||||
|
|
||||||
public unsafe class UpdateSlotService : IDisposable
|
public unsafe class UpdateSlotService : IDisposable
|
||||||
{
|
{
|
||||||
public readonly SlotUpdating Event;
|
public readonly SlotUpdating SlotUpdatingEvent;
|
||||||
|
public readonly EquipmentLoading EquipmentLoadingEvent;
|
||||||
|
|
||||||
public UpdateSlotService(SlotUpdating slotUpdating)
|
public UpdateSlotService(SlotUpdating slotUpdating, EquipmentLoading equipmentLoadingEvent)
|
||||||
{
|
{
|
||||||
Event = slotUpdating;
|
SlotUpdatingEvent = slotUpdating;
|
||||||
|
EquipmentLoadingEvent = equipmentLoadingEvent;
|
||||||
SignatureHelper.Initialise(this);
|
SignatureHelper.Initialise(this);
|
||||||
_flagSlotForUpdateHook.Enable();
|
_flagSlotForUpdateHook.Enable();
|
||||||
|
_loadEquipmentHook.Enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
=> _flagSlotForUpdateHook.Dispose();
|
{
|
||||||
|
_flagSlotForUpdateHook.Dispose();
|
||||||
private delegate ulong FlagSlotForUpdateDelegateIntern(nint drawObject, uint slot, CharacterArmor* data);
|
_loadEquipmentHook.Dispose();
|
||||||
|
}
|
||||||
[Signature(Sigs.FlagSlotForUpdate, DetourName = nameof(FlagSlotForUpdateDetour))]
|
|
||||||
private readonly Hook<FlagSlotForUpdateDelegateIntern> _flagSlotForUpdateHook = null!;
|
|
||||||
|
|
||||||
public void UpdateSlot(Model drawObject, EquipSlot slot, CharacterArmor data)
|
public void UpdateSlot(Model drawObject, EquipSlot slot, CharacterArmor data)
|
||||||
{
|
{
|
||||||
|
|
@ -45,14 +46,34 @@ public unsafe class UpdateSlotService : IDisposable
|
||||||
public void UpdateStain(Model drawObject, EquipSlot slot, StainId stain)
|
public void UpdateStain(Model drawObject, EquipSlot slot, StainId stain)
|
||||||
=> UpdateArmor(drawObject, slot, drawObject.GetArmor(slot), stain);
|
=> UpdateArmor(drawObject, slot, drawObject.GetArmor(slot), stain);
|
||||||
|
|
||||||
|
private delegate ulong FlagSlotForUpdateDelegateIntern(nint drawObject, uint slot, CharacterArmor* data);
|
||||||
|
|
||||||
|
[Signature(Sigs.FlagSlotForUpdate, DetourName = nameof(FlagSlotForUpdateDetour))]
|
||||||
|
private readonly Hook<FlagSlotForUpdateDelegateIntern> _flagSlotForUpdateHook = null!;
|
||||||
|
|
||||||
|
private delegate void LoadEquipmentDelegateIntern(DrawDataContainer* drawDataContainer, uint slotIdx, CharacterArmor data, bool force);
|
||||||
|
|
||||||
|
// TODO: use client structs.
|
||||||
|
[Signature("E8 ?? ?? ?? ?? 41 B5 ?? FF C6", DetourName = nameof(LoadEquipmentDetour))]
|
||||||
|
private readonly Hook<LoadEquipmentDelegateIntern> _loadEquipmentHook = null!;
|
||||||
|
|
||||||
private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data)
|
private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data)
|
||||||
{
|
{
|
||||||
var slot = slotIdx.ToEquipSlot();
|
var slot = slotIdx.ToEquipSlot();
|
||||||
var returnValue = ulong.MaxValue;
|
var returnValue = ulong.MaxValue;
|
||||||
Event.Invoke(drawObject, slot, ref *data, ref returnValue);
|
SlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue);
|
||||||
|
Glamourer.Log.Information($"[FlagSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue}).");
|
||||||
return returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue;
|
return returnValue == ulong.MaxValue ? _flagSlotForUpdateHook.Original(drawObject, slotIdx, data) : returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LoadEquipmentDetour(DrawDataContainer* drawDataContainer, uint slotIdx, CharacterArmor data, bool force)
|
||||||
|
{
|
||||||
|
var slot = slotIdx.ToEquipSlot();
|
||||||
|
EquipmentLoadingEvent.Invoke(drawDataContainer->Parent, slot, data);
|
||||||
|
Glamourer.Log.Information($"[LoadEquipment] Called with 0x{(ulong)drawDataContainer:X} for slot {slot} with {data} ({force}).");
|
||||||
|
_loadEquipmentHook.Original(drawDataContainer, slotIdx, data, force);
|
||||||
|
}
|
||||||
|
|
||||||
private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor)
|
private ulong FlagSlotForUpdateInterop(Model drawObject, EquipSlot slot, CharacterArmor armor)
|
||||||
=> _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor);
|
=> _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ public static class ServiceManager
|
||||||
private static IServiceCollection AddEvents(this IServiceCollection services)
|
private static IServiceCollection AddEvents(this IServiceCollection services)
|
||||||
=> services.AddSingleton<VisorStateChanged>()
|
=> services.AddSingleton<VisorStateChanged>()
|
||||||
.AddSingleton<SlotUpdating>()
|
.AddSingleton<SlotUpdating>()
|
||||||
|
.AddSingleton<EquipmentLoading>()
|
||||||
.AddSingleton<DesignChanged>()
|
.AddSingleton<DesignChanged>()
|
||||||
.AddSingleton<AutomationChanged>()
|
.AddSingleton<AutomationChanged>()
|
||||||
.AddSingleton<StateChanged>()
|
.AddSingleton<StateChanged>()
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using Glamourer.Interop.Penumbra;
|
||||||
using Glamourer.Interop.Structs;
|
using Glamourer.Interop.Structs;
|
||||||
using Glamourer.Services;
|
using Glamourer.Services;
|
||||||
using OtterGui.Classes;
|
using OtterGui.Classes;
|
||||||
|
using Penumbra.GameData.Actors;
|
||||||
using Penumbra.GameData.Data;
|
using Penumbra.GameData.Data;
|
||||||
using Penumbra.GameData.Enums;
|
using Penumbra.GameData.Enums;
|
||||||
using Penumbra.GameData.Structs;
|
using Penumbra.GameData.Structs;
|
||||||
|
|
@ -25,6 +26,7 @@ public class StateListener : IDisposable
|
||||||
private readonly ItemManager _items;
|
private readonly ItemManager _items;
|
||||||
private readonly PenumbraService _penumbra;
|
private readonly PenumbraService _penumbra;
|
||||||
private readonly SlotUpdating _slotUpdating;
|
private readonly SlotUpdating _slotUpdating;
|
||||||
|
private readonly EquipmentLoading _equipmentLoading;
|
||||||
private readonly WeaponLoading _weaponLoading;
|
private readonly WeaponLoading _weaponLoading;
|
||||||
private readonly HeadGearVisibilityChanged _headGearVisibility;
|
private readonly HeadGearVisibilityChanged _headGearVisibility;
|
||||||
private readonly VisorStateChanged _visorState;
|
private readonly VisorStateChanged _visorState;
|
||||||
|
|
@ -41,7 +43,8 @@ public class StateListener : IDisposable
|
||||||
|
|
||||||
public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorService actors, Configuration config,
|
public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorService actors, Configuration config,
|
||||||
SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility,
|
SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility,
|
||||||
HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans)
|
HeadGearVisibilityChanged headGearVisibility, AutoDesignApplier autoDesignApplier, FunModule funModule, HumanModelList humans,
|
||||||
|
EquipmentLoading equipmentLoading)
|
||||||
{
|
{
|
||||||
_manager = manager;
|
_manager = manager;
|
||||||
_items = items;
|
_items = items;
|
||||||
|
|
@ -56,6 +59,7 @@ public class StateListener : IDisposable
|
||||||
_autoDesignApplier = autoDesignApplier;
|
_autoDesignApplier = autoDesignApplier;
|
||||||
_funModule = funModule;
|
_funModule = funModule;
|
||||||
_humans = humans;
|
_humans = humans;
|
||||||
|
_equipmentLoading = equipmentLoading;
|
||||||
|
|
||||||
if (Enabled)
|
if (Enabled)
|
||||||
Subscribe();
|
Subscribe();
|
||||||
|
|
@ -159,6 +163,42 @@ public class StateListener : IDisposable
|
||||||
(_, armor.Value) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender);
|
(_, armor.Value) = _items.RestrictedGear.ResolveRestricted(armor, slot, customize.Race, customize.Gender);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The game object does not actually invoke changes when the model id is identical,
|
||||||
|
/// so we need to handle that case too.
|
||||||
|
/// </summary>
|
||||||
|
private void OnEquipmentLoading(Actor actor, EquipSlot slot, CharacterArmor armor)
|
||||||
|
{
|
||||||
|
if (armor != actor.GetArmor(slot))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!actor.Identifier(_actors.AwaitedService, out var identifier)
|
||||||
|
|| !_manager.TryGetValue(identifier, out var state) || !state.BaseData.IsHuman)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (state.ModelData.Armor(slot) == armor)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var setItem = state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc;
|
||||||
|
var setStain = state[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc;
|
||||||
|
switch (setItem, setStain)
|
||||||
|
{
|
||||||
|
case (true, true):
|
||||||
|
_manager.ChangeEquip(state, slot, state.BaseData.Item(slot), state.BaseData.Stain(slot), StateChanged.Source.Manual);
|
||||||
|
state[slot, false] = StateChanged.Source.Game;
|
||||||
|
state[slot, true] = StateChanged.Source.Game;
|
||||||
|
break;
|
||||||
|
case (true, false):
|
||||||
|
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Manual);
|
||||||
|
state[slot, false] = StateChanged.Source.Game;
|
||||||
|
break;
|
||||||
|
case (false, true):
|
||||||
|
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Manual);
|
||||||
|
state[slot, true] = StateChanged.Source.Game;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A game object loads a new weapon.
|
/// A game object loads a new weapon.
|
||||||
/// Update base data, apply or update model data.
|
/// Update base data, apply or update model data.
|
||||||
|
|
@ -179,24 +219,14 @@ public class StateListener : IDisposable
|
||||||
case UpdateState.Transformed: break;
|
case UpdateState.Transformed: break;
|
||||||
case UpdateState.Change:
|
case UpdateState.Change:
|
||||||
if (state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
|
if (state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
|
||||||
{
|
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Game);
|
||||||
state.ModelData.SetItem(slot, state.BaseData.Item(slot));
|
|
||||||
state[slot, false] = StateChanged.Source.Game;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
apply = true;
|
apply = true;
|
||||||
}
|
|
||||||
|
|
||||||
if (state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
|
if (state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
|
||||||
{
|
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game);
|
||||||
state.ModelData.SetStain(slot, state.BaseData.Stain(slot));
|
|
||||||
state[slot, true] = StateChanged.Source.Game;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
apply = true;
|
apply = true;
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case UpdateState.NoChange:
|
case UpdateState.NoChange:
|
||||||
|
|
@ -254,24 +284,14 @@ public class StateListener : IDisposable
|
||||||
case UpdateState.Change:
|
case UpdateState.Change:
|
||||||
var apply = false;
|
var apply = false;
|
||||||
if (state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
|
if (state[slot, false] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
|
||||||
{
|
_manager.ChangeItem(state, slot, state.BaseData.Item(slot), StateChanged.Source.Game);
|
||||||
state.ModelData.SetItem(slot, state.BaseData.Item(slot));
|
|
||||||
state[slot, false] = StateChanged.Source.Game;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
apply = true;
|
apply = true;
|
||||||
}
|
|
||||||
|
|
||||||
if (state[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
|
if (state[slot, true] is not StateChanged.Source.Fixed and not StateChanged.Source.Ipc)
|
||||||
{
|
_manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game);
|
||||||
state.ModelData.SetStain(slot, state.BaseData.Stain(slot));
|
|
||||||
state[slot, true] = StateChanged.Source.Game;
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
apply = true;
|
apply = true;
|
||||||
}
|
|
||||||
|
|
||||||
if (apply)
|
if (apply)
|
||||||
armor = state.ModelData.Armor(slot);
|
armor = state.ModelData.Armor(slot);
|
||||||
|
|
@ -464,6 +484,7 @@ public class StateListener : IDisposable
|
||||||
{
|
{
|
||||||
_penumbra.CreatingCharacterBase += OnCreatingCharacterBase;
|
_penumbra.CreatingCharacterBase += OnCreatingCharacterBase;
|
||||||
_slotUpdating.Subscribe(OnSlotUpdating, SlotUpdating.Priority.StateListener);
|
_slotUpdating.Subscribe(OnSlotUpdating, SlotUpdating.Priority.StateListener);
|
||||||
|
_equipmentLoading.Subscribe(OnEquipmentLoading, EquipmentLoading.Priority.StateListener);
|
||||||
_weaponLoading.Subscribe(OnWeaponLoading, WeaponLoading.Priority.StateListener);
|
_weaponLoading.Subscribe(OnWeaponLoading, WeaponLoading.Priority.StateListener);
|
||||||
_visorState.Subscribe(OnVisorChange, VisorStateChanged.Priority.StateListener);
|
_visorState.Subscribe(OnVisorChange, VisorStateChanged.Priority.StateListener);
|
||||||
_headGearVisibility.Subscribe(OnHeadGearVisibilityChange, HeadGearVisibilityChanged.Priority.StateListener);
|
_headGearVisibility.Subscribe(OnHeadGearVisibilityChange, HeadGearVisibilityChanged.Priority.StateListener);
|
||||||
|
|
@ -474,6 +495,7 @@ public class StateListener : IDisposable
|
||||||
{
|
{
|
||||||
_penumbra.CreatingCharacterBase -= OnCreatingCharacterBase;
|
_penumbra.CreatingCharacterBase -= OnCreatingCharacterBase;
|
||||||
_slotUpdating.Unsubscribe(OnSlotUpdating);
|
_slotUpdating.Unsubscribe(OnSlotUpdating);
|
||||||
|
_equipmentLoading.Unsubscribe(OnEquipmentLoading);
|
||||||
_weaponLoading.Unsubscribe(OnWeaponLoading);
|
_weaponLoading.Unsubscribe(OnWeaponLoading);
|
||||||
_visorState.Unsubscribe(OnVisorChange);
|
_visorState.Unsubscribe(OnVisorChange);
|
||||||
_headGearVisibility.Unsubscribe(OnHeadGearVisibilityChange);
|
_headGearVisibility.Unsubscribe(OnHeadGearVisibilityChange);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue