diff --git a/Glamourer/Automation/AutoDesignApplier.cs b/Glamourer/Automation/AutoDesignApplier.cs index bcc907c..52956cc 100644 --- a/Glamourer/Automation/AutoDesignApplier.cs +++ b/Glamourer/Automation/AutoDesignApplier.cs @@ -202,17 +202,8 @@ public sealed class AutoDesignApplier : IDisposable } } - /// - /// JOB CHANGE IS CALLED UPON HERE. - /// private void OnJobChange(Actor actor, Job oldJob, Job newJob) { - unsafe - { - var drawObject = actor.AsCharacter->DrawObject; - Glamourer.Log.Information($"[AutoDesignApplier][OnJobChange] 0x{(nint)drawObject:X} changed job from {oldJob} ({oldJob.Id}) to {newJob} ({newJob.Id})."); - } - if (!_config.EnableAutoDesigns || !actor.Identifier(_actors, out var id)) return; diff --git a/Glamourer/Events/GearsetDataLoaded.cs b/Glamourer/Events/GearsetDataLoaded.cs index 47d0108..680ae3f 100644 --- a/Glamourer/Events/GearsetDataLoaded.cs +++ b/Glamourer/Events/GearsetDataLoaded.cs @@ -1,7 +1,5 @@ using OtterGui.Classes; -using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; -using Penumbra.GameData.Structs; namespace Glamourer.Events; @@ -9,7 +7,7 @@ namespace Glamourer.Events; /// Triggers when the equipped gearset finished running all of its LoadEquipment, LoadWeapon, and crest calls. /// This defines a universal endpoint of base game state application to monitor. /// -/// The model drawobject associated with the finished load (should always be ClientPlayer) +/// The model drawobject associated with the finished load (Also fired by other players on render) /// /// public sealed class GearsetDataLoaded() diff --git a/Glamourer/Events/StateUpdated.cs b/Glamourer/Events/StateUpdated.cs index 82d737f..f18a69a 100644 --- a/Glamourer/Events/StateUpdated.cs +++ b/Glamourer/Events/StateUpdated.cs @@ -9,10 +9,8 @@ namespace Glamourer.Events; /// /// Triggered when a Design is edited in any way. /// -/// Parameter is the type of the change -/// Parameter is the changed saved state. +/// Parameter is the operation that finished updating the saved state. /// Parameter is the existing actors using this saved state. -/// Parameter is any additional data depending on the type of change. /// /// public sealed class StateUpdated() diff --git a/Glamourer/Interop/ChangeCustomizeService.cs b/Glamourer/Interop/ChangeCustomizeService.cs index 10e3a12..495d69c 100644 --- a/Glamourer/Interop/ChangeCustomizeService.cs +++ b/Glamourer/Interop/ChangeCustomizeService.cs @@ -64,7 +64,6 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2 _changeCustomizeHook; - // manual invoke by calling the detours _original call to `execute to` instead of `listen to`. public bool UpdateCustomize(Model model, CustomizeArray customize) { if (!model.IsHuman) @@ -79,7 +78,6 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2 UpdateCustomize(actor.Model, customize); - // detoured method. private bool ChangeCustomizeDetour(Human* human, byte* data, byte skipEquipment) { if (!InUpdate.InMethod) @@ -90,7 +88,6 @@ public unsafe class ChangeCustomizeService : EventWrapperRef2 action, Post.Priority priority) => _postEvent.Subscribe(action, priority); diff --git a/Glamourer/Interop/InventoryService.cs b/Glamourer/Interop/InventoryService.cs index 743bea1..33ba5cf 100644 --- a/Glamourer/Interop/InventoryService.cs +++ b/Glamourer/Interop/InventoryService.cs @@ -5,7 +5,6 @@ using FFXIVClientStructs.FFXIV.Client.Game; using FFXIVClientStructs.FFXIV.Client.UI.Misc; using Glamourer.Events; using OtterGui.Services; -using Penumbra.GameData; using Penumbra.GameData.Enums; using Penumbra.GameData.Structs; using Penumbra.String; @@ -14,10 +13,6 @@ namespace Glamourer.Interop; public sealed unsafe class InventoryService : IDisposable, IRequiredService { - private readonly MovedEquipment _movedItemsEvent; - private readonly EquippedGearset _gearsetEvent; - private readonly List<(EquipSlot, uint, StainIds)> _itemList = new(12); - // Called by EquipGearset, but returns a pointer instead of an int. // This is the internal function processed by all sources of Equipping a gearset, // such as hotbar gearset application and command gearset application @@ -27,10 +22,16 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService [Signature(EquipGearsetInternal, DetourName = nameof(EquipGearSetInternalDetour))] private readonly Hook _equipGearsetInternalHook = null!; + // The following above is currently pending for an accepted PR in FFXIVCLientStructs. + // Once accepted, remove everything above this comment and replace EquipGearset with EquipGearsetInternal. + + private readonly MovedEquipment _movedItemsEvent; + private readonly EquippedGearset _gearsetEvent; + private readonly List<(EquipSlot, uint, StainIds)> _itemList = new(12); public InventoryService(MovedEquipment movedItemsEvent, IGameInteropProvider interop, EquippedGearset gearsetEvent) { _movedItemsEvent = movedItemsEvent; - _gearsetEvent = gearsetEvent; + _gearsetEvent = gearsetEvent; _moveItemHook = interop.HookFromAddress((nint)InventoryManager.MemberFunctionPointers.MoveItemSlot, MoveItemDetour); _equipGearsetHook = interop.HookFromAddress((nint)RaptureGearsetModule.MemberFunctionPointers.EquipGearset, EquipGearSetDetour); @@ -58,7 +59,7 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService var ret = _equipGearsetInternalHook.Original(module, gearsetId, glamourPlateId); var set = module->GetGearset((int)gearsetId); _gearsetEvent.Invoke(new ByteString(set->Name).ToString(), (int)gearsetId, prior, glamourPlateId, set->ClassJob); - Glamourer.Log.Verbose($"[InventoryService] [EquipInternal] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); + Glamourer.Log.Verbose($"[InventoryService] Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); if (ret == 0) { var entry = module->GetGearset((int)gearsetId); @@ -131,9 +132,10 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService return ret; } + // Remove once internal is added. This no longer serves any purpose. private int EquipGearSetDetour(RaptureGearsetModule* module, int gearsetId, byte glamourPlateId) { - var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); + var ret = _equipGearsetHook.Original(module, gearsetId, glamourPlateId); Glamourer.Log.Excessive($"[InventoryService] (old) Applied gear set {gearsetId} with glamour plate {glamourPlateId} (Returned {ret})"); return ret; } @@ -216,18 +218,18 @@ public sealed unsafe class InventoryService : IDisposable, IRequiredService private static EquipSlot GetSlot(uint slot) => slot switch { - 0 => EquipSlot.MainHand, - 1 => EquipSlot.OffHand, - 2 => EquipSlot.Head, - 3 => EquipSlot.Body, - 4 => EquipSlot.Hands, - 6 => EquipSlot.Legs, - 7 => EquipSlot.Feet, - 8 => EquipSlot.Ears, - 9 => EquipSlot.Neck, + 0 => EquipSlot.MainHand, + 1 => EquipSlot.OffHand, + 2 => EquipSlot.Head, + 3 => EquipSlot.Body, + 4 => EquipSlot.Hands, + 6 => EquipSlot.Legs, + 7 => EquipSlot.Feet, + 8 => EquipSlot.Ears, + 9 => EquipSlot.Neck, 10 => EquipSlot.Wrists, 11 => EquipSlot.RFinger, 12 => EquipSlot.LFinger, - _ => EquipSlot.Unknown, + _ => EquipSlot.Unknown, }; } diff --git a/Glamourer/Interop/UpdateSlotService.cs b/Glamourer/Interop/UpdateSlotService.cs index 7e5cf59..9ee8d8f 100644 --- a/Glamourer/Interop/UpdateSlotService.cs +++ b/Glamourer/Interop/UpdateSlotService.cs @@ -8,12 +8,11 @@ using Penumbra.GameData.DataContainers; using Penumbra.GameData.Enums; using Penumbra.GameData.Interop; using Penumbra.GameData.Structs; + namespace Glamourer.Interop; -/// -/// This struct is the struct that loadallequipment passes in as its gearsetData container. -/// -[StructLayout(LayoutKind.Explicit)] // Size of 70 bytes maybe? +// This struct is implemented into a PR for FFXIVClientStructs. Once merged, remove this struct and reference the data in ClientStructs instead. +[StructLayout(LayoutKind.Explicit)] public readonly struct GearsetItemDataStruct { // Stores the weapon data. Includes both dyes in the data. @@ -54,30 +53,15 @@ public readonly struct GearsetItemDataStruct public unsafe class UpdateSlotService : IDisposable { + // This function is what calls the weapon/equipment/crest loads, which call FlagSlotForUpdate if different. (MetaData not included) + public const string LoadGearsetDataSig = "48 89 5C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B6 B9"; + private delegate Int64 LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData); + // The above can be removed after the FFXIVClientStruct Merge is made! + public readonly EquipSlotUpdating EquipSlotUpdatingEvent; public readonly BonusSlotUpdating BonusSlotUpdatingEvent; public readonly GearsetDataLoaded GearsetDataLoadedEvent; private readonly DictBonusItems _bonusItems; - - // This function is what calls the weapon/equipment/crest loads, which call FlagSlotForUpdate if different. (MetaData not included) - public const string LoadGearsetDataSig = "48 89 5C 24 ?? 55 56 57 41 54 41 55 41 56 41 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 44 0F B6 B9"; - private delegate Int64 LoadGearsetDataDelegate(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData); - private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData) - { - // Let the gearset data process all of its loads and slot flag update calls first. - var ret = _loadGearsetDataHook.Original(drawDataContainer, gearsetData); - // Ensure that the owner of the drawdata container is a character base. - Model ownerDrawObject = drawDataContainer->OwnerObject->DrawObject; - if (!ownerDrawObject.IsCharacterBase) - return ret; - - // invoke the changed event for the state listener and return. - Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] Owner: 0x{ownerDrawObject.Address:X} Finished Applying its GameState!"); - // Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); - GearsetDataLoadedEvent.Invoke(drawDataContainer->OwnerObject->DrawObject); - return ret; - } - public UpdateSlotService(EquipSlotUpdating equipSlotUpdating, BonusSlotUpdating bonusSlotUpdating, GearsetDataLoaded gearsetDataLoaded, IGameInteropProvider interop, DictBonusItems bonusItems) { @@ -150,7 +134,7 @@ public unsafe class UpdateSlotService : IDisposable private ulong FlagSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { - var slot = slotIdx.ToEquipSlot(); + var slot = slotIdx.ToEquipSlot(); var returnValue = ulong.MaxValue; EquipSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); Glamourer.Log.Excessive($"[FlagSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); @@ -160,7 +144,7 @@ public unsafe class UpdateSlotService : IDisposable private ulong FlagBonusSlotForUpdateDetour(nint drawObject, uint slotIdx, CharacterArmor* data) { - var slot = slotIdx.ToBonusSlot(); + var slot = slotIdx.ToBonusSlot(); var returnValue = ulong.MaxValue; BonusSlotUpdatingEvent.Invoke(drawObject, slot, ref *data, ref returnValue); Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Called with 0x{drawObject:X} for slot {slot} with {*data} ({returnValue:X})."); @@ -173,6 +157,20 @@ public unsafe class UpdateSlotService : IDisposable Glamourer.Log.Excessive($"[FlagBonusSlotForUpdate] Invoked by Glamourer on 0x{drawObject.Address:X} on {slot} with itemdata {armor}."); return _flagSlotForUpdateHook.Original(drawObject.Address, slot.ToIndex(), &armor); } + private Int64 LoadGearsetDataDetour(DrawDataContainer* drawDataContainer, GearsetItemDataStruct* gearsetData) + { + // Let the gearset data process all of its loads and slot flag update calls first. + var ret = _loadGearsetDataHook.Original(drawDataContainer, gearsetData); + Model ownerDrawObject = drawDataContainer->OwnerObject->DrawObject; + if (!ownerDrawObject.IsCharacterBase) + return ret; + + // invoke the changed event for the state listener and return. + Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] Owner: 0x{ownerDrawObject.Address:X} Finished Applying its GameState!"); + // Glamourer.Log.Verbose($"[LoadAllEquipmentDetour] GearsetItemData: {FormatGearsetItemDataStruct(*gearsetData)}"); + GearsetDataLoadedEvent.Invoke(drawDataContainer->OwnerObject->DrawObject); + return ret; + } // If you ever care to debug this, here is a formatted string output of this new gearsetDataPacket struct. private string FormatGearsetItemDataStruct(GearsetItemDataStruct gearsetData) @@ -183,7 +181,6 @@ public unsafe class UpdateSlotService : IDisposable $"\nOffhandWeaponData: Id: {gearsetData.OffhandWeaponData.Id}, Type: {gearsetData.OffhandWeaponData.Type}, " + $"Variant: {gearsetData.OffhandWeaponData.Variant}, Stain0: {gearsetData.OffhandWeaponData.Stain0}, Stain1: {gearsetData.OffhandWeaponData.Stain1}" + $"\nCrestBitField: {gearsetData.CrestBitField} | JobId: {gearsetData.JobId} | UNK_18: {gearsetData.UNK_18} | UNK_19: {gearsetData.UNK_19}"; - // Iterate through offsets from 20 to 60 and format the CharacterArmor data for (int offset = 20; offset <= 56; offset += sizeof(LegacyCharacterArmor)) { LegacyCharacterArmor* equipSlotPtr = (LegacyCharacterArmor*)((byte*)&gearsetData + offset); diff --git a/Glamourer/State/StateEditor.cs b/Glamourer/State/StateEditor.cs index e1bd6a4..891c61d 100644 --- a/Glamourer/State/StateEditor.cs +++ b/Glamourer/State/StateEditor.cs @@ -54,7 +54,7 @@ public class StateEditor( return; var actors = Applier.ChangeCustomize(state, settings.Source.RequiresChange()); - Glamourer.Log.Information( + Glamourer.Log.Verbose( $"Set {idx.ToDefaultName()} customizations in state {state.Identifier.Incognito(null)} from {old.Value} to {value.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Customize, settings.Source, state, actors, new CustomizeTransaction(idx, old, value)); } @@ -67,7 +67,7 @@ public class StateEditor( return; var actors = Applier.ChangeCustomize(state, settings.Source.RequiresChange()); - Glamourer.Log.Information( + Glamourer.Log.Verbose( $"Set {applied} customizations in state {state.Identifier.Incognito(null)} from {old} to {customizeInput}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.EntireCustomize, settings.Source, state, actors, new EntireCustomizeTransaction(applied, old, customizeInput)); @@ -78,10 +78,7 @@ public class StateEditor( { var state = (ActorState)data; if (!Editor.ChangeItem(state, slot, item, settings.Source, out var old, settings.Key)) - { - Glamourer.Log.Information("Not Setting State or invoking, Editor requested us not to change it!"); return; - } var type = slot.ToIndex() < 10 ? StateChangeType.Equip : StateChangeType.Weapon; var actors = type is StateChangeType.Equip @@ -92,8 +89,8 @@ public class StateEditor( if (slot is EquipSlot.MainHand) ApplyMainhandPeriphery(state, item, null, settings); - Glamourer.Log.Debug( - $"[ChangeItem] Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}). [Affecting {actors.ToLazyString("nothing")}.]"); + Glamourer.Log.Verbose( + $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item.Name} ({item.ItemId}). [Affecting {actors.ToLazyString("nothing")}.]"); if (type is StateChangeType.Equip) { @@ -122,8 +119,8 @@ public class StateEditor( return; var actors = Applier.ChangeBonusItem(state, slot, settings.Source.RequiresChange()); - Glamourer.Log.Debug( - $"[ChangeBonus] Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}). [Affecting {actors.ToLazyString("nothing")}.]"); + Glamourer.Log.Verbose( + $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.Id}) to {item.Name} ({item.Id}). [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.BonusItem, settings.Source, state, actors, new BonusItemTransaction(slot, old, item)); } @@ -155,8 +152,8 @@ public class StateEditor( if (slot is EquipSlot.MainHand) ApplyMainhandPeriphery(state, item, stains, settings); - Glamourer.Log.Debug( - $"[ChangeEquip] Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item!.Value.Name} ({item.Value.ItemId}) and its stain from {oldStains} to {stains!.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); + Glamourer.Log.Verbose( + $"Set {slot.ToName()} in state {state.Identifier.Incognito(null)} from {old.Name} ({old.ItemId}) to {item!.Value.Name} ({item.Value.ItemId}) and its stain from {oldStains} to {stains!.Value}. [Affecting {actors.ToLazyString("nothing")}.]"); if (type is StateChangeType.Equip) { StateChanged.Invoke(type, settings.Source, state, actors, new EquipTransaction(slot, old, item!.Value)); @@ -187,7 +184,7 @@ public class StateEditor( return; var actors = Applier.ChangeStain(state, slot, settings.Source.RequiresChange()); - Glamourer.Log.Debug( + Glamourer.Log.Verbose( $"Set {slot.ToName()} stain in state {state.Identifier.Incognito(null)} from {old} to {stains}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Stains, settings.Source, state, actors, new StainTransaction(slot, old, stains)); } @@ -419,8 +416,8 @@ public class StateEditor( var actors = settings.Source.RequiresChange() ? Applier.ApplyAll(state, requiresRedraw, false) : ActorData.Invalid; - - Glamourer.Log.Debug($"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); + + Glamourer.Log.Verbose($"Applied design to {state.Identifier.Incognito(null)}. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Design, state.Sources[MetaIndex.Wetness], state, actors, null); // FIXME: maybe later if(settings.SendStateUpdate) StateUpdated.Invoke(StateUpdateType.DesignApplied, actors); diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 4d10c49..d312815 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -13,8 +13,8 @@ using Glamourer.GameData; using Penumbra.GameData.DataContainers; using Glamourer.Designs; using Penumbra.GameData.Interop; -using ObjectManager = Glamourer.Interop.ObjectManager; using Glamourer.Api.Enums; +using ObjectManager = Glamourer.Interop.ObjectManager; namespace Glamourer.State; diff --git a/Glamourer/State/StateManager.cs b/Glamourer/State/StateManager.cs index 129f8bb..0348148 100644 --- a/Glamourer/State/StateManager.cs +++ b/Glamourer/State/StateManager.cs @@ -274,7 +274,7 @@ public sealed class StateManager( if (source is not StateSource.Game) actors = Applier.ApplyAll(state, redraw, true); - Glamourer.Log.Debug( + Glamourer.Log.Verbose( $"Reset entire state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); // only invoke if we define this reset call as the final call in our state update. @@ -302,7 +302,7 @@ public sealed class StateManager( state.Materials.Clear(); - Glamourer.Log.Debug( + Glamourer.Log.Verbose( $"Reset advanced customization and dye state of {state.Identifier.Incognito(null)} to game base. [Affecting {actors.ToLazyString("nothing")}.]"); StateChanged.Invoke(StateChangeType.Reset, source, state, actors, null); // Update that we have completed a full operation. (We can do this directly as nothing else is linked) @@ -453,22 +453,22 @@ public sealed class StateManager( } } - public void ReapplyState(Actor actor, bool forceRedraw, StateSource source, bool isUpdate = false) + public void ReapplyState(Actor actor, bool forceRedraw, StateSource source, bool stateUpdate = false) { if (!GetOrCreate(actor, out var state)) return; - ReapplyState(actor, state, forceRedraw, source, isUpdate); + ReapplyState(actor, state, forceRedraw, source, stateUpdate); } - public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source, bool isUpdate) + public void ReapplyState(Actor actor, ActorState state, bool forceRedraw, StateSource source, bool stateUpdate) { var data = Applier.ApplyAll(state, forceRedraw || !actor.Model.IsHuman || CustomizeArray.Compare(actor.Model.GetCustomize(), state.ModelData.Customize).RequiresRedraw(), false); StateChanged.Invoke(StateChangeType.Reapply, source, state, data, null); - if(isUpdate) + if(stateUpdate) StateUpdated.Invoke(StateUpdateType.Reapply, data); }