0220e5d756
Pull in the refreshed linter and tooling configs (editorconfig, gitignore, gitattributes, prettierignore, prettierrc, markdownlint, yamllint, env.example, dotnet-tools) and run prettier and markdownlint in --fix / --write mode across the repo so the existing tree matches the new rules. - prettier 2-space indent on yaml/yml and json overrides, asterisk strong, underscore emphasis, proseWrap always - markdownlint MD007 indent aligned to 2 and MD049 to underscore so prettier output stays passing - preflight Block F also ignores CLAUDE.md (gitignored personal file) - prettierignore extended to keep HellionChat.yaml manifest and the NuGet packages.lock.json out of the formatter No semantic content changed; csharpier, build, full build-suite (729/729) and the new prettier/markdownlint/yamllint checks all green.
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 inside the chat UI
|
|
(`HellionChat/Ui/ChatLogWindow.cs:597`). `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.
|