From f16d6494640b07c73b243c97baf69068b394d723 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Wed, 25 Mar 2026 20:36:26 +0100 Subject: [PATCH 1/3] Add EmoteController.PlayEmote --- .../FFXIV/Client/Game/Control/EmoteController.cs | 14 +++++++++++--- ida/data.yml | 1 + 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/FFXIVClientStructs/FFXIV/Client/Game/Control/EmoteController.cs b/FFXIVClientStructs/FFXIV/Client/Game/Control/EmoteController.cs index 65664a9d31..ee641d97e7 100644 --- a/FFXIVClientStructs/FFXIV/Client/Game/Control/EmoteController.cs +++ b/FFXIVClientStructs/FFXIV/Client/Game/Control/EmoteController.cs @@ -1,6 +1,7 @@ using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Object; using FFXIVClientStructs.FFXIV.Client.LayoutEngine; +using FFXIVClientStructs.FFXIV.Client.UI.Agent; namespace FFXIVClientStructs.FFXIV.Client.Game.Control; @@ -10,11 +11,17 @@ namespace FFXIVClientStructs.FFXIV.Client.Game.Control; public unsafe partial struct EmoteController { [FieldOffset(0x08)] public BattleChara* OwnerObject; [FieldOffset(0x14)] public ushort EmoteId; - [FieldOffset(0x16)] private ushort Unk1; // Seems to be 1 when close enough to a target that height adjustment is needed, maybe. + [FieldOffset(0x16)] public byte Stance; + [FieldOffset(0x17)] private byte Unk17; [FieldOffset(0x18)] public GameObjectId Target; [FieldOffset(0x20)] public PoseType CurrentPoseType; [FieldOffset(0x21)] public byte CPoseState; + /// Plays an emote for a character that's not the local player. + /// For the local player, use or instead. + [MemberFunction("E8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B7 44 24")] + public partial bool PlayEmote(uint emoteId, PlayEmoteOption* options); + [MemberFunction("E8 ?? ?? ?? ?? 8B F8 83 F8 FF 0F 84 ?? ?? ?? ?? 8B CF")] public partial int GetPoseKind(); @@ -40,9 +47,10 @@ public enum PoseType : byte { [VirtualTable("48 8D 05 ?? ?? ?? ?? 48 8D 0D ?? ?? ?? ?? 48 89 44 24", 3)] public partial struct PlayEmoteOption { [FieldOffset(0x08)] public GameObjectId TargetId; - /// If bit 1 is set it does not display a log message. + [BitField(nameof(DisableLogMessage), 0, 1)] [FieldOffset(0x10)] public byte Flags; - [FieldOffset(0x11)] private bool Unk11; + /// For example, this might be useful to set for spawning characters in a sitting pose. + [FieldOffset(0x11)] public bool DisableSitDownAnimation; // or DisableInitAnimation? [FieldOffset(0x18)] public ILayoutInstance* Layout; } } diff --git a/ida/data.yml b/ida/data.yml index e0994373a1..59dca05adb 100644 --- a/ida/data.yml +++ b/ida/data.yml @@ -11012,6 +11012,7 @@ classes: 0x1405F4B00: GetAvailablePoses 0x1405F4B50: Initialize 0x1405F5390: SetPose + 0x1405F5470: PlayEmote 0x1405F5870: IsEmoting 0x1405F58B0: IsInEmoteLoop 0x1405F77C0: GetPoseState From 279f9deb62ca13673dbeda9a79a2f9f7878b7a16 Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Wed, 25 Mar 2026 20:36:39 +0100 Subject: [PATCH 2/3] Add Character.ResolveTargetedEmoteId --- .../FFXIV/Client/Game/Character/Character.cs | 10 ++++++++++ ida/data.yml | 1 + 2 files changed, 11 insertions(+) diff --git a/FFXIVClientStructs/FFXIV/Client/Game/Character/Character.cs b/FFXIVClientStructs/FFXIV/Client/Game/Character/Character.cs index 93f320c7eb..de1ff12252 100644 --- a/FFXIVClientStructs/FFXIV/Client/Game/Character/Character.cs +++ b/FFXIVClientStructs/FFXIV/Client/Game/Character/Character.cs @@ -147,6 +147,16 @@ public unsafe partial struct Character { [MemberFunction("E8 ?? ?? ?? ?? 84 C0 75 05 8B 4D F4")] public partial bool IsInPvP(); + /// + /// Resolves the correct emote id, based on the targets height or distance. + /// + /// The base emote id. + /// The PlayEmote options. + /// The adjusted emote id for the target. + /// For example, this is used for Throw/Snowball, Dote, Splash, All Saints' Charm, Bouquet or Photograph. + [MemberFunction("E8 ?? ?? ?? ?? 48 8B 5D ?? 0F B7 F8")] + public partial ushort ResolveTargetedEmoteId(ushort emoteId, EmoteController.PlayEmoteOption* options); // TODO: judging from the address, this might be a static function + [VirtualFunction(77)] public partial StatusManager* GetStatusManager(); diff --git a/ida/data.yml b/ida/data.yml index 59dca05adb..039e633919 100644 --- a/ida/data.yml +++ b/ida/data.yml @@ -10955,6 +10955,7 @@ classes: 0x14087FB60: HasStatus 0x1408AF6E0: IsJumping 0x140ADBB00: IsInPvP + 0x1405F62D0: ResolveTargetedEmoteId # static? Client::Game::Character::Character::CastInfo: funcs: 0x1408B3190: Reset From 05eb031970688a36d37e86abac4ded49b82ebc7a Mon Sep 17 00:00:00 2001 From: Haselnussbomber Date: Wed, 25 Mar 2026 21:08:43 +0100 Subject: [PATCH 3/3] Add IsEmoting, IsInEmoteLoop --- .../FFXIV/Client/Game/Control/EmoteController.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/FFXIVClientStructs/FFXIV/Client/Game/Control/EmoteController.cs b/FFXIVClientStructs/FFXIV/Client/Game/Control/EmoteController.cs index ee641d97e7..34bae846af 100644 --- a/FFXIVClientStructs/FFXIV/Client/Game/Control/EmoteController.cs +++ b/FFXIVClientStructs/FFXIV/Client/Game/Control/EmoteController.cs @@ -17,6 +17,12 @@ public unsafe partial struct EmoteController { [FieldOffset(0x20)] public PoseType CurrentPoseType; [FieldOffset(0x21)] public byte CPoseState; + [MemberFunction("E8 ?? ?? ?? ?? 84 C0 75 ?? 48 39 5D")] + public partial bool IsEmoting(); // EmoteId != 0 + + [MemberFunction("E8 ?? ?? ?? ?? 84 C0 74 ?? 33 D2 48 8D 8E")] + public partial bool IsInEmoteLoop(); // OwnerObject->Mode == CharacterModes.EmoteLoop + /// Plays an emote for a character that's not the local player. /// For the local player, use or instead. [MemberFunction("E8 ?? ?? ?? ?? E8 ?? ?? ?? ?? 44 0F B7 44 24")]