Every claim in the user-facing docs, checked against what the plugin actually does after A2 to A5. The two GDPR sections in PRIVACY.md describe features that only exist again as of this cycle, so they now name the screens they live on and say what the cleanup does with a channel this build does not recognise. The export sentence promised a date range; the form offers an age in days. PRIVACY.md also named "Show emotes" in Settings as the way to stop the one outbound call the plugin makes. That switch has had no control since May, and a documented opt-out that only exists in the JSON is not an opt-out, so the toggle is back in the chat tab. Both its strings were already translated. Dead paths: the README source tree still listed a file deleted in May, docs/IPC.md cited a window that no longer exists, the first-run wizard pointed at the same removed file, and the theme authoring guide sent readers to a Themes tab that is called Appearance. The changelog lost two double blank lines that were failing markdownlint and holding preflight red. Preflight is green again. Historical documents keep their old paths on purpose: the changelog and the AI disclosure describe what was true when they were written.
239 lines
12 KiB
Markdown
Executable File
239 lines
12 KiB
Markdown
Executable File
# Hellion Chat IPC Integration Guide
|
|
|
|
This document describes the inter-plugin-communication (IPC) channels that Hellion Chat exposes to
|
|
other Dalamud plugins. Two integration surfaces are covered: the **Context Menu IPC** for adding
|
|
custom items to Hellion Chat's right-click menus, and the **Typing State IPC** for reacting to the
|
|
user's input-box activity.
|
|
|
|
---
|
|
|
|
## Compatibility with Chat 2
|
|
|
|
Hellion Chat is a standalone fork of [Chat 2](https://github.com/Infiziert90/ChatTwo) (EUPL-1.2).
|
|
The IPC surface is one of the parts the fork inherits directly: the same call shapes, the same tuple
|
|
payloads, the same call semantics, the same lifecycle. We did not redesign the API, we re-published
|
|
it under our own plugin name.
|
|
|
|
Concretely, this means:
|
|
|
|
- **Tuple shapes are identical.** A subscriber that worked against Chat 2's `ChatTwo.Invoke` works
|
|
against Hellion Chat's `HellionChat.Invoke` without any code change beyond the channel string.
|
|
- **Lifecycle is identical.** The `Available` ping fires when the plugin becomes ready, your
|
|
subscriber re-registers, and the registration ID is returned by the same `Register` call as
|
|
before.
|
|
- **Channel-name prefix changed in v1.0.0.** Every `ChatTwo.*` channel name is now `HellionChat.*`.
|
|
Existing third-party integrations need a one-line rename per channel string and nothing else.
|
|
|
|
If your plugin already supports Chat 2 and you want to add Hellion Chat support, the cleanest path
|
|
is to bind both prefixes and treat whichever one becomes available first as the active host.
|
|
|
|
---
|
|
|
|
## Channel Reference
|
|
|
|
| Surface | Channel | Direction | Payload |
|
|
| ------------ | ----------------------------------- | --------------- | ----------------------------------------------------------------------------------- |
|
|
| Context Menu | `HellionChat.Available` | plugin → caller | event, no payload |
|
|
| Context Menu | `HellionChat.Register` | caller → plugin | returns registration `string` ID |
|
|
| Context Menu | `HellionChat.Unregister` | caller → plugin | takes registration ID `string` |
|
|
| Context Menu | `HellionChat.Invoke` | plugin → caller | event, see Context Menu section |
|
|
| Typing State | `HellionChat.GetChatInputState` | caller → plugin | returns the typing-state tuple |
|
|
| Typing State | `HellionChat.ChatInputStateChanged` | plugin → caller | event, fires once on subscribe and on every state change, payload is the same tuple |
|
|
|
|
---
|
|
|
|
## Context Menu IPC
|
|
|
|
Use this surface to draw your own selectables inside Hellion Chat's right-click context menus. All
|
|
registrations are called inside an ImGui `BeginMenu`, so anything you draw appears as a regular menu
|
|
entry.
|
|
|
|
### Lifecycle
|
|
|
|
1. Subscribe to `HellionChat.Available`. The host fires this once when it loads or reloads, so your
|
|
plugin can re-register without polling.
|
|
2. Call `HellionChat.Register` to obtain a registration ID. Save it. You need it to filter `Invoke`
|
|
callbacks that target your registration and to call `Unregister` later.
|
|
3. Subscribe to `HellionChat.Invoke` and draw your menu items inside the handler when the `id`
|
|
matches your saved registration ID.
|
|
4. On plugin disable or unload, call `HellionChat.Unregister` with your saved ID and unsubscribe
|
|
from `Invoke`.
|
|
|
|
### Example
|
|
|
|
```cs
|
|
public class HellionChatContextMenu {
|
|
// Used to register your plugin with the IPC; returns an ID you must save.
|
|
private ICallGateSubscriber<string> Register { get; }
|
|
// Used to unregister your plugin from the IPC; call this on unload.
|
|
private ICallGateSubscriber<string, object?> Unregister { get; }
|
|
// Subscribe to receive a notification when Hellion Chat becomes ready
|
|
// or reloads, so you can re-register.
|
|
private ICallGateSubscriber<object?> Available { get; }
|
|
// Subscribe to draw your custom context-menu items.
|
|
private ICallGateSubscriber<string, PlayerPayload?, ulong, Payload?, SeString?, SeString?, object?> Invoke { get; }
|
|
|
|
private string? _id;
|
|
|
|
public HellionChatContextMenu(DalamudPluginInterface @interface) {
|
|
this.Register = @interface.GetIpcSubscriber<string>("HellionChat.Register");
|
|
this.Unregister = @interface.GetIpcSubscriber<string, object?>("HellionChat.Unregister");
|
|
this.Invoke = @interface.GetIpcSubscriber<string, PlayerPayload?, ulong, Payload?, SeString?, SeString?, object?>("HellionChat.Invoke");
|
|
this.Available = @interface.GetIpcSubscriber<object?>("HellionChat.Available");
|
|
}
|
|
|
|
public void Enable() {
|
|
// Re-register automatically when Hellion Chat becomes ready or reloads.
|
|
this.Available.Subscribe(() => this.DoRegister());
|
|
// Register if Hellion Chat is already loaded.
|
|
this.DoRegister();
|
|
|
|
// Listen for context-menu events.
|
|
this.Invoke.Subscribe(this.OnInvoke);
|
|
}
|
|
|
|
private void DoRegister() {
|
|
this._id = this.Register.InvokeFunc();
|
|
}
|
|
|
|
public void Disable() {
|
|
if (this._id != null) {
|
|
this.Unregister.InvokeAction(this._id);
|
|
this._id = null;
|
|
}
|
|
this.Invoke.Unsubscribe(this.OnInvoke);
|
|
}
|
|
|
|
private void OnInvoke(string id, PlayerPayload? sender, ulong contentId, Payload? payload, SeString? senderString, SeString? content) {
|
|
// Filter: only react to invocations that target our registration.
|
|
if (id != this._id) {
|
|
return;
|
|
}
|
|
|
|
// Draw your custom menu items here. Available context:
|
|
// sender — first PlayerPayload in the sender SeString, or null
|
|
// contentId — content ID of the message sender, or 0 when not known
|
|
// payload — the payload that was right-clicked, or null when text
|
|
// senderString — the full sender SeString
|
|
// content — the full message content SeString
|
|
if (ImGui.Selectable("Test plugin")) {
|
|
PluginLog.Log($"hi!\nsender: {sender}\ncontent id: {contentId:X}\npayload: {payload}\nsender string: {senderString}\ncontent string: {content}");
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Migration from Chat 2
|
|
|
|
If your plugin already integrates with `ChatTwo.*`, the rename is the only required change:
|
|
|
|
```diff
|
|
-this.Register = @interface.GetIpcSubscriber<string>("ChatTwo.Register");
|
|
-this.Unregister = @interface.GetIpcSubscriber<string, object?>("ChatTwo.Unregister");
|
|
-this.Invoke = @interface.GetIpcSubscriber<...>("ChatTwo.Invoke");
|
|
-this.Available = @interface.GetIpcSubscriber<object?>("ChatTwo.Available");
|
|
+this.Register = @interface.GetIpcSubscriber<string>("HellionChat.Register");
|
|
+this.Unregister = @interface.GetIpcSubscriber<string, object?>("HellionChat.Unregister");
|
|
+this.Invoke = @interface.GetIpcSubscriber<...>("HellionChat.Invoke");
|
|
+this.Available = @interface.GetIpcSubscriber<object?>("HellionChat.Available");
|
|
```
|
|
|
|
---
|
|
|
|
## Typing State IPC
|
|
|
|
Use this surface when you need to know whether the player is currently interacting with Hellion
|
|
Chat's input box. Useful for typing indicators, keyboard-shortcut suppression, or HUD elements that
|
|
hide while the user is typing.
|
|
|
|
### Tuple Payload
|
|
|
|
Both `HellionChat.GetChatInputState` (poll) and `HellionChat.ChatInputStateChanged` (event) return
|
|
the same tuple:
|
|
|
|
```cs
|
|
(bool InputVisible, bool InputFocused, bool HasText, bool IsTyping, int TextLength, ChatType ChannelType)
|
|
```
|
|
|
|
| Field | Type | Meaning |
|
|
| -------------- | ---------- | -------------------------------------------------------------------------- |
|
|
| `InputVisible` | `bool` | True when Hellion Chat is not hidden by user, cutscene or battle settings. |
|
|
| `InputFocused` | `bool` | True while the input box currently has keyboard focus. |
|
|
| `HasText` | `bool` | True when the input buffer contains more than whitespace. |
|
|
| `IsTyping` | `bool` | Convenience flag, equivalent to `InputFocused && HasText`. |
|
|
| `TextLength` | `int` | Length of the raw input buffer in characters. |
|
|
| `ChannelType` | `ChatType` | The channel/mode that will be used if the buffer is submitted right now. |
|
|
|
|
### Where `ChannelType` comes from
|
|
|
|
`ChannelType` is the `HellionChat.Code.ChatType` enum value representing the target channel for the
|
|
current submission. It is sourced from the active tab's `UsedChannel`
|
|
(`HellionChat/Configuration.cs`), which the plugin keeps in sync by hooking the in-game shell
|
|
(`HellionChat/GameFunctions/Chat.cs`) and by resolving temporary overrides in the input bar
|
|
(`HellionChat/Ui/Components/InputBar.cs`). `InputChannel` values are converted into the exported
|
|
`ChatType` via `HellionChat/Code/InputChannelExt.ToChatType`.
|
|
|
|
### Behavior
|
|
|
|
- `ChatInputStateChanged` fires once immediately after subscribe so you do not need a separate
|
|
`GetChatInputState` poll for the initial snapshot.
|
|
- After that it fires only when one or more fields actually change, so it is safe to subscribe
|
|
without rate-limiting.
|
|
- `GetChatInputState` is available for one-shot polls, e.g. on plugin enable.
|
|
|
|
### Example 2
|
|
|
|
```cs
|
|
public sealed class HellionChatTypingIntegration {
|
|
private ICallGateSubscriber<(bool InputVisible, bool InputFocused, bool HasText, bool IsTyping, int TextLength, ChatType ChannelType)> GetChatInputState { get; }
|
|
private ICallGateSubscriber<(bool InputVisible, bool InputFocused, bool HasText, bool IsTyping, int TextLength, ChatType ChannelType)> ChatInputStateChanged { get; }
|
|
|
|
public HellionChatTypingIntegration(DalamudPluginInterface @interface) {
|
|
this.GetChatInputState = @interface.GetIpcSubscriber<(bool, bool, bool, bool, int, ChatType)>("HellionChat.GetChatInputState");
|
|
this.ChatInputStateChanged = @interface.GetIpcSubscriber<(bool, bool, bool, bool, int, ChatType)>("HellionChat.ChatInputStateChanged");
|
|
}
|
|
|
|
public void Enable() {
|
|
this.ChatInputStateChanged.Subscribe(OnChatInputStateChanged);
|
|
|
|
// Optional: poll once for an initial snapshot. The Subscribe call
|
|
// above already fires once, so this is only useful when your code
|
|
// path needs the value before the framework hands you the event.
|
|
var state = this.GetChatInputState.InvokeFunc();
|
|
PluginLog.Information($"Initial typing state: {state}");
|
|
}
|
|
|
|
public void Disable() {
|
|
this.ChatInputStateChanged.Unsubscribe(OnChatInputStateChanged);
|
|
}
|
|
|
|
private void OnChatInputStateChanged((bool InputVisible, bool InputFocused, bool HasText, bool IsTyping, int TextLength, ChatType ChannelType) state) {
|
|
if (state.IsTyping) {
|
|
// Show typing indicator.
|
|
} else {
|
|
// Hide typing indicator.
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Migration
|
|
|
|
Same shape as the Context Menu surface — only the channel-name prefix needs the rename:
|
|
|
|
```diff
|
|
-this.GetChatInputState = @interface.GetIpcSubscriber<...>("ChatTwo.GetChatInputState");
|
|
-this.ChatInputStateChanged = @interface.GetIpcSubscriber<...>("ChatTwo.ChatInputStateChanged");
|
|
+this.GetChatInputState = @interface.GetIpcSubscriber<...>("HellionChat.GetChatInputState");
|
|
+this.ChatInputStateChanged = @interface.GetIpcSubscriber<...>("HellionChat.ChatInputStateChanged");
|
|
```
|
|
|
|
---
|
|
|
|
## License & Attribution
|
|
|
|
This guide and the IPC surface it documents derive directly from the Chat 2 codebase. Hellion Chat
|
|
is licensed under [EUPL-1.2](LICENSE), and credit for the original IPC design and implementation
|
|
goes to **[Infiziert90 (Infi)](https://github.com/Infiziert90)** and
|
|
**[Anna](https://github.com/anna-is-cute)**,— see [`NOTICE.md`](NOTICE.md) for full attribution.
|