MCME Wiki

CommonerVote turns guests into Commoners. Team members vote for guests whose building they have seen; each vote is
worth a share of a promotion, and when a guest reaches 100 % the plugin promotes them through LuckPerms, welcomes them
and announces it to the network. Votes last a limited time, and a staff vote can count for more than a team vote.
Everything players see is a Paper dialog, the action bar or chat. This page describes 2.0.0, the latest release
on master (tag v2.0.0), a rewrite for Paper 26.2 and Java 25. The source is public at
MCME/CommonerVote.

At a glance

Repository MCME/CommonerVote
Release branch master (work goes to development first)
Version described 2.0.0 (tag v2.0.0, 2026-10-21)
Platform Paper, api-version: '26.2'; one plugin per backend server
Build Maven, JDK 25 (release 25), nothing shaded
Depends on LuckPerms (hard, depend); a MySQL or MariaDB database (required, the driver comes with Paper); a Velocity proxy with the BungeeCord channel enabled (optional, for cross-server updates)

How the code is organised

Everything lives under com.mcmiddleearth.commonervote. The services talk to small interfaces, so the tests can swap
the outside world (database, LuckPerms, dialogs, proxy) for fakes.

Package Job
(root) CommonerVotePlugin builds every service once on enable and stops the database thread on disable. Permissions holds the node names, spelled as in plugin.yml.
command VoteCommand builds the Brigadier tree for /vote. A subcommand the sender may not use is cut from the tree through its requires, so the client never sees it.
domain Plain values and rules: Settings (the shared settings, their defaults and ranges), VoteMath (scores and rescaling), PromotionGuard (who may be promoted automatically), Vote, IntRange, and the form objects behind the settings dialogs.
service The flows: VoteService (cast, renew, withdraw, progress), ReviewService (candidates, clear), PromotionService (automatic and manual promotions, the welcome), SettingsService and SettingsCache (shared settings), XpBarService (the progress shown on the XP bar), plus helpers for names, messages and console commands.
storage JDBC over java.sql only. DbExecutor runs every piece of database work on one dedicated thread, with a fresh connection each time. VoteStore, SettingsStore and PromotionStore own one table each; Schema creates the tables.
rank RankService (interface) and LuckPermsRankService: name lookups, last known names, the exempt check for offline players, and a player's direct groups. Every answer is a future completed on LuckPerms' threads.
network Messenger (interface), BungeeMessenger and BungeeCodec: the proxy plugin messages (see How it talks to other plugins).
ui DialogPresenter (interface) and PaperDialogPresenter; ui/spec describes each dialog, ui/view holds the nine views (welcome, guest info, candidate list, vote, confirm, review list and detail, settings, texts).
listener PlayerListener: join (promotion check, pending welcome), quit, and XP changes (the XP bar is a progress meter for guests only).

Two things to know before you change code:

Building and testing

You need JDK 25 and Maven.

mvn -B verify

This compiles, runs the tests and writes the jar to target/. The tests use JUnit, Mockito and MockBukkit
(mockbukkit-v26.2), with H2 in MariaDB mode standing in for the database. TestCommonerVotePlugin overrides the
protected create… methods of the main class to plug in the in-memory database, inline executors and fakes.
LegacyCompatibilityTest keeps the votes table readable by the old 1.2.9 plugin, and PluginDescriptorTest checks
plugin.yml.

The jar name follows MCME's convention, CommonerVote-<version>-<DEV|SNAPSHOT|RELEASE>-<commit id>.jar:

