Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
159 changes: 159 additions & 0 deletions FFXIVClientStructs/FFXIV/Client/Game/NpcYellBalloon.cs
Original file line number Diff line number Diff line change
@@ -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 {

/// <summary>
/// The text that this balloon will display.
/// </summary>
/// <remarks>
/// Empty when balloon is <see cref="NpcYellBalloonState.Inactive"/>.
/// </remarks>
[FieldOffset(0x0)] public Utf8String Text;

/// <summary>
/// Pointer to the <see cref="Character.Character"/> that contains this balloon.
/// </summary>
[FieldOffset(0x68)] public Character.Character* Character;

/// <summary>
/// How much longer (in seconds) the balloon should remain open.
/// </summary>
/// <remarks>
/// Starting value set by OpenBalloon. Starts counting down once in <see cref="NpcYellBalloonState.Active"/> state. If this is set
/// to zero before the balloon is activated, the balloon will remain open indefinitely, subject to the value of <see cref="CloseType"/>.
/// </remarks>
[FieldOffset(0x70)] public float PlayTimer;

/// <summary>
/// How much longer (in seconds) before the balloon should actually be opened with AgentScreenLog.
/// </summary>
/// <remarks>
/// Starting value is set by OpenBalloon. Resets to one second when balloon exits <see cref="NpcYellBalloonState.Waiting"/> state.
/// </remarks>
[FieldOffset(0x74)] public float DelayTime;

/// <summary>
/// The balloon's current state.
/// </summary>
[FieldOffset(0x78)] public NpcYellBalloonState State;

/// <summary>
/// Controls how the balloon determines when to close.
/// </summary>
/// <remarks>
/// If OpenBalloon's reducedCloseChecks parameter is true, this is <see cref="NpcYellBalloonCloseType.ReducedCloseChecks"/>, else if
/// playTime is zero this is <see cref="NpcYellBalloonCloseType.HoldOpen"/>, else this is <see cref="NpcYellBalloonCloseType.Normal"/>.
/// </remarks>
[FieldOffset(0x7C)] public NpcYellBalloonCloseType CloseType;

/// <summary>
/// The bone index to which the balloon is attached on the character.
/// </summary>
[FieldOffset(0x80)] public byte ParentBone;

/// <summary>
/// Miscellaneous balloon behaviors.
/// </summary>
[FieldOffset(0x81)] public NpcYellBalloonFlags Flags;

/// <summary>
/// Prepares the balloon to be opened during the next applicable Update.
/// </summary>
/// <param name="str">A null-terminated string containing the text to display.</param>
/// <param name="playTime">Time in seconds that the balloon should remain visible. If zero, the balloon will remain open indefinitely (until <see cref="CloseBalloon"/> is called).</param>
/// <param name="softOpen">If this is true, the bubble will fade in more gently than the normal "popping" in.</param>
/// <param name="openDelay">Time in seconds to wait before actually opening the balloon.</param>
/// <param name="printToLog">Whether the balloon text should also be printed to the chat log.</param>
/// <param name="reducedCloseChecks">Skips certain non-timer checks for closing the balloon. Unknown purpose. Usually false.</param>
/// <param name="ignoreRangeCheck">Ignore whether the character is "in range" when checking whether to display the balloon.</param>
/// <param name="parentBone">The bone index to which the balloon is visually attached. A value of 25 is used if the specified bone does not exist.</param>
[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);

/// <summary>
/// Closes and resets the balloon.
/// </summary>
/// <remarks>
/// Only required if balloon is set to hold open (or needs to be closed early).
/// </remarks>
[MemberFunction("E8 ?? ?? ?? ?? 66 3B BB ?? ?? ?? ?? 75")]
public partial void CloseBalloon();
}

public enum NpcYellBalloonState : int {

/// <summary>
/// Balloon is inactive and in the default state.
/// </summary>
Inactive = 0,

/// <summary>
/// Balloon is waiting to open (until <see cref="NpcYellBalloon.DelayTime"/> expires).
/// </summary>
Waiting = 1,

/// <summary>
/// Balloon is open.
/// </summary>
Active = 2,

/// <summary>
/// Balloon is attempting to open.
/// </summary>
Activating = 3,
}

