mirror of
https://github.com/Ottermandias/Glamourer.git
synced 2025-12-12 18:27:24 +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
|
||||
{
|
||||
/// <seealso cref="Api.GlamourerIpc.OnGPoseChanged"/>
|
||||
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 Glamourer.Customization;
|
||||
using Glamourer.Interop.Structs;
|
||||
using Penumbra.GameData.Structs;
|
||||
using OtterGui.Classes;
|
||||
using CustomizeData = Penumbra.GameData.Structs.CustomizeData;
|
||||
|
||||
namespace Glamourer.Interop;
|
||||
|
||||
|
|
@ -10,15 +14,32 @@ namespace Glamourer.Interop;
|
|||
/// Changes in Race, body type or Gender are probably ignored.
|
||||
/// This operates on draw objects, not game objects.
|
||||
/// </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()
|
||||
=> 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);
|
||||
|
||||
[Signature(Sigs.ChangeCustomize)]
|
||||
private readonly ChangeCustomizeDelegate _changeCustomize = null!;
|
||||
[Signature(Sigs.ChangeCustomize, DetourName = nameof(ChangeCustomizeDetour))]
|
||||
private readonly Hook<ChangeCustomizeDelegate> _changeCustomizeHook;
|
||||
|
||||
public bool UpdateCustomize(Model model, CustomizeData customize)
|
||||
{
|
||||
|
|
@ -26,9 +47,19 @@ public unsafe class ChangeCustomizeService
|
|||
return false;
|
||||
|
||||
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)
|
||||
=> 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.Events;
|
||||
using Glamourer.Interop;
|
||||
|
|
@ -11,6 +10,7 @@ using Penumbra.GameData.Actors;
|
|||
using Penumbra.GameData.Data;
|
||||
using Penumbra.GameData.Enums;
|
||||
using Penumbra.GameData.Structs;
|
||||
using System;
|
||||
|
||||
namespace Glamourer.State;
|
||||
|
||||
|
|
@ -27,6 +27,7 @@ public class StateListener : IDisposable
|
|||
private readonly StateManager _manager;
|
||||
private readonly StateApplier _applier;
|
||||
private readonly ItemManager _items;
|
||||
private readonly CustomizationService _customizations;
|
||||
private readonly PenumbraService _penumbra;
|
||||
private readonly SlotUpdating _slotUpdating;
|
||||
private readonly WeaponLoading _weaponLoading;
|
||||
|
|
@ -37,7 +38,8 @@ public class StateListener : IDisposable
|
|||
private readonly FunModule _funModule;
|
||||
private readonly HumanModelList _humans;
|
||||
private readonly MovedEquipment _movedEquipment;
|
||||
private readonly GPoseService _gpose;
|
||||
private readonly GPoseService _gPose;
|
||||
private readonly ChangeCustomizeService _changeCustomizeService;
|
||||
|
||||
private ActorIdentifier _creatingIdentifier = ActorIdentifier.Invalid;
|
||||
private ActorState? _creatingState;
|
||||
|
|
@ -52,25 +54,28 @@ public class StateListener : IDisposable
|
|||
public StateListener(StateManager manager, ItemManager items, PenumbraService penumbra, ActorService actors, Configuration config,
|
||||
SlotUpdating slotUpdating, WeaponLoading weaponLoading, VisorStateChanged visorState, WeaponVisibilityChanged weaponVisibility,
|
||||
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;
|
||||
_items = items;
|
||||
_penumbra = penumbra;
|
||||
_actors = actors;
|
||||
_config = config;
|
||||
_slotUpdating = slotUpdating;
|
||||
_weaponLoading = weaponLoading;
|
||||
_visorState = visorState;
|
||||
_weaponVisibility = weaponVisibility;
|
||||
_headGearVisibility = headGearVisibility;
|
||||
_autoDesignApplier = autoDesignApplier;
|
||||
_funModule = funModule;
|
||||
_humans = humans;
|
||||
_applier = applier;
|
||||
_movedEquipment = movedEquipment;
|
||||
_objects = objects;
|
||||
_gpose = gpose;
|
||||
_manager = manager;
|
||||
_items = items;
|
||||
_penumbra = penumbra;
|
||||
_actors = actors;
|
||||
_config = config;
|
||||
_slotUpdating = slotUpdating;
|
||||
_weaponLoading = weaponLoading;
|
||||
_visorState = visorState;
|
||||
_weaponVisibility = weaponVisibility;
|
||||
_headGearVisibility = headGearVisibility;
|
||||
_autoDesignApplier = autoDesignApplier;
|
||||
_funModule = funModule;
|
||||
_humans = humans;
|
||||
_applier = applier;
|
||||
_movedEquipment = movedEquipment;
|
||||
_objects = objects;
|
||||
_gPose = gPose;
|
||||
_changeCustomizeService = changeCustomizeService;
|
||||
_customizations = customizations;
|
||||
|
||||
if (Enabled)
|
||||
Subscribe();
|
||||
|
|
@ -132,15 +137,7 @@ public class StateListener : IDisposable
|
|||
case UpdateState.NoChange:
|
||||
|
||||
modelId = _creatingState.ModelData.ModelId;
|
||||
switch (UpdateBaseData(actor, _creatingState, customize))
|
||||
{
|
||||
case UpdateState.Transformed: break;
|
||||
case UpdateState.Change: break;
|
||||
case UpdateState.NoChange:
|
||||
customize = _creatingState.ModelData.Customize;
|
||||
break;
|
||||
}
|
||||
|
||||
UpdateCustomize(actor, _creatingState, ref customize, true);
|
||||
foreach (var slot in EquipSlotExtensions.EqdpSlots)
|
||||
HandleEquipSlot(actor, _creatingState, slot, ref ((CharacterArmor*)equipDataPtr)[slot.ToIndex()]);
|
||||
|
||||
|
|
@ -155,6 +152,54 @@ public class StateListener : IDisposable
|
|||
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>
|
||||
/// A draw model loads a new equipment piece.
|
||||
/// 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
|
||||
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;
|
||||
else if (actorWeapon.Set.Value != 0)
|
||||
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,
|
||||
/// or if they used other tools to change things.
|
||||
/// </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.
|
||||
if (!actor.GetCustomize().Equals(customize))
|
||||
if (checkTransform && !actor.GetCustomize().Equals(customize))
|
||||
return UpdateState.Transformed;
|
||||
|
||||
// Customize array did not change to stored state.
|
||||
|
|
@ -516,6 +561,7 @@ public class StateListener : IDisposable
|
|||
_visorState.Subscribe(OnVisorChange, VisorStateChanged.Priority.StateListener);
|
||||
_headGearVisibility.Subscribe(OnHeadGearVisibilityChange, HeadGearVisibilityChanged.Priority.StateListener);
|
||||
_weaponVisibility.Subscribe(OnWeaponVisibilityChange, WeaponVisibilityChanged.Priority.StateListener);
|
||||
_changeCustomizeService.Subscribe(OnCustomizeChange, ChangeCustomizeService.Priority.StateListener);
|
||||
}
|
||||
|
||||
private void Unsubscribe()
|
||||
|
|
@ -528,6 +574,7 @@ public class StateListener : IDisposable
|
|||
_visorState.Unsubscribe(OnVisorChange);
|
||||
_headGearVisibility.Unsubscribe(OnHeadGearVisibilityChange);
|
||||
_weaponVisibility.Unsubscribe(OnWeaponVisibilityChange);
|
||||
_changeCustomizeService.Unsubscribe(OnCustomizeChange);
|
||||
}
|
||||
|
||||
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