Skip to content

Architecture

Florianpal1 edited this page Aug 6, 2026 · 1 revision

Architecture

For contributors and anyone debugging the relay.


Modules

FMessage is a Maven multi-module project (fr.florianpal:FMessage:2.4.5).

FMessage/
├── FMessageCommon/     # shared: database, queries, configs, domain objects
├── FMessageBukkit/     # backend server plugin  (Paper API)
├── FMessageBungee/     # proxy plugin           (BungeeCord API)
└── FMessageVelocity/   # proxy plugin           (Velocity API)
Module Depends on Notes
FMessageCommon HikariCP, MariaDB driver, BoostedYAML No Minecraft API at all
FMessageBukkit paper-api, PlaceholderAPI, ACF-Paper Does not depend on FMessageCommon — no database access
FMessageBungee bungeecord-api, ACF-Bungee, bStats, FMessageCommon
FMessageVelocity velocity-api, ACF-Velocity, bStats, FMessageCommon

FMessageBungee and FMessageVelocity are near-identical ports of the same logic against two different proxy APIs: a change to commands or message handling usually has to be made in both.

What lives in FMessageCommon

  • managers/DatabaseManager — HikariCP pool, table bootstrap
  • managers/ConfigurationManager — loads config.yml and database.yml via BoostedYAML
  • configurations/ChatConfig, configurations/DatabaseConfig
  • queries/GroupeQueries, GroupeMemberQueries, IgnoreQueries, NickNameQueries; each implements IDatabaseTable, which exposes the CREATE TABLE definition used at startup
  • managers/commandManagers/ — the caching layer above the queries
  • objects/Group, objects/Member

Plugin-message channels

Chat travels over two Minecraft plugin-message channels:

Channel Direction Payload
fmessage:chatbungee backend → proxy a chat message or a staff message written by a player
fmessage:chatbukkit proxy → all backend servers the message to display

Both are registered by the proxy module and by the Bukkit module at startup.

fmessage:chatbungee (backend → proxy)

# Field Type
1 subchannel — Message or StaffMessage UTF
2 author UUID UTF
3 author display name UTF
4 format, PlaceholderAPI already resolved UTF
5 message body UTF
6 author has fmessage.colors boolean (Message only)
7 author has fmessage.nick.colors boolean (Message only)

fmessage:chatbukkit (proxy → backend)

# Field Type
1 subchannel UTF
2 author UUID UTF
3 author display name UTF
4 author nickname, empty when none UTF
5 format UTF
6 message body UTF
7 ;-separated UUIDs of players ignoring the author UTF (Message only)
8 colors flag boolean (Message only)
9 nick colors flag boolean (Message only)

Message flow

Public chat

  1. ChatListener (Bukkit) catches AsyncPlayerChatEvent at HIGH priority.
  2. Anti-flood, then anti-caps, are applied (unless bypassed).
  3. The event is always cancelled — vanilla broadcast never happens.
  4. chatFormat.general is resolved through PlaceholderAPI for the author, then everything is written to fmessage:chatbungee.
  5. The proxy's MessageListener reads it and:
    • if the author has an active group toggle, the message is delivered to that group's online members and nothing else happens;
    • if the subchannel is StaffMessage, it is relayed to every server;
    • otherwise the author's nickname and the list of players ignoring them are attached, and the message is broadcast to every registered server over fmessage:chatbukkit.
  6. Each backend server substitutes {displayName} and {message} and sends the component to every online player — except those on the ignore list, who receive ignoreFormat instead.

This is why the Bukkit module cannot run alone: step 3 cancels chat unconditionally, and only the proxy can put it back.

Private messages

/msg, /m and /r are handled entirely on the proxy by MessageManager — no plugin message is involved. It checks the ignore list in both directions, renders targetChatFormat, senderChatFormat and spyChatFormat, records the /r pairing in memory, logs the spy line to the proxy console and pushes it to every player in spy mode.


Runtime state

Held in memory on the proxy, lost on restart:

  • playerMessage — the /r pairing, written both ways on each private message
  • playerSpy — who has /chatspy enabled
  • groups, ignores — caches refreshed after each mutating command

Technical notes

  • Formatting: Adventure's LegacyComponentSerializer.legacyAmpersand(). Legacy &-codes only — no hex, no MiniMessage.
  • Commands: ACF (co.aikar.commands), relocated to fr.florianpal.fmessage.acf by the shade plugin. unstableAPI("help") is enabled.
  • Config: BoostedYAML with BasicVersioning("version") on the proxy, so user files are auto-upgraded. The Bukkit module uses plain Bukkit configuration.
  • Metrics: bStats, plugin id 24047, on both proxy modules.
  • Shading: MariaDB driver, ACF, ACF locales and bStats are relocated under fr.florianpal.fmessage.* to avoid clashing with other plugins.

Clone this wiki locally