MCME-Architect is the builder's toolkit of the MCME build server: one large Paper plugin that changes how blocks behave and gives builders their tools. It holds about twenty mostly independent features: resource-pack "special blocks" with their own placing rules, build inventories, kits, item blocks, the per-world no-physics list, editors for armor stands, banners, signs and paintings, its own copy and paste, resource-pack switching and releases, live biome tuning, and map layers on the dynmap web map. Its build tools ask TheGaffer whether a player may build at a spot, so they respect job areas. The code is on GitHub at MCME/MCME-Architect; this page describes release 3.3.1 on master.
The repository's own Developer Manual is the detailed reference (startup order, every subsystem, threading rules, pitfalls). This page is the overview; read the manual before changing code.
At a glance
| Repository | MCME/MCME-Architect (GPL-3.0) |
| Release branch | master (releases); new work goes to development |
| Version described | 3.3.1 (tag v3.3.1) |
| Platform | Paper 26.2 (api-version: 26.2), plugin name MCME-Architect, load: STARTUP |
| Build | Maven 3.9, JDK 25 (<release>25</release>), no Maven wrapper |
| Depends on | Hard: PluginUtils (depend). Needed but not declared: WorldEdit 7.4. Soft: ProtocolLib, dynmap, ViaVersion, MCME-Connect, Essentials (softdepend). Optional and not declared: TheGaffer |
| Maven coordinate | com.mcmiddleearth:MCME-Architect, on the MCME Maven repository |
How the code is organised
There is one main class, com.mcmiddleearth.architect.ArchitectPlugin. Each feature lives in its own package under com.mcmiddleearth.architect, usually as a manager or data class with static state, one or more listeners, and commands whose executors extend AbstractArchitectCommand. Shared helpers sit in com.mcmiddleearth.util.
| Package | Feature |
|---|---|
| (root) | Core: ArchitectPlugin, PluginData (per-world data, permission checks), WorldConfig, Modules (the per-world switches), Permission (the node enum), ConfigFile (all config writes), Log and LogFileManager |
specialBlockHandling |
Special blocks, the build inventory (/inv), /get kits, item blocks, the stick and the flint, doors. More than a third of the source files |
serverResoucePack (sic) |
Resource packs: /rp, pack regions, player pack settings, /rp release (RpManager, RpListener, RpReleaseUtil) |
biomeTuning |
Live biome tuning (/biometune) and its Dialog editor; the only code that reaches Minecraft internals, through ReflectionNmsBridge |
mapLayers, mapLayers.customBiomes |
Architect's layers on the dynmap web map; DynmapBackend is the only class here that names dynmap types |
noPhysicsEditor |
The no-physics list, exception areas and the redstone freeze (/nophy) |
copyPaste |
Architect's own clipboard (/copy, /cut, /paste, /undo, /redo, /flip, /rot) |
gamemodeSwitcher |
The F3+F4 game-mode switcher for builders without op, and the invisible-blocks gate |
armorStand, bannerEditor, signEditor, paintingEditor |
The editors |
customHeadManager, randomiser, viewDistance, chunkUpdate, entityLogging, voxelStencilEditor, weSchematicsViewer, WorldGeneration |
Smaller tools |
com.mcmiddleearth.util |
Shared helpers, including TheGafferUtil (the TheGaffer link) and PathSafety |
Things to know before you read the code:
- Features are switched per world. Nearly every feature checks
PluginData.isModuleEnabled(world, Modules.X). The 64 switches live inWorldConfig/<world>.yml, withWorldConfig/defaultWorldConfig.ymlas fallback. A switch missing from both counts as on (and is written astrue), so a new feature starts on in every world. - Two code generations. The 2.x code (2016–2025) uses static managers and legacy
ChatColor; the 3.x code (map layers, biome tuning, the game-mode switcher,/rp releasefeedback) uses small final classes, records and interfaces as test seams. New code follows the 3.x style. - Architect starts before the worlds (
load: STARTUP), soBukkit.getWorlds()is empty inonEnable. Worlds are set up onWorldLoadEvent. This also lets Architect act as a world generator: a world with generatorMCME-ArchitectorMCME-Architect:<name>_<size>gets a void world with one flat square of grass. - Architect logs to its own files,
plugins/MCME-Architect/logs/architect_*.log, not to the console, unlesslogging.file.alsoServerLogistrue. - Commands are classic
plugin.ymlcommands (no Brigadier). No command has apermission:key; every check is in the executor.
Building and testing
mvn -B -ntp clean verify # build, run every test, make the jar (what CI runs)
mvn -B package -DskipTests # the jar only
mvn -B package -Dbuild.type=RELEASE # a jar named as a release build
mvn -B test -Dtest=FbtCommandTest # one test class
- JDK 25 and Maven 3.9. Dependencies resolve from Maven Central, the PaperMC, Mojang, EngineHub, ProtocolLib, ViaVersion and dynmap repositories, and repo.mcmiddleearth.com for PluginUtils and MCME-Connect. Reading needs no credentials. See Setting up.
- Jar name:
MCME-Architect-<version>-<build.type>-<commit>.jarintarget/.build.typeisDEVby default. The commit id comes fromgit-commit-id-maven-plugin; in a linked git worktree pass-Dgit.commit.id.abbrev=$(git rev-parse --short=7 HEAD), because the plugin can read the wrong HEAD there. - Everything is
provided, nothing is shaded. The server supplies Paper, PluginUtils, WorldEdit, ProtocolLib, dynmap, ViaVersion, MCME-Connect, TheGaffer and a MySQL JDBC driver (the pom declares none). - Only four resources go into the jar:
plugin.yml,config.yml,protocolVersions.ymlanditems.json, all filtered. The other files insrc/main/resources(old release scripts, pre-1.13 inventories) are not packaged. A new resource needs an<include>in the pom. - Tests: JUnit Jupiter 6.1 and MockBukkit for Paper 26.2 (
mockbukkit-v26.24.116.1); 118 test classes in 3.3.1. Surefire runs every test class in a fresh JVM (reuseForks=false) because of static state. The rule is oneMockBukkit.mock()and oneMockBukkit.load(ArchitectPlugin.class)per class. Hand-written fakes stand in for other systems:FakeGaffer(TheGaffer's reflective API),FakeDynmapandFakeMarkerApi,FakeMysql,FakeScript(release scripts) andFakeNmsBridge(Minecraft internals). Guard tests to know about:PluginYmlPermissionsTest,DynmapIsolationTest,ConfigFileTest,RpTableSchemaTest. - Run the tests through Maven. They read the filtered
plugin.yml; the source copy hasname: ${project.artifactId}, which Bukkit rejects. - CI:
.github/workflows/build.ymlrunsmvn -B -ntp clean verifyon every push, every pull request and everyv*tag, and uploads the DEV jar as an artifact. On av*tag it checks that the tag equalsvplus the pom version, then publishes a RELEASE build to repo.mcmiddleearth.com. A manual run also publishes, without the tag check, so run it only on a commit that really is that version. The workflow does not create GitHub Releases; maintainers attach the published jar and a.sha256to a GitHub Release by hand.
Configuration
Architect's data folder is plugins/MCME-Architect/. The files a developer meets most:
| File | What it holds |
|---|---|
config.yml |
Server-wide settings (below). Several servers may share one file |
WorldConfig/defaultWorldConfig.yml, WorldConfig/<world>.yml |
The per-world module switches and lists (no-physics list, inventory access, item-block limit, and more) |
WorldConfig/server.yml |
A pseudo-world; only its modules.command.fullBrightness is read, to decide whether /fbt exists |
protocolVersions.yml |
Version keys mapped to protocol numbers, used to pick a resource-pack slot |
inventories/block/<pack>/*.yml |
Special-block definitions, one folder per resource pack |
Two rules shape all config.yml code:
- The bundled
config.ymlis the default for every key a server's file lacks, but new keys are never written into an existing file. Only put safe defaults in it. - Never call
saveConfig(). Every write goes throughConfigFile.update(config -> ...), which rereads the file first so servers sharing it do not overwrite each other.ConfigFileTestfails if any other class saves the config.
Sections of the bundled config.yml:
| Key | Default | Meaning |
|---|---|---|
ServerResourcePacks.<pack>.<client>.<resolution>.<variant>.'<versionKey>' |
one example Human slot |
Pack slots, each with url and sha. Client is vanilla, sodium or lite; version keys come from protocolVersions.yml and must be quoted. /rp server and /rp calcsha write these |
rpSettingsDatabase.user / password / dbName / ip / port |
xxx / xxx / xxx / localhost / 3306 |
MySQL database for players' pack settings, shared by all servers (table architect_rp). xxx as user or database name means "no database" |
dynmap.enabled |
true |
Legacy switch for the RP-region and item-block-limit layers |
mapLayers.rpRegions, itemBlockLimit, noPhysics, itemBlockBudget, customBiomes |
commented out (every layer on, hidden by default) | Per-layer enabled, hidden, colours, border and area opacity; customBiomes.scanMegabytesPerSecond (20) paces the region-file scan |
entityLogger.enabled |
true |
The entity-count layer (/architect eLog) |
EntityLimit.number / radius |
500 / 80 |
Read, but nothing uses the values |
headGalleryLocation |
a location | Where /chead warp sends players; written by /chead setCollection |
gitHubRpReleases.<pack>.owner / repo / script / path / timeoutMinutes |
example entries; timeoutMinutes 30 |
What /rp release runs for a pack. separateSodium appears in the example but nothing reads it |
inventoryDownload.script / path |
example entry | The script /inv download runs |
logging.file.enabled / alsoServerLog / maxSizeMB / maxFiles / retentionDays |
true / false / 10 / 5 / 14 |
Architect's rotating log files |
biomeTuning.undoDepth / backupsPerBiome |
20 / 20 |
Undo steps per biome; backups kept per biome file |
biomeTuning.sparePattern / targetPack |
'mcme:custom_###' / bukkit |
Ids of spare biomes builders can claim; the datapack folder that holds new spares and mcme-biometune.json |
biomeTuning.refresh.cooldownSeconds / timeoutSeconds / publishPlayersPerSecond / publishCooldownSeconds |
5 / 15 / 5 / 30 |
Pacing of the client refreshes that show tuned biomes |
Three keys are read but not in the bundled file: CopyPasteUndoLimit, CustomHeadUploadPath (written when missing) and inventoryUpload (/inv upload fails until it is set).
/architect reload runs loadData() only. Changes to logging.*, biomeTuning.*, rpSettingsDatabase, and anything that onEnable registers need a server restart. The User Manual's Running Architect chapter explains every per-world switch.
Commands and permissions
Every command also answers to the mcme-architect: prefix (for example /mcme-architect:undo when WorldEdit owns /undo). Most commands also need their per-world switch to be on.
| Command | Aliases | What it does | Permission |
|---|---|---|---|
/architect [help] |
Lists the commands you may use | architect.help (everyone) |
|
/architect version, reload, reloaddata, world, blockdata, dev, checkNBT, weather, eLog, placeAllBlockStates |
Admin and debug tools | architect.info, architect.reload, architect.checknbt or architect.weather |
|
/architect maplayers refresh, scan [<world> [off]] |
Redraws the map layers; picks the worlds of the Custom biomes layer | architect.maplayers |
|
/inv |
Opens the build inventory of your pack (the F key does too); saved inventories; admin reload, download, upload | architect.inventory, plus .save, .other, .reload, .download, .upload |
|
/get |
Hotbar kits, dyed leather armor, heads | architect.getCommand, plus architect.get.* per part |
|
/itemblock |
Item-block counts, glow, clean-up and limit regions | architect.itemBlock, plus .remove, .region.create, .region.limit |
|
/nophy |
The no-physics list and its exception areas | architect.noPhysicsList.view, .edit, .exception |
|
/chunkupdate |
Automatic block resends (auto); the radius form does nothing in 3.3.1 |
architect.chunkupdate, .auto |
|
/switchstick |
/swst, /ss |
Turns block-state editing with the stick on or off | architect.switchstick |
/weselect |
/wes |
Sets the command a flint click puts in chat | architect.weselect |
/sign, /banner, /armor |
Sign, banner and armor-stand editors | architect.signEditor, architect.bannerEditor[.save\|.delete], architect.armorStandEditor[.trusted\|.delete] |
|
/copy, /cut |
/cp, /ct |
Copies (and clears) a cuboid WorldEdit selection to Architect's clipboard | architect.copypaste plus architect.copypaste.limit.<n> or architect.copypaste.unlimited |
/paste, /undo, /redo, /flip, /rot |
/pst /ps, /un /ud, /re /rd, /flp /fl, /rt |
Clipboard operations | architect.copypaste |
/random |
Randomises one block state around you | architect.randomiser.user, .allowMaterials |
|
/vv, /sl, /schlist |
VoxelSniper stencils; WorldEdit schematic list | architect.voxel.viewer, .delete, .stencilListEditor; architect.weSchemViewer |
|
/chead |
Custom heads, their review and the gallery | architect.customHeadManager.user, .manager |
|
/fbt |
/fullbright, /nightvision |
Endless night vision | architect.fullBrightness |
/viewdistance |
/viewdist |
Sets the view distance Architect tells your client | architect.viewdistance |
/parrot |
Removes parrots from your shoulders | none | |
/rp |
Switches your pack and pack settings; check needs no permission |
architect.resourcePackSwitcher |
|
/rp server, calcsha, release, dropdb, list, create, edit, remove |
Pack slots, hashing, releases, the settings table, pack regions | architect.resourcePackAdmin |
|
/biometune |
Live biome tuning: the editor and its commands | architect.biomeTune, plus .save, .publish, .admin |
Permission structure. Nodes follow architect.<feature>[.<sub>] in camelCase and are listed in the Permission enum. Four rank bundles nest, each including the one below: architect.ranks.headDesigner → designer → artist → adventurer; architect.* grants headDesigner. Notable nodes outside the bundles:
| Permission | Default | Grants |
|---|---|---|
architect.help |
true | /architect help |
architect.gamemodeSwitcher |
false | Every mode on the F3+F4 switcher without op (children .creative, .survival, .adventure, .spectator, each default false). Give .spectator only with another mode, or a spectator cannot switch back |
architect.invisibleBlocks |
op | Placing barrier, light and structure-void blocks |
architect.interactDragonEgg |
false | Interacting with dragon eggs (in the adventurer bundle) |
architect.inventory.ignoreProtection |
op | Changing container contents without build permission where chest protection is on |
architect.copypaste.limit.<n> |
not declared | Copy size limit of n blocks, read from the player's effective permissions |
architect.biomeTune.save / .publish / .admin |
op | Writing tuned biomes to the datapack; refreshing other players; spares, self-test and debug |
The full tree, with every description, is in plugin.yml and the User Manual's Permissions chapter.
APIs and extension points
Architect has no formal API module, but other plugins rely on a few public static methods. Keep them stable:
| API | Used by | What it gives |
|---|---|---|
RpManager.getPlayerData(Player) → RpPlayerData |
MCME-Introduction | A player's resource-pack settings and status (RpPlayerStatus, with the SENT and last-status pair kept for it) |
RpManager.isSodiumClient(Player) |
MCME-Introduction | Whether the client announced MCME's modpack marker |
RpManager.getCurrentRpName(Player) |
Architect itself (special blocks, inventories) | The name of the pack last sent to the player |
BiomeTuning.isRefreshing(UUID) |
any listener | true while a player is being sent through the configuration phase, which looks like a quit and a rejoin; skip quit and join work then |
To compile against Architect, depend on com.mcmiddleearth:MCME-Architect (scope provided) from the Maven repository.
Inside the plugin, the extension points are:
- A new feature: a package of its own, a command in
plugin.yml(withoutpermission:), a node inplugin.ymlwith adescription:and in thePermissionenum, an executor extendingAbstractArchitectCommand, registration inonEnable, and usually aModulesswitch. The DEV_MANUAL's Adding a feature checklist has twelve steps;PluginYmlPermissionsTestchecks the plugin.yml side. - A new special-block type: a constant in
SpecialBlockType, a class with a staticloadFromConfig(ConfigurationSection, String)that overridesgetBlockState, and a case inSpecialBlock.createSpecialBlock. - A new map layer: implement
MapLayer(marker-set id, label,shapes()), build it inArchitectLayers.fromConfig, and callMapLayers.changed(markerSetId)when its data changes. Never name dynmap types outsideDynmapBackend;DynmapIsolationTestfails the build if you do. - Soft dependencies stay in classes of their own, loaded only after
isPluginEnabled: dynmap inDynmapBackendandELogDynmapUtil, ProtocolLib in the*Protocolclasses, MCME-Connect inRpListener.
How it talks to other plugins and systems
flowchart LR
A[MCME-Architect]
A -- "reflection: hasBuildPermission,<br/>getBuildProtectionMessage,<br/>recordExternalBuild" --> G[TheGaffer]
A -- "depend (Maven + plugin.yml)" --> PU[PluginUtils]
A -- "selections, regions" --> WE[WorldEdit]
A -- "marker API via DynmapBackend" --> D[dynmap / LiveAtlas]
C[MCME-Connect] -- "PlayerConnectEvent" --> A
I[MCME-Introduction] -- "RpManager API" --> A
A -- "runs release script,<br/>reads its output" --> R[ResourcePackScripts]
A -- "JDBC, table architect_rp" --> DB[(Pack settings DB)]
| System | Direction | How | Notes |
|---|---|---|---|
| TheGaffer | calls | Reflection, in com.mcmiddleearth.util.TheGafferUtil |
Before a build or interact action Architect calls TheGaffer's public static hasBuildPermission(Player, Location) and, on a refusal, getBuildProtectionMessage(Player, Location) to tell the player why. After it sets blocks in code, which vanilla events would not report (special blocks, doors it places itself, half doors and similar, but not sneak edits), it calls recordExternalBuild(Player, Location, true) for each block so they count in TheGaffer's job statistics. Breaks need no call: they go through the vanilla break event, which TheGaffer counts itself. No Maven dependency and no plugin.yml entry. The checks fail open: without TheGaffer, or if the reflective call fails, the build is allowed (and the failure logged). A TheGaffer older than 3.0.0, which lacks recordExternalBuild, gets one warning and no counting. TheGaffer pins the three signatures with ProtectionApiContractTest; change both repos together |
| PluginUtils | depends on | Maven provided (2.0.4) and depend |
Messages with the [Architect] prefix, paged and clickable lists (FancyMessage), WorldEdit selections, plotStoring for the clipboard, the second debug channel |
| WorldEdit 7.4 | depends on | Maven provided (7.4.5); not declared in plugin.yml |
Selections for /copy, /cut, /nophy exception, /rp create, /itemblock create; pack and item-block regions are WorldEdit regions. Guarded by WorldEditGuard |
| dynmap (shown in LiveAtlas) | writes | Marker API behind MapBackend |
Map layers (RP regions, item-block limits and budget, no-physics exception areas, custom biomes) and the entity-log layer. Marker sets are non-persistent and redrawn at every start |
| MCME-Connect | listens | PlayerConnectEvent (Maven provided 3.0.1, softdepend) |
Sends a player's pack when they join the network. Without MCME-Connect none of RpListener's handlers register |
| MCME-Introduction | is read by | RpManager static methods |
See the API table above |
| ProtocolLib | uses | Packet listeners | /viewdistance and the game-mode switcher. Compiled against 5.3.0; 26.x servers need a 5.5.0+ dev build |
| ViaVersion | reads | viaversion-api |
The client's real protocol version, to choose a pack slot; falls back to Paper's |
| ResourcePackScripts | starts | /rp release runs the pack's release script with sh (Linux only) |
Architect reads the script's output lines to report progress and the result in chat. That output format is a contract with the release pipeline; change both sides together |
| MCME modpack clients | receives | Plugin channel mcme-modpack-marker:hello |
Marks the player as a Sodium client for the choice of pack |
| MySQL | reads/writes | JDBC (jdbc:mysql:), driver supplied by the server |
Players' pack settings, one table shared by all servers. The connection is made on the main thread at start-up |
| Datapacks | writes | Files in the primary world's datapacks/ folder |
Biome tuning saves biome JSON, spare biomes and mcme-biometune.json |
Further reading
- README, Developer Manual and User Manual on
master - Setting up, Contributing, Maven repository
- PluginUtils, TheGaffer
- For builders and staff: Architect for builders, Architect staff tools