Carbon viewer-context bug: <luckperms_prefix> resolves against viewer not sender. Researched two open-source alternatives. Both fix the bug. ChatChat (HelpChat fork) renders per-recipient with PlaceholderAPI.setPlaceholders(sender, ...) + Kyorifier converts legacy & codes to MM. VentureChat resolves PAPI once vs sender, splits packets via ProtocolLib. Concerns: - ChatChat: 0 GitHub releases, last commit 2025-04-02, apiVersion 1.21.4, uses deprecated AsyncPlayerChatEvent - VentureChat: built for 1.21.8, open issues #154/#156/#157 report 1.21.10+ breakage with no maintainer response 4+ months Both verdicts: cautious recommend. Operator decision pending player input + migration plan synthesis.
7.9 KiB
ChatChat Migration Research — racked.ru
Date: 2026-05-07
Target: Replace CarbonChat 3.0.0-beta.36 (viewer-context bug on <luckperms_prefix>)
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 ™️" |
| 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
<papi:luckperms_prefix>MM tag also exists, also bound to sender viacreatePlaceholderAPITag(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
MiniPlaceholderManagerresolves config-defined MM tags with explicitMiniPlaceholderContext.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 <luckperms_prefix> MM tag) is not auto-bridged. Two practical fixes:
- Use
%luckperms_prefix%PAPI placeholder in the format (Kyorifier converts&codes) — works out of the box. - Or use
<papi:luckperms_prefix>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 |
/<channel> 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 |
<itemlink> / <item> in chat |
<itemlink> |
<item> (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 foruse. Pluschatchat.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 onedefaultformat 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
<itemlink>in messages must become<item>. - External MiniPlaceholders ignored — operator's
<luckperms_prefix>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 (<gray><message>), 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.21.11 runtime compatibility. plugin.yml says
apiVersion: 1.21.4. Need to test the latest CI jar (or build commit5b9...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. - DiscordSRV 1.25.1 vs whatever you currently run. ChatChat compiles against DSRV 1.25.1; the
ChatHookinterface or the shaded Adventure-Gson serializer dance can break across DSRV versions. Test in/out routing on a staging server. - MiniPlaceholders LP-Expansion: do you actually need the MM tag form? If any other plugin (MOTD, TAB) consumes
<luckperms_prefix>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 loopplugin/src/main/java/at/helpch/chatchat/util/FormatUtils.java— sender-context PAPI callplugin/src/main/java/at/helpch/chatchat/util/PapiTagUtils.java—<papi:...>MM tag bridgeplugin/src/main/java/at/helpch/chatchat/util/Kyorifier.java— legacy&/hex → MM converterplugin/src/main/java/at/helpch/chatchat/listener/ChatListener.java— usesAsyncPlayerChatEventplugin/src/main/java/at/helpch/chatchat/util/ChannelUtils.java—chatchat.channel.{see,use}.<name>permsplugin/src/main/java/at/helpch/chatchat/hooks/dsrv/{ChatChatDsrvHook,DsrvListener}.javaplugin/src/main/resources/{channels,formats,extensions,settings}.ymlplugin/build.gradle.kts— apiVersion 1.21.4, soft-deps Towny/DSRV/SuperVanish/GP, loadBefore Essentialsgradle/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.