public enum NpcYellBalloonCloseType : int {

/// <summary>
/// Performs normal timer checks every active update cycle.
/// </summary>
Normal = 0,

/// <summary>
/// Ignore <see cref="NpcYellBalloon.PlayTimer"/> and hold open indefinitely until <see cref="NpcYellBalloon.CloseBalloon"/> is called.
/// </summary>
HoldOpen = 1,

/// <remarks>
/// Similar to <see cref="Normal"/>, but skips some non-<see cref="NpcYellBalloon.PlayTimer"/> checks.
/// </remarks>
ReducedCloseChecks = 2,
}

[Flags]
public enum NpcYellBalloonFlags : byte {
None = 0,

/// <summary>
/// All balloons will have this flag while in use.
/// </summary>
/// <remarks>
/// If this is not set, the balloon will not be opened (or will be immediately closed).
/// </remarks>
Valid = 1,

/// <summary>
/// The balloon fades in instead of opening with a "pop".
/// </summary>
/// <remarks>
/// Is passed as the bool third parameter to AgentScreenLog::OpenBalloon, and probably has the same effect as the Balloon EXD's boolean "Slowly" column.
/// </remarks>
SoftOpen = 2,

/// <remarks>
/// Call AgentScreenLog::OpenBalloon regardless of character range test result.
/// </remarks>
IgnoreRangeCheck = 4,

/// <remarks>
/// Also call RaptureLogModule::PrintMessage with balloon text when the balloon is opened.
/// </remarks>
PrintToLog = 8,
}
157 changes: 155 additions & 2 deletions FFXIVClientStructs/FFXIV/Client/Game/UI/NpcYell.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,158 @@
using FFXIVClientStructs.FFXIV.Client.Game.Object;
using FFXIVClientStructs.FFXIV.Common.Component.Excel;

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 unsafe partial struct NpcYell {
[StaticAddress("48 8D 0D ?? ?? ?? ?? 0F 11 44 24 ?? C6 44 24", 3)]
public static partial NpcYell* Instance();

/// <remarks>
/// Null until at least one NpcYell has been queued.
/// </remarks>
[FieldOffset(0x10)] public ExcelSheet* NpcYellSheet;

/// <remarks>
/// Entries at index <see cref="UnhandledYellCount"/> and above are stale.
/// </remarks>
[FieldOffset(0x48), FixedSizeArray] internal FixedSizeArray32<NpcYellSlot> _yellSlots;

/// <remarks>
/// Entries at index <see cref="UnhandledYellCount"/> and above are stale.
/// </remarks>
[FieldOffset(0x548), FixedSizeArray] internal FixedSizeArray32<NpcYellInfo> _yellInfo;

/// <summary>
/// The number of new <see cref="YellSlots"/> and <see cref="YellInfo"/> entries that have not yet been handled.
/// </summary>
[FieldOffset(0x2848)] public byte UnhandledYellCount;
[FieldOffset(0x2849)] public byte UnhandledBattleTalkCount;
[FieldOffset(0x284A)] private bool Unk284A; // Probably indicates waiting on the Excel sheet when an ENpc is involved, but can't tell for sure.

[GenerateInterop]
[StructLayout(LayoutKind.Explicit, Size = 0x28)]
public partial struct NpcYellSlot {
[FieldOffset(0x0)] public GameObjectId ObjectId;
/// <remarks>
/// Only valid before this slot is handled.
/// </remarks>
[FieldOffset(0x8)] public uint NpcYellRowId;
/// <remarks>
/// In the case of the speaker being an event NPC (i.e., for Gold Saucer announcements), this will instead contain the ENpcResident row ID.
/// </remarks>
[FieldOffset(0xC)] public uint NameId;
[FieldOffset(0x10), FixedSizeArray] internal FixedSizeArray4<uint> _messageParams;
/// <summary>
/// How much longer (in seconds) before the yell should be expanded into a <see cref="NpcYellInfo"/>.
/// </summary>
[FieldOffset(0x20)] public float Delay;
[FieldOffset(0x24)] private bool Unk24; // Something to do with whether this entry shows a BattleTalk, but only sometimes.
}

[GenerateInterop]
[StructLayout(LayoutKind.Explicit, Size = 0x118)]
public partial struct NpcYellInfo {
[FieldOffset(0x0)] public uint NpcYellRowId;
[FieldOffset(0x8)] public GameObjectId ObjectId;
/// <remarks>
/// In the case of the speaker being an event NPC (i.e., for Gold Saucer announcements), this will instead contain the ENpcResident row ID.
/// </remarks>
[FieldOffset(0x10)] public uint NameId;
[FieldOffset(0x18)] public Utf8String Name;
[FieldOffset(0x80)] public Utf8String Message;
[FieldOffset(0xE8), FixedSizeArray] internal FixedSizeArray4<uint> _messageParams;

/// <summary>
/// How much longer (in seconds) before the yell should be handled.
/// </summary>
[FieldOffset(0xF8)] public float Delay;

/// <summary>
/// How long (in seconds) to display the message as a balloon.
/// </summary>
/// <remarks>
/// Only used if <see cref="OutputType"/> includes <see cref="NpcYellOutputFlags.ShowBalloon"/>.
/// </remarks>
[FieldOffset(0xFC)] public float BalloonTime;

/// <summary>
/// How long (in seconds) to display the message in the BattleTalk addon.
/// </summary>
/// <remarks>
/// Only used if <see cref="OutputType"/> includes <see cref="NpcYellOutputFlags.ShowBattleTalk"/>.
/// </remarks>
[FieldOffset(0x100)] public float BattleTalkTime;

/// <summary>
/// The method(s) used to show the dialogue to the player.
/// </summary>
[FieldOffset(0x104)] public NpcYellOutputFlags OutputType;

/// <summary>
/// The background style used if <see cref="OutputType"/> includes <see cref="NpcYellOutputFlags.ShowBattleTalk"/>.
/// </summary>
/// <remarks>
/// 6 is linkshell-style.<br/>
/// 7 is dark.<br/>
/// 9 and 11 are robot-style (blue).<br/>
/// Others are default white.
/// </remarks>
[FieldOffset(0x105)] public byte BattleTalkStyle;

/// <summary>
/// The icon ID to show as the BattleTalk portrait if <see cref="OutputType"/> includes <see cref="NpcYellOutputFlags.ShowBattleTalk"/>.
/// </summary>
[FieldOffset(0x108)] public uint BattleTalkImage;

/// <summary>
/// The index of the character bone to which to attach the <see cref="NpcYellBalloon"/>.
/// </summary>
/// <remarks>
/// Only used if <see cref="OutputType"/> includes <see cref="NpcYellOutputFlags.ShowBalloon"/>.
/// </remarks>
[FieldOffset(0x10C)] public byte ParentBone;
[FieldOffset(0x10E)] private ushort Unk10E; // Copied to NpcYellSot 0x8 in one function. May be sheet row ID sometimes, but that doesn't make a ton of sense, and have never seen it used during gameplay.
[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,
/// <summary>
/// Controls the corresponding flag in the <see cref="NpcYellBalloon"/>.
/// </summary>
SkipCloseChecks = 1,
/// <summary>
/// Controls the corresponding flag in the <see cref="NpcYellBalloon"/>.
/// </summary>
/// <remarks>
/// Also checked when formatting NPC name.
/// </remarks>
SkipRangeCheck = 2,
Unk4 = 4,
/// <remarks>
/// Causes speaker name to be an empty string.
/// </remarks>
HideSpeakerName = 8,
/// <summary>
/// Indicates that this slot is currently ready to be assigned yell information.
/// </summary>
/// <remarks>
/// If this is set, all other information in the structure should be considered stale.
/// </remarks>
SlotAvailable = 0x10,
Unk20 = 0x20,
Unk40 = 0x40, // May indicate that message string is valid. Is set in certain places after copying the string.
}
}
12 changes: 12 additions & 0 deletions ida/data.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,16 @@ classes:
0x1418906B0: 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:
0x141890800: Reset
0x141890860: Ctor
0x1418908C0: Dtor
0x141890920: Initialize # (this, chara*)
0x141890970: Update
0x141890C50: OpenBalloon # (this, char*, float, bool, float, bool, bool, bool, byte)
0x141890ED0: Reset2 # This is identical to Reset, but called from things outside of the class instead of from other member functions.
0x141890F10: CloseBalloon
Client::Game::Fate::FateManager:
instances:
- ea: 0x1429E02C8
Expand Down Expand Up @@ -3237,7 +3247,9 @@ classes:
0x140B626D0: ctor
0x140D7A280: Dtor
0x140B62830: Initialize
0x140B62A70: QueueYell
0x140B63030: Update
0x140B63910: HandleYell
Client::Game::UI::CharaCard:
instances:
- ea: 0x1429DBC98
Expand Down