MCME-Introduction is the new-player introduction on the hub. A player who joins for the first time passes a welcome room and, if their client needs attention (an unsupported Minecraft version, Forge or OptiFine, or Fabric without MCME's installer), a compatibility room that explains what to do. After the rooms come a welcome title and a series of tips shown as advancement toasts. While a player is in a room, chat from the rest of the network is hidden so the screens can be read. One jar is both the Paper backend plugin and the Velocity proxy plugin. This page describes 1.4.1, the latest release, from MCME/MCME-Introduction.
At a glance
| Repository | MCME/MCME-Introduction (public, GPL-3.0) |
| Release branch | master (work goes into development through pull requests) |
| Version described | 1.4.1 (tag v1.4.1) |
| Platform | Paper backend plugin (api-version: '26.2') and Velocity proxy plugin, id mcme-introduction (velocity-api 4.2.1-SNAPSHOT plus Velocity proxy internals) |
| Build | Maven, JDK 25 (--release 25) |
| Depends on | Backend: MCME-Architect, ViaVersion, ProtocolLib (hard, depend); MCME-Connect (needed, but not declared). Proxy: VPacketEvents (hard). Optional: MySQL/MariaDB |
How the code is organised
src/main/java/com/mcmiddleearth/introduction/
├── paper/
│ ├── IntroductionPlugin backend entry point: registers the mcme:intro channel, the ProtocolLib
│ │ chat listener and the /confirm and /intro commands; loads the rooms
│ ├── IntroductionChain the tips after the rooms: title, start message, broadcast, toasts
│ ├── command/ ConfirmCommand, IntroCommand (Paper Brigadier commands)
│ ├── confirmData/ ConfirmDataManager and DatabaseConnector: players' /confirm choices
│ ├── flow/ FlowEvents and FlowLog: the JSON-lines flow log
│ ├── listener/ ChatPacketListener (hides system chat), ConfirmPreLoginListener,
│ │ GlowListener (glowing item frames), RoomListener (enter, leave, join)
│ └── rooms/ Room (shared behaviour), FirstRoom (welcome), SecondRoom (compatibility)
└── velocity/
└── IntroductionPlugin an empty placeholder; see below
src/main/resources/templates/IntroductionPlugin.java the real Velocity plugin class
Things to know before you change code:
- The Velocity main class is generated. Maven filters
src/main/resources/templates/IntroductionPlugin.javawith the pom'splugin.*properties (id, name, version, author) and compiles the result. The file of the same name undersrc/main/java/.../velocity/is an empty placeholder: edit the template.velocity-plugin.jsonis filtered the same way. The version is set once, inpom.xml. - A room is a box in the world. A player who enters it is placed at the room's
playerLocation, switched to spectator mode and shown a full-screen picture through an item on their head. Stepping left leaves the room. While a player is in a room, the plugin keeps their own game mode and helmet in the player's data (mcme:intro_gamemode,mcme:intro_head), so both come back even after a crash. - Who gets the compatibility room is decided in
SecondRoomfrom the client version (ViaVersion, againstsupportedVersions), the client brand (Forge family, Fabric) and MCME's marker mod (reported by MCME-Architect). - Advancements. The room toasts and the tips are advancements registered at start-up through Paper's
UnsafeValues.loadAdvancement(keys fromadvancementKeyandmcme:introchain<n>). Paper saves them in its built-inbukkitdatapack; the plugin deletes them at a clean shutdown. - Plugin messages carry the insertion
mcme:introon their root component. That is how the chat filter lets the plugin's own messages through.
Building and testing
mvn package
This runs the tests and produces target/MCME-Introduction-<version>-<type>-<commit>.jar. <type> is RELEASE unless you pass -Dbuild.type=DEV.
- The Velocity side compiles against Velocity proxy internals (for its packet listener), which no Maven repository publishes. Put a Velocity 4.2.1 proxy jar at
libs/velocity-proxy.jar, or pass-Dvelocity.proxy.path=/path/to/velocity.jar. - Dependencies resolve from the MCME Maven repository: MCME-Architect 3.1.0 and MCME-Connect 3.0.3 from
releases, the rest through itsmirrorand the upstreams in the pom. - In a linked git worktree the commit id in the jar name comes from the main checkout (a git-commit-id-maven-plugin bug). Pass
-Dgit.commit.id.abbrev=$(git rev-parse --short=7 HEAD)or build from a plain clone. - Tests:
mvn testruns a MockBukkit 26.2 suite for the Paper side (JUnit Jupiter and Mockito).PluginTestBasestarts a mock server with the plugin loaded;RoomTestBasebuilds two rooms and gives every player a 26.2 client without the marker mod. Tests swap the static seamsRoom.rpPlayerData(Architect's resource-pack status),SecondRoom.clientVersion(ViaVersion) andSecondRoom.mcmeMarker(Architect's marker check), because those plugins can't load in MockBukkit. The Velocity side and the ProtocolLib packet read are not covered. MockBukkit marks a test that reaches an unimplemented method as skipped, so check the skipped count.
There is no CI workflow. A release is a pull request from development into master, a vX.Y.Z tag on the merge commit, and a jar built from a plain clone of the tag, attached with its SHA-256 to a GitHub release. The plugin is not published to the Maven repository.
Configuration
Both files live in plugins/MCME-Introduction/ on each backend. The proxy side has no configuration.
config.yml: room behaviour and texts, the tips, the supported versions, the database. Written at first start and never overwritten: a key a newer version adds takes the jar's value or a built-in one until you add it by hand.locations.yml: the rooms' boxes, where players are placed and sent, the welcome text display, and the glowing-item area. The plugin never creates it; a backend without it has no rooms and only shows the reminders.
Texts are JSON text components in single quotes. Durations are in ticks, except the tip title's times (seconds). /intro reload re-reads both files; database settings and advancement texts need a restart.
| Key | Shipped value | Meaning |
|---|---|---|
supportedVersions |
['26.2', '26.3'] |
Client versions that skip the compatibility room. A version covers every release with the same protocol |
joinDelay |
11 |
Ticks after a join before a room, the tips or the reminders start |
roomEnterMoveDelay |
5 |
Ticks after entering a room before stepping left counts (a room can override it) |
reminderPeriod / reminderDuration |
600 / 100 |
How often the compatibility warning comes back for players who haven't confirmed, and how long it stays |
flowLog.enabled / flowLog.keepDays |
true / 90 |
The flow log in flow/YYYY-MM-DD.jsonl, and how many days to keep (0: all) |
rpMessages.messageRpWarning, messageRpDeclined, messageRpFailedDownload, messageRpFailedReload, messageRpInvalidUrl, messageRpTimeout |
English texts | Resource-pack failure messages; a room can override each |
firstRoom.*, secondRoom.* |
see the reference | Fade (tpTransitionTimes, tpTransitionComponent), action bar, toast (advancementKey, advancementDisplay), unsilenceDelay, the screens (cameraOverlay, the four overlayWarning* keys) and the room texts. <supported_versions> in a compatibility message shows the list |
introductionChain.* |
see the reference | The welcome title, startMessage, broadcastMessage (must contain _@p_ for the player's name), startBroadcastDelay, initialDelay (600), period (600) and advancementDisplays (one toast per tip) |
database.enabled |
false |
Store /confirm choices in MySQL/MariaDB (table confirm_data). Without it each server keeps them in memory until it restarts |
database.host / port / database / user / password |
localhost / 3306 / placeholders |
The database login |
locations.yml holds firstRoom and secondRoom (world, pos1, pos2, playerLocation, tpTarget, and for the welcome room an optional displayEntity) and itemGlow (world, pos1, pos2, items, glowColor, rayRange, rayWidth, rayRepetitions, zoom).
docs/configuration.md describes every key, its fallback value and the placeholders.
Data files the plugin writes in its folder: finishedPlayerList.uid (players who finished once, one UUID per line), EnabledGlowItems.dat (players with glowing items on) and the flow log.
Commands and permissions
All commands are on the backend, registered through Paper's Brigadier command API.
| Command | What it does | Who may use it |
|---|---|---|
/intro reload |
Reload config.yml and locations.yml and rebuild the rooms a second later; reports start, end or failure |
introduction.reload |
/intro glowitem <player> on\|off |
Turn the glowing item frames on or off for a player | the console or another non-player sender only |
/confirm optifine |
Leave the compatibility room and stop its reminders for good | players with a Forge-family client on a supported version |
/confirm ignore |
Same, for every other client | players /confirm optifine is not for |
/confirm reset |
Forget the player's confirmations | players only |
| Permission | Default | Grants |
| ------------ | --------- | -------- |
introduction.reload |
op | /intro reload |
The /confirm subcommands are gated by the player's client, not by permissions.
APIs and extension points
MCME-Introduction offers no API to other plugins. Two outputs are meant for other tools:
- The flow log: one JSON object per line for each step a player takes (entering a room with client details, the resource pack's result, leaving and how, finishing, reminders,
/confirm), one file per UTC day, players by UUID only. Readers should skip a last line that does not parse and ignore fields they don't know. docs/flow-log.md describes the format. - The
mcme:introchannel (below), which only its own proxy half reads.
How it talks to other plugins and systems
| Integrates with | Direction | How |
|---|---|---|
| Its own Velocity half | backend → proxy | Plugin messages on mcme:intro: a UTF sub-channel enter or exit, then the player's UUID as a UTF string. The backend sends enter when it puts a player in a room and exit when chat comes back |
| MCME-Architect | reads | RpManager.getPlayerData(player) for the resource-pack status (a room shows its screen and text once the pack has loaded) and RpManager.isSodiumClient(player) for MCME's marker mod. Architect is the plugin that sends the pack |
| MCME-Connect | reads and writes | Listens to PlayerConnectEvent to start a joining player's room, reminders or tips; sends the first-time "just joined" broadcast to the whole network with ConnectUtil.sendMessage(player, "", "", text, 0) |
| ViaVersion | reads | Via.getAPI().getPlayerProtocolVersion(player) for the client version |
| ProtocolLib | intercepts | A packet listener drops system chat for players in a room, except messages whose root component carries the insertion mcme:intro |
| VPacketEvents (proxy) | intercepts | The proxy half drops SystemChatPackets for players a backend reported with enter |
| MySQL/MariaDB | reads and writes | Only with database.enabled: true, through the MySQL driver that comes with Paper. Choices are read before the player joins (ConfirmPreLoginListener) |
Paper's bukkit datapack |
writes | The room and tip advancements |
sequenceDiagram
participant Px as Velocity proxy (MCME-Introduction)
participant C as MCME-Connect (backend)
participant I as MCME-Introduction (backend)
participant A as MCME-Architect
C->>I: PlayerConnectEvent (player arrived)
I->>I: player stands in a room?
I->>Px: mcme:intro "enter" + UUID
Px->>Px: drop system chat for this player
I->>A: RpManager.getPlayerData (pack loaded?)
I->>I: show the room's screen and text
Note over I: player steps left, rooms end
I->>Px: mcme:intro "exit" + UUID
I->>C: ConnectUtil.sendMessage (first-time broadcast)
The proxy only accepts an enter or exit for the player whose connection carries the message, and it drops a player's silence when they leave the network or arrive on another server, because a backend's exit may never arrive.
System chat is hidden; player chat (/say, /me, signed chat) is not, because since Minecraft 1.21.5 the client disconnects when a numbered player chat message is missing.
Further reading
- README, configuration reference, flow log format and CHANGELOG on GitHub
- Joining the server, which covers a player's first visit
- Setting up, Maven repository