mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-15 13:14:17 +01:00
Add listening to ChangeCustomize.
This commit is contained in:
parent
19e81b4e16
commit
ccb29ef9b2
4 changed files with 120 additions and 41 deletions
|
|
@ -15,6 +15,7 @@ public class GPoseService : EventWrapper<Action<bool>, GPoseService.Priority>
|
||||||
|
|
||||||
public enum Priority
|
public enum Priority
|
||||||
{
|
{
|
||||||
|
/// <seealso cref="Api.GlamourerIpc.OnGPoseChanged"/>
|
||||||
GlamourerIpc = int.MinValue,
|
GlamourerIpc = int.MinValue,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
using Dalamud.Utility.Signatures;
|
using System;
|
||||||
|
using Dalamud.Hooking;
|
||||||
|
using Dalamud.Utility.Signatures;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
|
||||||
|
using Glamourer.Customization;
|
||||||
using Glamourer.Interop.Structs;
|
using Glamourer.Interop.Structs;
|
||||||
using Penumbra.GameData.Structs;
|
using OtterGui.Classes;
|
||||||
|
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||||
|
|
||||||
namespace Glamourer.Interop;
|
namespace Glamourer.Interop;
|
||||||
|
|
||||||
|
|
@ -10,15 +14,32 @@ namespace Glamourer.Interop;
|
||||||
/// Changes in Race, body type or Gender are probably ignored.
|
/// Changes in Race, body type or Gender are probably ignored.
|
||||||
/// This operates on draw objects, not game objects.
|
/// This operates on draw objects, not game objects.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public unsafe class ChangeCustomizeService
|
public unsafe class ChangeCustomizeService : EventWrapper<Action<Model, Ref<Customize>>, ChangeCustomizeService.Priority>
|
||||||
{
|
{
|
||||||
|
public enum Priority
|
||||||
|
{
|
||||||
|
/// <seealso cref="State.StateListener.OnCustomizeChange"/>
|
||||||
|
StateListener = 0,
|
||||||
|
}
|
||||||
|
|
||||||
public ChangeCustomizeService()
|
public ChangeCustomizeService()
|
||||||
=> SignatureHelper.Initialise(this);
|
: base("ChangeCustomize")
|
||||||
|
{
|
||||||
|
_changeCustomizeHook =
|
||||||
|
Hook<ChangeCustomizeDelegate>.FromAddress((nint)Human.MemberFunctionPointers.UpdateDrawData, ChangeCustomizeDetour);
|
||||||
|
_changeCustomizeHook.Enable();
|
||||||
|
}
|
||||||
|
|
||||||
|
public new void Dispose()
|
||||||
|
{
|
||||||
|
base.Dispose();
|
||||||
|
_changeCustomizeHook.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
private delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment);
|
private delegate bool ChangeCustomizeDelegate(Human* human, byte* data, byte skipEquipment);
|
||||||
|
|
||||||
[Signature(Sigs.ChangeCustomize)]
|
[Signature(Sigs.ChangeCustomize, DetourName = nameof(ChangeCustomizeDetour))]
|
||||||
private readonly ChangeCustomizeDelegate _changeCustomize = null!;
|
private readonly Hook<ChangeCustomizeDelegate> _changeCustomizeHook;
|
||||||
|
|
||||||
public bool UpdateCustomize(Model model, CustomizeData customize)
|
public bool UpdateCustomize(Model model, CustomizeData customize)
|
||||||
{
|
{
|
||||||
|
|
@ -26,9 +47,19 @@ public unsafe class ChangeCustomizeService
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Glamourer.Log.Verbose($"[ChangeCustomize] Invoked on 0x{model.Address:X} with {customize}.");
|
Glamourer.Log.Verbose($"[ChangeCustomize] Invoked on 0x{model.Address:X} with {customize}.");
|
||||||
return _changeCustomize(model.AsHuman, customize.Data, 1);
|
return _changeCustomizeHook.Original(model.AsHuman, customize.Data, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool UpdateCustomize(Actor actor, CustomizeData customize)
|
public bool UpdateCustomize(Actor actor, CustomizeData customize)
|
||||||
=> UpdateCustomize(actor.Model, customize);
|
=> UpdateCustomize(actor.Model, customize);
|
||||||
|
|
||||||
|
private bool ChangeCustomizeDetour(Human* human, byte* data, byte skipEquipment)
|
||||||
|
{
|
||||||
|
var customize = new Ref<Customize>(new Customize(*(CustomizeData*)data));
|
||||||
|
Invoke(this, (Model)human, customize);
|
||||||
|
fixed (byte* ptr = customize.Value.Data.Data)
|
||||||
|
{
|
||||||
|
return _changeCustomizeHook.Original(human, ptr, skipEquipment);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System;
|
using Glamourer.Automation;
|
||||||
using Glamourer.Automation;
|
|
||||||
using Glamourer.Customization;
|
using Glamourer.Customization;
|
||||||
using Glamourer.Events;
|
using Glamourer.Events;
|
||||||
using Glamourer.Interop;
|
using Glamourer.Interop;
|
||||||
|
|
@ -11,6 +10,7 @@ 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;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Glamourer.State;
|
namespace Glamourer.State;
|
||||||
|
|
||||||
|
|
@ -27,6 +27,7 @@ public class StateListener : IDisposable
|
||||||
private readonly StateManager _manager;
|
private readonly StateManager _manager;
|
||||||
private readonly StateApplier _applier;
|
private readonly StateApplier _applier;
|
||||||
private readonly ItemManager _items;
|
private readonly ItemManager _items;
|
||||||
|
private readonly CustomizationService _customizations;
|
||||||
private readonly PenumbraService _penumbra;
|
private readonly PenumbraService _penumbra;
|
||||||
private readonly SlotUpdating _slotUpdating;
|
private readonly SlotUpdating _slotUpdating;
|
||||||
private readonly WeaponLoading _weaponLoading;
|
private readonly WeaponLoading _weaponLoading;
|
||||||
|
|
@ -37,7 +38,8 @@ public class StateListener : IDisposable
|
||||||
private readonly FunModule _funModule;
|
private readonly FunModule _funModule;
|
||||||
private readonly HumanModelList _humans;
|
private readonly HumanModelList _humans;
|
||||||
private readonly MovedEquipment _movedEquipment;
|
private readonly MovedEquipment _movedEquipment;
|
||||||
private readonly GPoseService _gpose;
|
private readonly GPoseService _gPose;
|
||||||
|
private readonly ChangeCustomizeService _changeCustomizeService;
|
||||||
|
|
||||||
private ActorIdentifier _creatingIdentifier = ActorIdentifier.Invalid;
|
private ActorIdentifier _creatingIdentifier = ActorIdentifier.Invalid;
|
||||||
private ActorState? _creatingState;
|
private ActorState? _creatingState;
|
||||||
|
|
@ -52,25 +54,28 @@ 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,
|
||||||
StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gpose)
|
StateApplier applier, MovedEquipment movedEquipment, ObjectManager objects, GPoseService gPose,
|
||||||
|
ChangeCustomizeService changeCustomizeService, CustomizationService customizations)
|
||||||
{
|
{
|
||||||
_manager = manager;
|
_manager = manager;
|
||||||
_items = items;
|
_items = items;
|
||||||
_penumbra = penumbra;
|
_penumbra = penumbra;
|
||||||
_actors = actors;
|
_actors = actors;
|
||||||
_config = config;
|
_config = config;
|
||||||
_slotUpdating = slotUpdating;
|
_slotUpdating = slotUpdating;
|
||||||
_weaponLoading = weaponLoading;
|
_weaponLoading = weaponLoading;
|
||||||
_visorState = visorState;
|
_visorState = visorState;
|
||||||
_weaponVisibility = weaponVisibility;
|
_weaponVisibility = weaponVisibility;
|
||||||
_headGearVisibility = headGearVisibility;
|
_headGearVisibility = headGearVisibility;
|
||||||
_autoDesignApplier = autoDesignApplier;
|
_autoDesignApplier = autoDesignApplier;
|
||||||
_funModule = funModule;
|
_funModule = funModule;
|
||||||
_humans = humans;
|
_humans = humans;
|
||||||
_applier = applier;
|
_applier = applier;
|
||||||
_movedEquipment = movedEquipment;
|
_movedEquipment = movedEquipment;
|
||||||
_objects = objects;
|
_objects = objects;
|
||||||
_gpose = gpose;
|
_gPose = gPose;
|
||||||
|
_changeCustomizeService = changeCustomizeService;
|
||||||
|
_customizations = customizations;
|
||||||
|
|
||||||
if (Enabled)
|
if (Enabled)
|
||||||
Subscribe();
|
Subscribe();
|
||||||
|
|
@ -132,15 +137,7 @@ public class StateListener : IDisposable
|
||||||
case UpdateState.NoChange:
|
case UpdateState.NoChange:
|
||||||
|
|
||||||
modelId = _creatingState.ModelData.ModelId;
|
modelId = _creatingState.ModelData.ModelId;
|
||||||
switch (UpdateBaseData(actor, _creatingState, customize))
|
UpdateCustomize(actor, _creatingState, ref customize, true);
|
||||||
{
|
|
||||||
case UpdateState.Transformed: break;
|
|
||||||
case UpdateState.Change: break;
|
|
||||||
case UpdateState.NoChange:
|
|
||||||
customize = _creatingState.ModelData.Customize;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||||
HandleEquipSlot(actor, _creatingState, slot, ref ((CharacterArmor*)equipDataPtr)[slot.ToIndex()]);
|
HandleEquipSlot(actor, _creatingState, slot, ref ((CharacterArmor*)equipDataPtr)[slot.ToIndex()]);
|
||||||
|
|
||||||
|
|
@ -155,6 +152,54 @@ public class StateListener : IDisposable
|
||||||
ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender);
|
ProtectRestrictedGear(equipDataPtr, customize.Race, customize.Gender);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private unsafe void OnCustomizeChange(Model model, Ref<Customize> customize)
|
||||||
|
{
|
||||||
|
if (!model.IsHuman)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var actor = _penumbra.GameObjectFromDrawObject(model);
|
||||||
|
if (!actor.Identifier(_actors.AwaitedService, out var identifier)
|
||||||
|
|| !_manager.TryGetValue(identifier, out var state))
|
||||||
|
return;
|
||||||
|
|
||||||
|
UpdateCustomize(actor, state, ref customize.Value, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateCustomize(Actor actor, ActorState state, ref Customize customize, bool checkTransform)
|
||||||
|
{
|
||||||
|
switch (UpdateBaseData(actor, state, customize, checkTransform))
|
||||||
|
{
|
||||||
|
case UpdateState.Transformed: break;
|
||||||
|
case UpdateState.Change:
|
||||||
|
var set = _customizations.AwaitedService.GetList(state.ModelData.Customize.Clan, state.ModelData.Customize.Gender);
|
||||||
|
var model = state.ModelData.Customize;
|
||||||
|
foreach (var index in CustomizationExtensions.AllBasic)
|
||||||
|
{
|
||||||
|
if (state[index] is not StateChanged.Source.Fixed)
|
||||||
|
{
|
||||||
|
var newValue = customize[index];
|
||||||
|
var oldValue = model[index];
|
||||||
|
if (newValue != oldValue)
|
||||||
|
{
|
||||||
|
if (set.Validate(index, newValue, out _, model.Face))
|
||||||
|
_manager.ChangeCustomize(state, index, newValue, StateChanged.Source.Game);
|
||||||
|
else
|
||||||
|
customize[index] = oldValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
customize[index] = model[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
case UpdateState.NoChange:
|
||||||
|
customize = state.ModelData.Customize;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A draw model loads a new equipment piece.
|
/// A draw model loads a new equipment piece.
|
||||||
/// Update base data, apply or update model data, and protect against restricted gear.
|
/// Update base data, apply or update model data, and protect against restricted gear.
|
||||||
|
|
@ -250,7 +295,7 @@ public class StateListener : IDisposable
|
||||||
{
|
{
|
||||||
// Only allow overwriting identical weapons
|
// Only allow overwriting identical weapons
|
||||||
var newWeapon = state.ModelData.Weapon(slot);
|
var newWeapon = state.ModelData.Weapon(slot);
|
||||||
if (baseType is FullEquipType.Unknown || baseType == state.ModelData.Item(slot).Type || _gpose.InGPose && actor.IsGPoseOrCutscene)
|
if (baseType is FullEquipType.Unknown || baseType == state.ModelData.Item(slot).Type || _gPose.InGPose && actor.IsGPoseOrCutscene)
|
||||||
actorWeapon = newWeapon;
|
actorWeapon = newWeapon;
|
||||||
else if (actorWeapon.Set.Value != 0)
|
else if (actorWeapon.Set.Value != 0)
|
||||||
actorWeapon = actorWeapon.With(newWeapon.Stain);
|
actorWeapon = actorWeapon.With(newWeapon.Stain);
|
||||||
|
|
@ -385,10 +430,10 @@ public class StateListener : IDisposable
|
||||||
/// only if we kept track of state of someone who went to the aesthetician,
|
/// only if we kept track of state of someone who went to the aesthetician,
|
||||||
/// or if they used other tools to change things.
|
/// or if they used other tools to change things.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private UpdateState UpdateBaseData(Actor actor, ActorState state, Customize customize)
|
private UpdateState UpdateBaseData(Actor actor, ActorState state, Customize customize, bool checkTransform)
|
||||||
{
|
{
|
||||||
// Customize array does not agree between game object and draw object => transformation.
|
// Customize array does not agree between game object and draw object => transformation.
|
||||||
if (!actor.GetCustomize().Equals(customize))
|
if (checkTransform && !actor.GetCustomize().Equals(customize))
|
||||||
return UpdateState.Transformed;
|
return UpdateState.Transformed;
|
||||||
|
|
||||||
// Customize array did not change to stored state.
|
// Customize array did not change to stored state.
|
||||||
|
|
@ -516,6 +561,7 @@ public class StateListener : IDisposable
|
||||||
_visorState.Subscribe(OnVisorChange, VisorStateChanged.Priority.StateListener);
|
_visorState.Subscribe(OnVisorChange, VisorStateChanged.Priority.StateListener);
|
||||||
_headGearVisibility.Subscribe(OnHeadGearVisibilityChange, HeadGearVisibilityChanged.Priority.StateListener);
|
_headGearVisibility.Subscribe(OnHeadGearVisibilityChange, HeadGearVisibilityChanged.Priority.StateListener);
|
||||||
_weaponVisibility.Subscribe(OnWeaponVisibilityChange, WeaponVisibilityChanged.Priority.StateListener);
|
_weaponVisibility.Subscribe(OnWeaponVisibilityChange, WeaponVisibilityChanged.Priority.StateListener);
|
||||||
|
_changeCustomizeService.Subscribe(OnCustomizeChange, ChangeCustomizeService.Priority.StateListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Unsubscribe()
|
private void Unsubscribe()
|
||||||
|
|
@ -528,6 +574,7 @@ public class StateListener : IDisposable
|
||||||
_visorState.Unsubscribe(OnVisorChange);
|
_visorState.Unsubscribe(OnVisorChange);
|
||||||
_headGearVisibility.Unsubscribe(OnHeadGearVisibilityChange);
|
_headGearVisibility.Unsubscribe(OnHeadGearVisibilityChange);
|
||||||
_weaponVisibility.Unsubscribe(OnWeaponVisibilityChange);
|
_weaponVisibility.Unsubscribe(OnWeaponVisibilityChange);
|
||||||
|
_changeCustomizeService.Unsubscribe(OnCustomizeChange);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCreatedCharacterBase(nint gameObject, string _, nint drawObject)
|
private void OnCreatedCharacterBase(nint gameObject, string _, nint drawObject)
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit f306da6f43e8681d3bace45cbd4fd98ef49927ca
|
Subproject commit 5dd2b440e69b1725fa214b005b7179f2414a4053
|
||||||
Loading…
Add table
Add a link
Reference in a new issue