GitHub Actions (.github/workflows/build.yml) runs mvn -B verify on pushes to master, development and
revision/**, and on pull requests. It does not publish anything. Releases are on the repository's
Releases page, as the RELEASE jar plus its .sha256.

Configuration

plugins/CommonerVote/config.yml holds only the database connection and the proxy switch. Everything else is a
shared setting in the database.

Key Shipped value Meaning
database.ip localhost Database host. Keep it on localhost or a private network.
database.port 3306 Database port.
database.dbName development Database name. It must exist; the plugin creates its tables.
database.user development Database user. Needs SELECT, INSERT, UPDATE, DELETE and CREATE.
database.password development Database password (a placeholder: replace it).
useBungee true Send cross-server updates and announcements through the proxy. Set false on a single server.

The keys of 1.2.9 that moved into the database (validityPeriod, votesNeeded, useXpBar and others) are ignored;
the start-up log names any your file still has.

Shared settings (database)

Edited in game with /vote config and stored in the commonervote_settings table. Another server picks up a change
within about a minute.

Setting Default Range / notes
votesNeeded 5 1–20. Changing it rescales every stored vote.
validityDays 100 1–365. Older votes stop counting but stay in the table.
staffWeight 2 1–5 votes per staff vote.
teamWeight 1 1–5 votes per other vote.
autoPromote true Promote at 100 % without an admin.
xpBar true Show guests' progress on the XP bar.
broadcastNetwork true Announce promotions to the whole network.
commonerGroup commoner The LuckPerms group a promotion gives (<group>).
promotableGroups empty Groups besides default that do not block an automatic promotion.
promotionCommand lp user <player> parent set <group> The command a promotion runs; <player> becomes the UUID. Must contain <player>.
rulesUrl https://www.mcmiddleearth.com/help/terms Button in the welcome dialog.
guideUrl empty "Building guide" button; hidden when empty.
guestInfoText, welcomeText, announceText see the README MiniMessage, with <player>, <votes_needed>, <validity_days>.

Database tables

The plugin creates these with CREATE TABLE IF NOT EXISTS:

Table Holds
commonervote_votes One row per vote: voter and recipient UUIDs, time (epoch ms), weight (a share of a promotion) and reason. Unchanged from 1.2.9, so the old plugin can still read it.
commonervote_settings The shared settings, one name/value row each.
commonervote_promotions Who was promoted, when, how (auto, manual, detected) and whether they have seen the welcome.

Commands and permissions

/vote is registered with Brigadier (there is no commands: block in plugin.yml); /commonervote:vote works too.

Command What it does Permission
/vote Team: the guests online on this server. Others: their own progress, or "already a Commoner". none (what you see depends on commonervote.vote)
/vote <player> The vote dialog for a player (cast, renew, withdraw, optional reason). commonervote.vote
/vote withdraw <player> Withdraw your vote. commonervote.vote
/vote score Your own progress. none
/vote score <player> A player's progress. commonervote.score.other or commonervote.review
/vote review [player] The candidates, or the votes for one player. commonervote.review
/vote clear <player> Delete every vote for a player, after a confirmation. commonervote.clear
/vote promote <player> Promote now, after a confirmation. commonervote.promote
/vote config The settings dialogs (players only). commonervote.config
/vote help The subcommands you may use. none
Permission Default Grants
--- --- ---
commonervote.vote op Cast, renew and withdraw votes; the guest list.
commonervote.staff op Votes count with staffWeight. Includes commonervote.vote.
commonervote.exempt op Marks a player as already a Commoner or higher: cannot be voted for, never promoted, normal XP. Give it to the Commoner group and every rank above.
commonervote.score true Kept for compatibility; no command checks it.
commonervote.score.other op /vote score <player>.
commonervote.review op Review dialogs and /vote review.
commonervote.clear op Clear a player's votes.
commonervote.config op The settings dialogs.
commonervote.promote op "Promote now" and /vote promote.
commonervote.* op Everything above except commonervote.exempt.

Dialog buttons check the permission again when clicked, so removing a permission takes effect at once.

APIs and extension points

CommonerVote offers no Java API and fires no events for other plugins, and it is not published to the
Maven repository. What other software can rely on:

Inside the plugin, RankService, Messenger, DialogPresenter and CommandRunner are the seams: a new rank plugin or
transport means a new implementation of one interface, not changes to the services.

How it talks to other plugins and systems

System Direction How
LuckPerms reads, and writes through a command API: find players by name (any case, also offline), last known names, commonervote.exempt for offline players, direct groups. The promotion itself is the console command promotionCommand, with the player's UUID.
Velocity proxy both BungeeCord plugin messages on the BungeeCord channel (needs bungee-plugin-message-channel = true in velocity.toml).
MySQL / MariaDB reads and writes JDBC through the driver Paper ships; one database shared by every server.
Paper uses Dialog API for every screen; Paper's profile cache for names of offline players.

Proxy messages. A vote for a guest on another server goes out as ForwardToPlayer to that guest's name, subchannel
CommonerVote, with the payload Voted (new in 2.0) or Update (votes changed: refresh the XP bar and check for a
promotion; 1.2.9 understands this one too). The promotion announcement is MessageRaw to ALL, carrying the message as
JSON. Plugin messages need a player online on the sending server; when nobody is, the guest is brought up to date at
their next join.

The promotion guard. lp user <player> parent set <group> replaces all of a player's groups. So the plugin promotes
only a player whose direct groups are default plus the groups in promotableGroups; anyone else is listed as
"Auto-promotion blocked" in the review, and "Promote now" names the group that blocks it.

flowchart LR
  T[Team member: /vote] --> V[VoteService]
  V --> DB[(commonervote_* tables)]
  V --> P[PromotionService]
  P -->|direct groups, exempt| LP[LuckPerms API]
  P -->|console: promotionCommand| LP
  V -->|ForwardToPlayer, subchannel CommonerVote| PX[Velocity proxy]
  P -->|MessageRaw ALL| PX
  PX --> O[Guest's server: XP bar, action bar, promotion check]

Further reading