diff --git a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs index 04baa06..6963053 100644 --- a/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs +++ b/Glamourer/Gui/Tabs/ActorTab/ActorPanel.cs @@ -68,7 +68,7 @@ public class ActorPanel private CustomizeFlag CustomizeApplicationFlags => _lockedRedraw ? CustomizeFlagExtensions.AllRelevant & ~CustomizeFlagExtensions.RedrawRequired : CustomizeFlagExtensions.AllRelevant; - public void Draw() + public unsafe void Draw() { using var group = ImRaii.Group(); (_identifier, _data) = _selector.Selection; @@ -114,12 +114,18 @@ public class ActorPanel if (!child || !_selector.HasSelection || !_stateManager.GetOrCreate(_identifier, _actor, out _state)) return; + var transformationId = _actor.IsCharacter ? _actor.AsCharacter->CharacterData.TransformationId : 0; + if (transformationId != 0) + ImGuiUtil.DrawTextButton($"Currently transformed to Transformation {transformationId}.", + -Vector2.UnitX, Colors.SelectedRed); + DrawApplyToSelf(); ImGui.SameLine(); DrawApplyToTarget(); RevertButtons(); + using var disabled = ImRaii.Disabled(transformationId != 0); if (_state.ModelData.IsHuman) DrawHumanPanel(); else diff --git a/Glamourer/Interop/Structs/Actor.cs b/Glamourer/Interop/Structs/Actor.cs index 644d904..1d1d172 100644 --- a/Glamourer/Interop/Structs/Actor.cs +++ b/Glamourer/Interop/Structs/Actor.cs @@ -45,6 +45,9 @@ public readonly unsafe struct Actor : IEquatable public bool IsGPoseOrCutscene => Index.Index is >= (int)ScreenActor.CutsceneStart and < (int)ScreenActor.CutsceneEnd; + public bool IsTransformed + => AsCharacter->CharacterData.TransformationId != 0; + public ActorIdentifier GetIdentifier(ActorManager actors) => actors.FromObject(AsObject, out _, true, true, false); diff --git a/Glamourer/State/FunModule.cs b/Glamourer/State/FunModule.cs index 06ed9ef..afa5b5e 100644 --- a/Glamourer/State/FunModule.cs +++ b/Glamourer/State/FunModule.cs @@ -92,10 +92,7 @@ public unsafe class FunModule : IDisposable public void ApplyFun(Actor actor, ref CharacterArmor armor, EquipSlot slot) { - if (!actor.IsCharacter || actor.AsObject->ObjectKind is not (byte)ObjectKind.Player) - return; - - if (actor.AsCharacter->CharacterData.ModelCharaId != 0) + if (!ValidFunTarget(actor)) return; if (_config.DisableFestivals == 0 && _festivalSet != null @@ -112,10 +109,7 @@ public unsafe class FunModule : IDisposable public void ApplyFun(Actor actor, Span armor, ref Customize customize) { - if (!actor.IsCharacter || actor.AsObject->ObjectKind is not (byte)ObjectKind.Player) - return; - - if (actor.AsCharacter->CharacterData.ModelCharaId != 0) + if (!ValidFunTarget(actor)) return; if (_config.DisableFestivals == 0 && _festivalSet != null) @@ -140,16 +134,19 @@ public unsafe class FunModule : IDisposable public void ApplyFun(Actor actor, ref CharacterWeapon weapon, EquipSlot slot) { - if (!actor.IsCharacter || actor.AsObject->ObjectKind is not (byte)ObjectKind.Player) - return; - - if (actor.AsCharacter->CharacterData.ModelCharaId != 0) + if (!ValidFunTarget(actor)) return; if (_codes.EnabledWorld) _worldSets.Apply(actor, _rng, ref weapon, slot); } + private static bool ValidFunTarget(Actor actor) + => actor.IsCharacter + && actor.AsObject->ObjectKind is (byte)ObjectKind.Player + && !actor.IsTransformed + && actor.AsCharacter->CharacterData.ModelCharaId == 0; + public void ApplyClown(Span armors) { if (!_codes.EnabledClown) diff --git a/Glamourer/State/StateListener.cs b/Glamourer/State/StateListener.cs index 6e1d060..791a0f8 100644 --- a/Glamourer/State/StateListener.cs +++ b/Glamourer/State/StateListener.cs @@ -13,6 +13,7 @@ using Penumbra.GameData.Structs; using System; using Dalamud.Game.ClientState.Conditions; using Dalamud.Plugin.Services; +using Glamourer.Structs; namespace Glamourer.State; @@ -116,6 +117,9 @@ public class StateListener : IDisposable /// The base state changed compared to prior state. Change, + + /// Special case for hat stuff. + HatHack, } /// @@ -315,7 +319,6 @@ public class StateListener : IDisposable _manager.ChangeStain(state, slot, state.BaseData.Stain(slot), StateChanged.Source.Game); else apply = true; - break; case UpdateState.NoChange: apply = true; @@ -359,12 +362,20 @@ public class StateListener : IDisposable if (actorArmor.Value != armor.Value) { // Update base data in case hat visibility is off. - if (slot is EquipSlot.Head && armor.Value == 0 && actorArmor.Value != state.BaseData.Armor(EquipSlot.Head).Value) + if (slot is EquipSlot.Head && armor.Value == 0) { - var item = _items.Identify(slot, actorArmor.Set, actorArmor.Variant); - state.BaseData.SetItem(EquipSlot.Head, item); - state.BaseData.SetStain(EquipSlot.Head, actorArmor.Stain); - return UpdateState.Change; + if (actor.IsTransformed) + return UpdateState.Transformed; + + if (actorArmor.Value != state.BaseData.Armor(EquipSlot.Head).Value) + { + var item = _items.Identify(slot, actorArmor.Set, actorArmor.Variant); + state.BaseData.SetItem(EquipSlot.Head, item); + state.BaseData.SetStain(EquipSlot.Head, actorArmor.Stain); + return UpdateState.Change; + } + + return UpdateState.HatHack; } if (!fistWeapon) @@ -410,12 +421,10 @@ public class StateListener : IDisposable if (apply) armor = state.ModelData.ArmorWithState(slot); - break; // Use current model data. - // Transformed also handles invisible hat state. case UpdateState.NoChange: - case UpdateState.Transformed when slot is EquipSlot.Head && armor.Value is 0: + case UpdateState.HatHack: armor = state.ModelData.ArmorWithState(slot); break; case UpdateState.Transformed: break; @@ -423,8 +432,15 @@ public class StateListener : IDisposable } /// Update base data for a single changed weapon slot. - private UpdateState UpdateBaseData(Actor actor, ActorState state, EquipSlot slot, CharacterWeapon weapon) + private unsafe UpdateState UpdateBaseData(Actor actor, ActorState state, EquipSlot slot, CharacterWeapon weapon) { + if (actor.AsCharacter->CharacterData.TransformationId != 0) + { + var actorWeapon = slot is EquipSlot.MainHand ? actor.GetMainhand() : actor.GetOffhand(); + if (weapon.Value != actorWeapon.Value) + return UpdateState.Transformed; + } + var baseData = state.BaseData.Weapon(slot); var change = UpdateState.NoChange;