MagmaCore developer guide
Generated Java reference includes searchable classes, methods and signatures, with the source version and commit recorded for each module.
MagmaCore is a library shaded into consuming plugins. It is not a standalone server plugin and should not appear in depend as if players could install a MagmaCore plugin JAR.
Dependency and packaging
The current development coordinate is com.magmaguy:MagmaCore:2.2.0-SNAPSHOT. It is published to MagmaGuy's releases repository, not the snapshots repository, which only holds older 1.x snapshots. Snapshots can change without changing that version string, and the published build is not refreshed with every plugin release, so it can be older than the MagmaCore shaded into current plugins. The build published on October 5, 2026 includes the restart-free update and match APIs below. To compile against newer unpublished changes, build MagmaCore from source and publish it to Maven Local, as described in Building a consumer after library changes. Record the resolved artifact when you need reproducible builds.
repositories {
maven("https://repo.magmaguy.com/releases")
}
dependencies {
implementation("com.magmaguy:MagmaCore:2.2.0-SNAPSHOT")
}
<repositories>
<repository>
<id>magmaguy-releases</id>
<url>https://repo.magmaguy.com/releases</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>com.magmaguy</groupId>
<artifactId>MagmaCore</artifactId>
<version>2.2.0-SNAPSHOT</version>
</dependency>
</dependencies>
These declarations resolve the dependency. Configure your build's shading task to include it in the final plugin JAR. Follow the relocation rules of an existing consumer with the same integration needs, and check string-based reflective lookups when changing package names. The distribution relocates org.luaj, org.reflections and javassist into com.magmaguy.shaded and leaves out the compile-time javax.annotation classes.
Each plugin can have its own loaded copy of the library. Java classes with the same name from different plugin classloaders are different types. Use the explicit cross-plugin contracts, such as EliteMobs' Lua power service and the location registry's adapters. Do not cast another plugin's private MagmaCore object to your copy's type.
Modules and ownership
| Module | Responsibility |
|---|---|
core | Plugin lifecycle, configuration, commands, menus, matches, location queries, Lua scripting, Nightbreak content and shared utilities |
nms:core | NMSManager, NMSAdapter and the version-independent EasyMinecraftGoals interfaces |
nms:v* | Server-version implementations of the NMS contract |
dist | Shaded distribution containing the core and active adapters |
Shared enchantments, item actions, account handling, setup menus, catalogs and updates belong to the core services. Reuse their owning component when adding behavior to a consumer.
Plugin lifecycle
The minimal core lifecycle follows the existing consumer pattern:
package example;
import com.magmaguy.magmacore.MagmaCore;
import org.bukkit.plugin.java.JavaPlugin;
public final class CoreConsumer extends JavaPlugin {
@Override
public void onLoad() {
MagmaCore.createInstance(this);
}
@Override
public void onEnable() {
MagmaCore.onEnable(this);
}
@Override
public void onDisable() {
MagmaCore.shutdown(this);
}
}
This initialization has effects beyond obtaining a utility object. It initializes account handling, shared commands, listeners and temporary-block support. The overload createInstance(plugin, NightbreakPluginSpec) supplies plugin-specific shared-command copy.
Prefer methods that take the owning plugin explicitly. getInstance() refers to the singleton for the loaded library copy, whose requesting plugin is established when that instance is created. It should not be used to infer ownership for every consumer.
shutdown(plugin) clears that plugin's downloader, content refresher, initialization and updater registrations, then also invokes shutdown for the loaded copy's shared runtime. It is not limited to removing one registry entry. Call it for your own embedded library during your plugin's shutdown; never shut down another plugin's library to reset your integration.
Call createInstance from onLoad so the plugin's configuration archive owner is registered before another plugin can import content for it. The archive runs through the target plugin's own shaded implementation; do not interpret another plugin's retirement rules using your copy of MagmaCore.
For phased startup, startInitialization accepts asynchronous work, synchronous work, success and failure callbacks. It archives matching retired configuration files before invoking the asynchronous initialization work. Keep Bukkit world/entity work in the synchronous phase. Long-running asynchronous work should observe isShutdownRequested(plugin) between phases. getInitializationState and isPluginReady expose the initialization manager's state.
Restart-free plugin updates
NightbreakPluginBootstrap.registerStandardCommands registers applypluginupdate along with the other shared subcommands. It swaps in a downloaded update by disabling your plugin, replacing its jar and enabling it again in the same server session, so onDisable must cancel tasks, unregister listeners and release every reference that would keep the old class loader alive. Enabled plugins whose main class is under com.magmaguy. and that depend or soft-depend on yours are reloaded with it. Any other plugin depending on yours makes the swap refuse and ask for a restart. See Applying updates without a restart for the server-owner side.
If part of your plugin cannot reload in place, call NightbreakPluginHotSwap.requireRestartForUpdates(plugin, reason) before update checks start, and pass false as the second argument of NightbreakPluginUpdater.setAutoDownloadConfigDefault so config.yml does not offer applyPluginUpdatesWithoutRestart. The plugin still reloads with its current jar when a plugin it depends on is swapped. NightbreakPluginUpdater.onPluginUpdateDownloaded(plugin, listener) runs a listener on the download thread after a validated update reaches the update folder.
NMS and packet entities
Initialize NMS only when your plugin needs it, using NMSManager.initializeAdapter(plugin) from com.magmaguy.easyminecraftgoals. Check NMSManager.isEnabled() before using adapter-dependent behavior. An initialization call can fail or find an unsupported server version.
The distribution includes the following adapter families:
| Adapter | Minecraft versions |
|---|---|
v1_21_R3 | 1.21.4 |
v1_21_R4 | 1.21.5 |
v1_21_R5 | 1.21.6, 1.21.7, 1.21.8 |
v1_21_R6 | 1.21.9, 1.21.10 |
v1_21_R7_spigot, v1_21_R7_paper | 1.21.11, separate Spigot/Paper implementations |
v26 | 26.1 and the current 26.x adapter family |
Adapter selection is not a blanket compatibility guarantee for future server releases. The runtime selector in NMSManager determines whether the installed server can load an adapter. Older than 1.21.4 is outside the current adapter set.
EasyMinecraftGoals owns packet-only entities, their visibility and interaction routing, and movement/pathfinding helpers. These are not ordinary world entities. Manage them through their owning API and clean up the objects your consumer creates while the adapter is still available.
Location and protection queries
com.magmaguy.magmacore.location.LocationQueryRegistry exposes dungeon and region-protection queries, including isInAnyDungeon, isInAnyProtectedRegion and canBuild.
Register custom providers with registerDungeonLocator or registerProtectionProvider; unregister the same provider when its owner disables. The registry includes adapters for existing plugins and discovers cross-plugin dungeon providers without requiring consumers to share one MagmaCore classloader.
Check the query that matches the action. Being outside a dungeon is not equivalent to having build permission at a location. Run queries that touch Bukkit or protection plugins on the server thread.
Matches
The match lifecycle lives in com.magmaguy.magmacore.match. Call MatchCore.enable(plugin) once while enabling, then extend Match, pass a MatchSettings from MatchSettings.builder() to its constructor, and call open() after your subclass finishes constructing. Game logic goes in the protected hooks such as onJoin, onStart, onDeath and onLeave. MagmaCore.shutdown(plugin) destroys the plugin's remaining matches.
The core fires no Bukkit events of its own. It calls the MatchApi set in the settings, so your plugin decides which events to expose. A cancellable callback vetoes the step by returning false or throwing. Each plugin's copy only tracks its own matches; a player metadata marker, magmacore_match, tells other copies that a player is already in one.
MatchSettings.builder().permission(...) sets the permission a player needs to join. Override hasJoinPermission(player) to decide per player, for example to let staff in without it. A participant who leaves because they died (LeaveReason.DIED) is healed to full before being moved out, so a maximum-health bonus removed on the way out keeps the full ratio. A player who leaves the match for any other reason keeps their current health, and players still in the match when it ends are healed. Both behaviors are newer than the build published on October 5, 2026.
The older match classes in com.magmaguy.magmacore.instance, including MatchInstance, were removed. InstanceProtector remains there and is registered by MagmaCore.enableWorldProtections(plugin).
Lua Mind hosts
MagmaCore compiles Lua-authored Mind programs for host plugins. The script format is described in Behavior Programs. A host creates its own LuaMindModuleRegistry, opens an owner for its namespace with openOwner(namespace), registers modules with the owner's registerModule, and gets a LuaMindDefinition from the owner's validateProgram. Each instantiate call on the definition builds a fresh program, with its own Lua environment, for one entity. The hooks below are newer than the build published on October 5, 2026.
- Inspecting a module.
LuaMindModuleRegistry.inspectModule(sourceName, source)reads a module's id, revision, dependencies andsettingsdefaults without registering it.LuaMindModuleDescriptor.settings()holdsDouble,Boolean,StringorList<String>values. MagmaCore does not hand settings to scripts. A host that wants per-creature values merges its own over these defaults and exposes them, as FreeMinecraftModels pets do withc.settings. - Extending callback contexts.
LuaMindDefinition.instantiate(LuaMindContextExtension)adds your fields to the context table of every callback.extend(table, context, contribution)receives the table before Lua sees it, the callback'sMindContext, and the id of the module that declared the callback, or the program's own id. It runs on the server thread inside the callback's execution budget, so its CPU time counts toward the program'scpu_microsand an exception fails the callback. Add fields withrawset, or withLuaTableSupport.lazyField, which keeps the table's existing__indexfor every other key. Do not replace the table's metatable or its__index, because the built-in sections are read through them, and do not overwritetick,generation,entity,memory,perception,actuatororactions. Reading a built-in section fromextendbuilds it for that callback. Pass a separate extension to eachinstantiatecall when it holds per-entity state. - Probe interval. A Java behavior can override
MindBehavior.probeInterval(), the equivalent of Lua'sprobe_interval, to askcanStartevery N ticks while it is stopped. It must be 1 toMindBehavior.MAX_PROBE_INTERVAL(200);MindProgramrefuses other values. The default of 1 asks every tick. AMindSensorsenses on the first tick its body is ticked and then everyintervalTicks()ticks on a per-entity phase. - Flight.
MindActuator.steerFlight(Vector), which was already in that build, flies aFLYINGbody by that velocity for one tick and needs the MOVE and LOOK leases. New are its cap and the Luasteer_flight:MindActuator.MAX_FLIGHT_SPEEDis 4 blocks per tick, and a faster velocity is scaled down to it.RouteFlightalso caps its speed there, so a route set faster than 80 blocks per second flies at 80.
Building a consumer after library changes
Use JDK 21 and publish MagmaCore to Maven Local with ./gradlew publishToMavenLocal, or .\gradlew.bat publishToMavenLocal on Windows. Make sure the consumer resolves that repository and the intended version, then rebuild its shaded JAR. Replacing a library in Maven Local does not update an already built plugin.
See the developer index for installed-plugin APIs. Those integrations normally use provided or compileOnly; MagmaCore's embedded-library contract is different.