Paradaux

PAR-20

0

Employee-only firm chat channel (CarbonChat integration, akin to /f chat)

Pending ReleaserianBusinessFeature

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

DependencycompileOnly("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 as Audiences. Primitive exists: StaffApi.getOnlineEmployees(firmId) / FirmStaffService.getOnlineEmployees(firm) returns online staff + proprietor.
  • permissions()ChannelPermissions.uniformDynamic(p -> isInAFirm(p) ? allowed() : denied(reason)), so dynamic() is true (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 set shouldRegisterCommands() → false and drive everything through our own /firm chat command.
  • 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

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