diff --git a/FFXIVClientStructs/FFXIV/Client/Game/Character/Character.cs b/FFXIVClientStructs/FFXIV/Client/Game/Character/Character.cs
index 93f320c7e..de1ff1225 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/FFXIVClientStructs/FFXIV/Client/Game/Control/EmoteController.cs b/FFXIVClientStructs/FFXIV/Client/Game/Control/EmoteController.cs
index 65664a9d3..34bae846a 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,23 @@ 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;
+ [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")]
+ 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 +53,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 e0994373a..039e63391 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
@@ -11012,6 +11013,7 @@ classes:
0x1405F4B00: GetAvailablePoses
0x1405F4B50: Initialize
0x1405F5390: SetPose
+ 0x1405F5470: PlayEmote
0x1405F5870: IsEmoting
0x1405F58B0: IsInEmoteLoop
0x1405F77C0: GetPoseState