# ChatChat Migration Research — racked.ru **Date:** 2026-05-07 **Target:** Replace CarbonChat 3.0.0-beta.36 (viewer-context bug on ``) **Source:** `HelpChat/ChatChat` — repo URL `github.com/HelpChat/ChatChat` (the `HibiscusMC/ChatChat` URL 404s; HelpChat is the actual maintainer org — DeluxeChat / extendedclip family). --- ## 1. Project Health — RISKY | Metric | Value | |---|---| | Last commit (main) | 2025-04-02 (PR #230 merge) — ~13 months ago | | Substantive 1.21.4 work | 2025-02-26 (one-shot bump) | | Releases on GitHub | **0** (CI artifacts only) | | Jenkins last build | #79, ~13 months ago, marked stable; CI appears idle | | Open issues / PRs | 23 open non-PR issues (some open since 2022) | | License | MIT | | Stars / forks | 51 / 31 | | README | Literally says "Coming Soon :tm:" | | API target | `apiVersion: 1.21.4` in plugin.yml; Spigot 1.21.4-R0.1-SNAPSHOT | | Adventure / MiniMessage | 4.16.0 (somewhat behind 4.20.x current) | Verdict: low velocity, no formal release, niche maintainers (M0diis, BlitzOffline, bridgelol). Functional but not a "thriving" project — expect to fork or pin a build. ## 2. Architecture — Would It Fix Our Bug? **YES (with caveats)** `MessageProcessor.process()` iterates every recipient and re-renders the format **per-recipient** via `FormatUtils.parseFormat(format, sender, recipient, message, miniPlaceholders)`. Inside that call: - `PlaceholderAPI.setPlaceholders(player=sender, formatString)` — `%luckperms_prefix%` always resolves against the **sender**. This is the exact opposite of Carbon's bug. - `PlaceholderAPI.setRelationalPlaceholders(sender, recipient, ...)` — `%rel_*%` get both contexts. - A `` MM tag also exists, also bound to sender via `createPlaceholderAPITag(player=sender)`. - `Kyorifier.kyorify()` then converts any `&` legacy codes returned by PAPI (e.g. `&8[&4⛧&8]`) into MiniMessage tags **before** parsing. The legacy-code workaround Carbon swallows works natively here. - A separate internal `MiniPlaceholderManager` resolves config-defined MM tags with explicit `MiniPlaceholderContext.builder().sender(user).recipient(target)` — sender-context guaranteed. Caveat: ChatChat's bundled `MiniPlaceholderManager` is its own config-yaml system. The external **kyori MiniPlaceholders** library + LP-Expansion (which provides the bare `` MM tag) is **not auto-bridged**. Two practical fixes: 1. Use `%luckperms_prefix%` PAPI placeholder in the format (Kyorifier converts `&` codes) — works out of the box. 2. Or use `` MM tag — same effect. Either way the viewer-context bug disappears. ## 3. Feature Gap vs Carbon (only what matters here) | Feature | Carbon 3.0 | ChatChat | Notes | |---|---|---|---| | Channels (multi, per-channel format) | yes | yes | `channels.yml` + per-channel formats list | | `/` toggle / message-prefix | yes | yes | `toggle-command`, `message-prefix` | | Mute / ignore | yes | yes | `/ignore`, `/ignorelist`, GP soft-mute hook | | Mentions (with sound) | yes | yes | personal + everyone, perm-gated, sound configurable | | Profanity / rules | yes | basic (RuleManager + unicode rule) | likely needs simple regex rules | | DiscordSRV bridge | yes | yes (channels-bridging on, name-matched) | uses DSRV `ChatHook` interface | | PlaceholderAPI + Vault | yes | yes (PAPI hard-dep) | Vault implicit via `%vault_group%` examples | | MiniPlaceholders (external lib) | yes (native) | **no auto-bridge** — use PAPI tag instead | minor format rewrite | | `` / `` in chat | `` | `` (perm `chatchat.tag.item`) | tag name differs | | Hover/click on names/items | yes | yes | full MM standard tags, perm-gated | | Chat preview (1.19+) | partial | no (uses deprecated `AsyncPlayerChatEvent`) | preview gone in 1.19.3+ anyway | | Cross-server / Velocity | yes | no (single-server only) | matches our setup | | Async chat | yes | rides Bukkit async event | open issue #91 wants deeper async | | Database backend | H2/MySQL/JSON | **JSON only** (`GsonDatabase`) | fine for our scale | ## 4. Migration Cost — **MEDIUM** What breaks / needs work: - **Permission rename:** `carbon.channel.global.see` → `chatchat.channel.see.global`; same for `use`. Plus `chatchat.tag.*` per-tag perms (color/hover/click etc) need granting in LP (Carbon doesn't split these). - **Format syntax rewrite:** Carbon uses a single MM string per format. ChatChat splits formats into named `parts:` (channel/prefix/name/divider/message). Operator's target string maps cleanly onto one `default` format with one part. - **DiscordSRV config:** ChatChat-channel name MUST match DSRV-config channel name (not Discord channel name). Re-check `DiscordSRV/config.yml > Channels:` keys. - **User data:** Carbon JSON/H2/MySQL nicknames don't carry over; ChatChat starts a fresh `users.json`. EssentialsX nicknames are unaffected (different store). - **Item-tag rename:** any `` in messages must become ``. - **External MiniPlaceholders ignored** — operator's `` MM tag becomes `%luckperms_prefix%` (PAPI form) inside the format. - **partychat removal:** trivial — just don't define it in `channels.yml`. What's preserved automatically: LP prefixes-as-`&`-codes (Kyorifier handles them), EssentialsX nickname display via `%player_displayname%`, Discord routing intent, gray-message rendering (``), MiniMOTD/TAB/ProAntiTab/ProtocolLib (no overlap). ## 5. Verdict — **CAUTIOUS RECOMMEND** ChatChat will fix the viewer-context bug because its renderer is per-recipient with explicit sender-context PAPI resolution and a built-in legacy-code Kyorifier — but it's a sleepy single-Jenkins-build project pinned to 1.21.4 with no formal releases, so adopt only if you're comfortable building from source and pinning a known-good commit. --- ## Top 3 unknowns to verify before pulling the trigger 1. **1.21.11 runtime compatibility.** plugin.yml says `apiVersion: 1.21.4`. Need to test the latest CI jar (or build commit `5b9...` on a Purpur 1.21.11 staging instance) and confirm no NMS/AsyncPlayerChatEvent regressions on Purpur. Paper still fires the deprecated event so likely OK, but verify on an actual 1.21.11 Purpur jar before swapping in prod. 2. **DiscordSRV 1.25.1 vs whatever you currently run.** ChatChat compiles against DSRV 1.25.1; the `ChatHook` interface or the shaded Adventure-Gson serializer dance can break across DSRV versions. Test in/out routing on a staging server. 3. **MiniPlaceholders LP-Expansion: do you actually need the MM tag form?** If any other plugin (MOTD, TAB) consumes `` as a MiniMessage tag from racked formats, it must keep working. Confirm switching to `%luckperms_prefix%` (PAPI) doesn't break TAB/MiniMOTD elsewhere — those may be unaffected since they have their own resolvers. ## Source artefacts inspected (for re-audit) - `plugin/src/main/java/at/helpch/chatchat/util/MessageProcessor.java` — per-recipient render loop - `plugin/src/main/java/at/helpch/chatchat/util/FormatUtils.java` — sender-context PAPI call - `plugin/src/main/java/at/helpch/chatchat/util/PapiTagUtils.java` — `` MM tag bridge - `plugin/src/main/java/at/helpch/chatchat/util/Kyorifier.java` — legacy `&`/hex → MM converter - `plugin/src/main/java/at/helpch/chatchat/listener/ChatListener.java` — uses `AsyncPlayerChatEvent` - `plugin/src/main/java/at/helpch/chatchat/util/ChannelUtils.java` — `chatchat.channel.{see,use}.` perms - `plugin/src/main/java/at/helpch/chatchat/hooks/dsrv/{ChatChatDsrvHook,DsrvListener}.java` - `plugin/src/main/resources/{channels,formats,extensions,settings}.yml` - `plugin/build.gradle.kts` — apiVersion 1.21.4, soft-deps Towny/DSRV/SuperVanish/GP, loadBefore Essentials - `gradle/libs.versions.toml` — Spigot 1.21.4, MM 4.16.0, DSRV 1.25.1, EssX 2.19.4, PAPI 2.11.6 Last upstream commit reviewed: `Merge pull request #230 from M0diis/resolve-228` @ 2025-04-02T17:18:51Z.