PAR-20
Employee-only firm chat channel (CarbonChat integration, akin to /f chat)
Private business chat — a channel for a firm's employees (proprietor + current employees) to talk in-game, akin to Factions' /f chat. A player toggles into firm chat and their messages go only to fellow online firm members.
The server's chat plugin is CarbonChat (Hexaoxide/Carbon, docs carbonmod.gg). CarbonChat is channel-based with a first-class public API. Implement this as a single dynamically-scoped CarbonChat ChatChannel registered by business-rian — the same pattern Carbon's own Factions/Towny/Party integrations use. (Project: Business / business-rian — it owns the firm-employee model and adds the Carbon dependency.)
Design — ONE dynamic channel (not one per firm)
Carbon's own group-chat integrations (FactionChannel/ResidentListChannel, Towny TownChannel, PartyChatChannel) register one channel whose recipients(sender) resolves the sender's group at send time. Membership lives in the integrating plugin, not in Carbon's registry. We follow that exactly: one firmchat channel, firm membership stays in business-rian.
Do NOT register one channel per firm. Verified against Carbon source (trunk): the registry is a cheap HashMap<Key,ChatChannel>, BUT WrappedCarbonPlayer.channelForMessage(...) iterates every registered channel on every chat message (to check each channel's quickPrefix()). Tens of thousands of firm channels = tens of thousands of iterations per message per player. Plus, command-bearing channels register ~2 Brigadier commands each (commandName() + the /ch <name> proxy), bloating the command tree. A per-firm design is therefore a non-starter at firm scale.
Approach
Dependency — compileOnly("de.hexaoxi:carbonchat-api:<pin a 3.0.0-beta.x>") (Maven Central, GPL-3.0). Add Carbon as a Paper depend/softdepend in plugin.yml so we register after it loads. ⚠️ The 3.0.0-beta API line churns across betas — pin one version and verify signatures against that version's Javadoc.
On enable (after CarbonChatProvider.carbonChat() is available), build and register a single FirmChatChannel implements ChatChannel:
recipients(CarbonPlayer sender)→ resolve the sender's active firm (see multi-firm note), return its online members asAudiences. Primitive exists:StaffApi.getOnlineEmployees(firmId)/FirmStaffService.getOnlineEmployees(firm)returns online staff + proprietor.permissions()→ChannelPermissions.uniformDynamic(p -> isInAFirm(p) ? allowed() : denied(reason)), sodynamic()istrue(Carbon re-evaluates per-context instead of caching). Membership:StaffApi/FirmStaffService.isEmployedBy(firmId, uuid)or proprietor.radius()→-1(org-wide);quickPrefix()→ keep cheap;commandName()→ e.g.firm/fc(single command, fine), or setshouldRegisterCommands() → falseand drive everything through our own/firm chatcommand.- Register once via
carbonChat.channelRegistry().register(firmChannel).
Toggling in (the real /f chat behaviour) — resolve CarbonPlayer via carbonChat.userManager() and call carbonPlayer.selectedChannel(firmChannel); joinChannel/leaveChannel handle opt-out/opt-in. Routing by selected channel is O(1).
Multi-firm membership — "active firm" pointer (in business-rian)
Unlike Factions (one faction per player), a player can belong to multiple firms (FirmApi.getPlayerFirms(uuid) returns a list). Since there's one shared channel, business-rian holds a per-player active-firm pointer deciding which firm recipients(sender) routes to:
- Default to the player's sole firm (or most-recent) when they have one.
/firm chat <firm>(or a/firm setchannel <firm>) sets the active firm for multi-firm players.- Persist per-player (in-memory map is enough to start; persist if we want it to survive relog).
This keeps membership/identity in our plugin — exactly how Factions keeps faction state out of Carbon — without inflating Carbon's per-message channel loop.
Fallback / interim
If we want something before the full Carbon integration: a ChatCommands @Route("chat <firm> <message>") (greedy arg) resolving recipients via getOnlineEmployees filtered to the sender's membership, broadcasting with a business.chat.format key, registered in CommanderModule. One-shot only (no toggle), bypasses Carbon's formatting/channel UX — a stopgap, with the single Carbon channel as the real deliverable.
Related
- Reuses the same recipient primitive as PAR-94 (firm notification service) and PAR-56 (incoming-transfer notifications):
getOnlineEmployees.
Resources
- PR#2 Last major release ParadauxIO/hibernia-economy
- commit2aa6bc4 Move firm-chat social spy to /firm admin chatspy [firm] (PAR-20)ParadauxIO/hibernia-economy
- commit9014bc8 Add firm-chat social spy for chat moderators (PAR-20)ParadauxIO/hibernia-economy
- commit9c9abdc Fix firm chat dropping the message body (PAR-20)ParadauxIO/hibernia-economy
- commita2d99ae Harden new sales/chat surfaces (post-port audit fixes)ParadauxIO/hibernia-economy
- commit3a20272 Employee-only /firm chat via CarbonChat (PAR-20)ParadauxIO/hibernia-economy
Comments
tesks · Jun 4, 2026, 9:57 AM
Code context — No chat feature today, but the recipient primitive exists: FirmStaffService.getOnlineEmployees(firm) returns online staff + proprietor, and Message.send(Collection<Player>, …) is already used elsewhere.
Approach: add a ChatCommands @Route("chat <firm> <message>") (greedy arg), resolve recipients via getOnlineEmployees filtered to the sender's membership, broadcast with a business.chat.format message key, register in CommanderModule. A toggle-channel mode (like /f chat) is a later enhancement on top of the one-shot form.
Activity
- ParadauxIO linked a pull request — PR #2 open — Last major release
- ParadauxIO linked a pull request — PR #2 open — Last major release
- ParadauxIO linked a pull request — PR #2 open — Last major release
- ParadauxIO linked a pull request — PR #2 open — Last major release
- ParadauxIO linked a pull request — PR #2 open — Last major release
- ParadauxIO linked a pull request — PR #2 open — Last major release
- ParadauxIO linked a pull request — PR #2 open — Last major release
- tesks changed status to Status → Pending Release
- ParadauxIO linked a commit — Commit 2aa6bc4 — Move firm-chat social spy to /firm admin chatspy [firm] (PAR-20)
- ParadauxIO linked a commit — Commit 9014bc8 — Add firm-chat social spy for chat moderators (PAR-20)
- ParadauxIO linked a commit — Commit 9c9abdc — Fix firm chat dropping the message body (PAR-20)
- ParadauxIO linked a commit — Commit a2d99ae — Harden new sales/chat surfaces (post-port audit fixes)
- ParadauxIO linked a commit — Commit 3a20272 — Employee-only /firm chat via CarbonChat (PAR-20)
- paradaux changed status to Status → Planned
- paradaux description: Description updated
- tesks description: Description updated
- tesks renamed the issue
- tesks description: Description updated
- tesks description: Description updated
- tesks commented
- tesks assigned Assigned to rian