From cd538cbf03e56fb232f2cba1c0ba63042b32510e Mon Sep 17 00:00:00 2001
From: PunishedPineapple <50609717+PunishedPineapple@users.noreply.github.com>
Date: Tue, 30 Dec 2025 00:50:36 -0600
Subject: [PATCH 1/4] Branch for PR.
---
.../FFXIV/Client/Game/Character/Character.cs | 1 +
.../FFXIV/Client/Game/NpcYellBalloon.cs | 159 ++++++++++++++++++
ida/data.yml | 10 ++
3 files changed, 170 insertions(+)
create mode 100644 FFXIVClientStructs/FFXIV/Client/Game/NpcYellBalloon.cs
diff --git a/FFXIVClientStructs/FFXIV/Client/Game/Character/Character.cs b/FFXIVClientStructs/FFXIV/Client/Game/Character/Character.cs
index 226ddfa95..b4d6209f0 100644
--- a/FFXIVClientStructs/FFXIV/Client/Game/Character/Character.cs
+++ b/FFXIVClientStructs/FFXIV/Client/Game/Character/Character.cs
@@ -47,6 +47,7 @@ public unsafe partial struct Character {
[FieldOffset(0x1CE8)] public byte ActorControlFlags;
[FieldOffset(0x21E0)] public Balloon Balloon;
+ [FieldOffset(0x2260)] public NpcYellBalloon YellBalloon;
[FieldOffset(0x22E8)] public float Alpha;
diff --git a/FFXIVClientStructs/FFXIV/Client/Game/NpcYellBalloon.cs b/FFXIVClientStructs/FFXIV/Client/Game/NpcYellBalloon.cs
new file mode 100644
index 000000000..884099584
--- /dev/null
+++ b/FFXIVClientStructs/FFXIV/Client/Game/NpcYellBalloon.cs
@@ -0,0 +1,159 @@
+using FFXIVClientStructs.FFXIV.Client.System.String;
+
+namespace FFXIVClientStructs.FFXIV.Client.Game;
+
+// Client::Game::NpcYellBalloon
+// Probably part of Client::Game::Character::Character
+[GenerateInterop]
+[StructLayout(LayoutKind.Explicit, Size = 0x88)]
+public unsafe partial struct NpcYellBalloon {
+
+ ///
+ /// The text that this balloon will display.
+ ///
+ ///
+ /// Empty when balloon is .
+ ///
+ [FieldOffset(0x0)] public Utf8String Text;
+
+ ///
+ /// Pointer to the that contains this balloon.
+ ///
+ [FieldOffset(0x68)] public Character.Character* Character;
+
+ ///
+ /// How much longer (in seconds) the balloon should remain open.
+ ///
+ ///
+ /// Starting value set by OpenBalloon. Starts counting down once in state. If this is set
+ /// to zero before the balloon is activated, the balloon will remain open indefinitely, subject to the value of .
+ ///
+ [FieldOffset(0x70)] public float PlayTimer;
+
+ ///
+ /// How much longer (in seconds) before the balloon should actually be opened with AgentScreenLog.
+ ///
+ ///
+ /// Starting value is set by OpenBalloon. Resets to one second when balloon exits state.
+ ///
+ [FieldOffset(0x74)] public float DelayTime;
+
+ ///
+ /// The balloon's current state.
+ ///
+ [FieldOffset(0x78)] public NpcYellBalloonState State;
+
+ ///
+ /// Controls how the balloon determines when to close.
+ ///
+ ///
+ /// If OpenBalloon's reducedCloseChecks parameter is true, this is , else if
+ /// playTime is zero this is , else this is .
+ ///
+ [FieldOffset(0x7C)] public NpcYellBalloonCloseType CloseType;
+
+ ///
+ /// The bone index to which the balloon is attached on the character.
+ ///
+ [FieldOffset(0x80)] public byte ParentBone;
+
+ ///
+ /// Miscellaneous balloon behaviors.
+ ///
+ [FieldOffset(0x81)] public NpcYellBalloonFlags Flags;
+
+ ///
+ /// Prepares the balloon to be opened during the next applicable Update.
+ ///
+ /// A null-terminated string containing the text to display.
+ /// Time in seconds that the balloon should remain visible. If zero, the balloon will remain open indefinitely (until is called).
+ /// If this is true, the bubble will fade in more gently than the normal "popping" in.
+ /// Time in seconds to wait before actually opening the balloon.
+ /// Whether the balloon text should also be printed to the chat log.
+ /// Skips certain non-timer checks for closing the balloon. Unknown purpose. Usually false.
+ /// Ignore whether the character is "in range" when checking whether to display the balloon.
+ /// The bone index to which the balloon is visually attached. A value of 25 is used if the specified bone does not exist.
+ [MemberFunction("E8 ?? ?? ?? ?? 0F 28 B4 24 ?? ?? ?? ?? 48 8D 4D"), GenerateStringOverloads]
+ public partial void OpenBalloon(CStringPointer str, float playTime, bool softOpen, float openDelay, bool printToLog, bool reducedCloseChecks, bool ignoreRangeCheck, byte parentBone);
+
+ ///
+ /// Closes and resets the balloon.
+ ///
+ ///
+ /// Only required if balloon is set to hold open (or needs to be closed early).
+ ///
+ [MemberFunction("E8 ?? ?? ?? ?? 66 3B BB ?? ?? ?? ?? 75")]
+ public partial void CloseBalloon();
+}
+
+public enum NpcYellBalloonState : int {
+
+ ///
+ /// Balloon is inactive and in the default state.
+ ///
+ Inactive = 0,
+
+ ///
+ /// Balloon is waiting to open (until expires).
+ ///
+ Waiting = 1,
+
+ ///
+ /// Balloon is open.
+ ///
+ Active = 2,
+
+ ///
+ /// Balloon is attempting to open.
+ ///
+ Activating = 3,
+}
+
+public enum NpcYellBalloonCloseType : int {
+
+ ///
+ /// Performs normal timer checks every active update cycle.
+ ///
+ Normal = 0,
+
+ ///
+ /// Ignore and hold open indefinitely until is called.
+ ///
+ HoldOpen = 1,
+
+ ///
+ /// Similar to , but skips some non- checks.
+ ///
+ ReducedCloseChecks = 2,
+}
+
+[Flags]
+public enum NpcYellBalloonFlags : byte {
+ None = 0,
+
+ ///
+ /// All balloons will have this flag while in use.
+ ///
+ ///
+ /// If this is not set, the balloon will not be opened (or will be immediately closed).
+ ///
+ Valid = 1,
+
+ ///
+ /// The balloon fades in instead of opening with a "pop".
+ ///
+ ///
+ /// Is passed as the bool third parameter to AgentScreenLog::OpenBalloon, and probably has the same effect as the Balloon EXD's boolean "Slowly" column.
+ ///
+ SoftOpen = 2,
+
+ ///
+ /// Call AgentScreenLog::OpenBalloon regardless of character range test result.
+ ///
+ IgnoreRangeCheck = 4,
+
+ ///
+ /// Also call RaptureLogModule::PrintMessage with balloon text when the balloon is opened.
+ ///
+ PrintToLog = 8,
+}
diff --git a/ida/data.yml b/ida/data.yml
index ce71cf5fd..531582a56 100644
--- a/ida/data.yml
+++ b/ida/data.yml
@@ -1505,6 +1505,16 @@ classes:
0x14188EB00: Terminate
#oldfail 0x141831340: StartTimerMode # (Balloon* this, float timer, ushort id) if id == 1 use default id else use id
#oldfail 0x141831380: StartOtherMode # (Balloon* this, ushort id) id same as above
+ Client::Game::NpcYellBalloon:
+ funcs:
+ 0x14188EC50: Reset
+ 0x14188ECB0: Ctor
+ 0x14188ED10: Dtor
+ 0x14188ED70: Initialize # (this, chara*)
+ 0x14188EDC0: Update
+ 0x14188F0A0: OpenBalloon # (this, char*, float, bool, float, bool, bool, bool, byte)
+ 0x14188F320: Reset2 # This is identical to Reset, but called from things outside of the class instead of from other member functions.
+ 0x14188F360: CloseBalloon
Client::Game::Fate::FateManager:
instances:
- ea: 0x1429EC2B8
From dc0315ccbfa0426d62e01711e87861315f7aaf11 Mon Sep 17 00:00:00 2001
From: PunishedPineapple <50609717+PunishedPineapple@users.noreply.github.com>
Date: Sun, 4 Jan 2026 20:21:44 -0600
Subject: [PATCH 2/4] Some of NpcYell itself as well.
---
.../FFXIV/Client/Game/UI/NpcYell.cs | 105 +++++++++++++++++-
ida/data.yml | 1 +
2 files changed, 104 insertions(+), 2 deletions(-)
diff --git a/FFXIVClientStructs/FFXIV/Client/Game/UI/NpcYell.cs b/FFXIVClientStructs/FFXIV/Client/Game/UI/NpcYell.cs
index 74e18fc40..fd98bdcc7 100644
--- a/FFXIVClientStructs/FFXIV/Client/Game/UI/NpcYell.cs
+++ b/FFXIVClientStructs/FFXIV/Client/Game/UI/NpcYell.cs
@@ -1,5 +1,106 @@
+using FFXIVClientStructs.FFXIV.Client.Game.Object;
+using FFXIVClientStructs.FFXIV.Client.System.String;
+
namespace FFXIVClientStructs.FFXIV.Client.Game.UI;
// Client::Game::UI::NpcYell
-[StructLayout(LayoutKind.Explicit, Size = 0x1750)]
-public struct NpcYell;
+[GenerateInterop]
+[StructLayout(LayoutKind.Explicit, Size = 0x2850)]
+public partial struct NpcYell {
+ [FieldOffset(0x48), FixedSizeArray] internal FixedSizeArray32 _yellSlots;
+ [FieldOffset(0x548), FixedSizeArray] internal FixedSizeArray32 _yellInfo;
+ [FieldOffset(0x2848)] private short Unk_2848; // Probably number of unhandled yells.
+
+ [StructLayout(LayoutKind.Explicit, Size = 0x28)]
+ public struct NpcYellSlot {
+ [FieldOffset(0x0)] public GameObjectId ObjectId;
+ [FieldOffset(0xC)] public uint NameId;
+
+ ///
+ /// Probably some kind of ID. Matches for the at the same index.
+ ///
+ [FieldOffset(0x10)] private uint Unk_10;
+ [FieldOffset(0x20)] private float Unk_20; // Probably a delay. Has effective frame delta subracted each update.
+ }
+
+ [StructLayout(LayoutKind.Explicit, Size = 0x118)]
+ public struct NpcYellInfo {
+ [FieldOffset(0x0)] public uint NpcYellRowId;
+ [FieldOffset(0x8)] public GameObjectId ObjectId;
+ [FieldOffset(0x10)] public uint NameId;
+ [FieldOffset(0x18)] public Utf8String Name;
+ [FieldOffset(0x80)] public Utf8String Message;
+
+ ///
+ /// Probably some kind of ID. Matches for the at the same index.
+ ///
+ [FieldOffset(0xE8)] private uint Unk_E8;
+
+ ///
+ /// How long (in seconds) to display the message as a balloon.
+ ///
+ ///
+ /// Only used if includes .
+ ///
+ [FieldOffset(0xFC)] public float BalloonTime;
+
+ ///
+ /// How long (in seconds) to display the message in the BattleTalk addon.
+ ///
+ ///
+ /// Only used if includes .
+ ///
+ [FieldOffset(0x100)] public float BattleTalkTime;
+
+ ///
+ /// The method(s) used to show the dialogue to the player.
+ ///
+ [FieldOffset(0x104)] public NpcYellOutputFlags OutputType;
+
+ ///
+ /// Controls which UIModule::ShowBattleTalk is called.
+ ///
+ [FieldOffset(0x108)] private uint Unk_108;
+
+ ///
+ /// The index of the character bone to which to attach the .
+ ///
+ ///
+ /// Only used if includes .
+ ///
+ [FieldOffset(0x10C)] public byte ParentBone;
+ [FieldOffset(0x110)] public NpcYellFlags Flags;
+ }
+
+ [Flags]
+ public enum NpcYellOutputFlags : byte {
+ None = 0,
+ PrintToLog = 1,
+ ShowBalloon = 2,
+ ShowBattleTalk = 4,
+ }
+
+ [Flags]
+ public enum NpcYellFlags : byte {
+ None = 0,
+ ///
+ /// Controls the corresponding flag in the .
+ ///
+ SkipCloseChecks = 1,
+ ///
+ /// Controls the corresponding flag in the .
+ ///
+ ///
+ /// Also checked when formatting NPC name.
+ ///
+ SkipRangeCheck = 2,
+ Unk_4 = 4,
+ Unk_8 = 8,
+ ///
+ /// This is the default value after initialization.
+ ///
+ Unk_10 = 0x10,
+ Unk_20 = 0x20,
+ Unk_40 = 0x40,
+ }
+}
diff --git a/ida/data.yml b/ida/data.yml
index 531582a56..afe43ffb7 100644
--- a/ida/data.yml
+++ b/ida/data.yml
@@ -3188,6 +3188,7 @@ classes:
0x140D78D80: Dtor
0x140B61120: Initialize
0x140B61920: Update
+ 0x140B62200: HandleYell
Client::Game::UI::CharaCard:
instances:
- ea: 0x1429E7C88
From 69c836773f40a32f6a2d9ce7e8ee59e56627d190 Mon Sep 17 00:00:00 2001
From: PunishedPineapple <50609717+PunishedPineapple@users.noreply.github.com>
Date: Sat, 10 Jan 2026 01:17:59 -0600
Subject: [PATCH 3/4] Confirmed UnhandledYellCount.
---
.../FFXIV/Client/Game/UI/NpcYell.cs | 16 ++++++++++++++--
1 file changed, 14 insertions(+), 2 deletions(-)
diff --git a/FFXIVClientStructs/FFXIV/Client/Game/UI/NpcYell.cs b/FFXIVClientStructs/FFXIV/Client/Game/UI/NpcYell.cs
index fd98bdcc7..206b0e840 100644
--- a/FFXIVClientStructs/FFXIV/Client/Game/UI/NpcYell.cs
+++ b/FFXIVClientStructs/FFXIV/Client/Game/UI/NpcYell.cs
@@ -7,9 +7,21 @@ namespace FFXIVClientStructs.FFXIV.Client.Game.UI;
[GenerateInterop]
[StructLayout(LayoutKind.Explicit, Size = 0x2850)]
public partial struct NpcYell {
+
+ ///
+ /// Entries at index and above are stale.
+ ///
[FieldOffset(0x48), FixedSizeArray] internal FixedSizeArray32 _yellSlots;
+
+ ///
+ /// Entries at index and above are stale.
+ ///
[FieldOffset(0x548), FixedSizeArray] internal FixedSizeArray32 _yellInfo;
- [FieldOffset(0x2848)] private short Unk_2848; // Probably number of unhandled yells.
+
+ ///
+ /// The number of new and entries that have not yet been handled.
+ ///
+ [FieldOffset(0x2848)] public short UnhandledYellCount;
[StructLayout(LayoutKind.Explicit, Size = 0x28)]
public struct NpcYellSlot {
@@ -58,7 +70,7 @@ public struct NpcYellInfo {
[FieldOffset(0x104)] public NpcYellOutputFlags OutputType;
///
- /// Controls which UIModule::ShowBattleTalk is called.
+ /// Controls which UIModule::ShowBattleTalk overload is called.
///
[FieldOffset(0x108)] private uint Unk_108;
From 663b2f6947d3b3cd0824306657d9bde9739293ef Mon Sep 17 00:00:00 2001
From: PunishedPineapple <50609717+PunishedPineapple@users.noreply.github.com>
Date: Thu, 15 Jan 2026 18:49:25 -0600
Subject: [PATCH 4/4] Unhandled count should be a byte. Unknown ints were
parameters for the message string.
---
.../FFXIV/Client/Game/UI/NpcYell.cs | 17 ++++++-----------
1 file changed, 6 insertions(+), 11 deletions(-)
diff --git a/FFXIVClientStructs/FFXIV/Client/Game/UI/NpcYell.cs b/FFXIVClientStructs/FFXIV/Client/Game/UI/NpcYell.cs
index 206b0e840..a03fe5a77 100644
--- a/FFXIVClientStructs/FFXIV/Client/Game/UI/NpcYell.cs
+++ b/FFXIVClientStructs/FFXIV/Client/Game/UI/NpcYell.cs
@@ -21,17 +21,15 @@ public partial struct NpcYell {
///
/// The number of new and entries that have not yet been handled.
///
- [FieldOffset(0x2848)] public short UnhandledYellCount;
+ [FieldOffset(0x2848)] public byte UnhandledYellCount;
+ [FieldOffset(0x2849)] private byte Unk_2849; // Looks like a counter that has something to do specifically with entries that show a BattleTalk.
+ [FieldOffset(0x284A)] private bool Unk_284A;
[StructLayout(LayoutKind.Explicit, Size = 0x28)]
public struct NpcYellSlot {
[FieldOffset(0x0)] public GameObjectId ObjectId;
[FieldOffset(0xC)] public uint NameId;
-
- ///
- /// Probably some kind of ID. Matches for the at the same index.
- ///
- [FieldOffset(0x10)] private uint Unk_10;
+ [FieldOffset(0x10)] private uint Unk_MessageParams; // A (probably) four-long array of integer parameters for the message string.
[FieldOffset(0x20)] private float Unk_20; // Probably a delay. Has effective frame delta subracted each update.
}
@@ -42,11 +40,8 @@ public struct NpcYellInfo {
[FieldOffset(0x10)] public uint NameId;
[FieldOffset(0x18)] public Utf8String Name;
[FieldOffset(0x80)] public Utf8String Message;
-
- ///
- /// Probably some kind of ID. Matches for the at the same index.
- ///
- [FieldOffset(0xE8)] private uint Unk_E8;
+ [FieldOffset(0xE8)] private uint Unk_MessageParams; // A (probably) four-long array of integer parameters for the message string.
+ [FieldOffset(0xF8)] private float Unk_F8; // Countdown timer of unknown use. Has effective frame delta subracted each update.
///
/// How long (in seconds) to display the message as a balloon.