Extending for Forks
DzusillCore ships the reflective default so the template works everywhere with zero build setup. When a fork needs deep NMS that reflection canβt do reliably β packet sending, fake entities, custom mappings β it registers its own NmsAdapter on the same NmsAdapters registry. Feature code keeps depending on the NmsAdapter interface, so supporting a new server version never touches it.
There are two routes, in order of recommendation.
Route 1 β reflection override (no build changes)
Section titled βRoute 1 β reflection override (no build changes)βRegister a fork adapter for the versions you care about. Most-recently-registered wins, so it overrides the built-in default:
new NmsModule(this, NmsAdapters.defaults() .register(v -> v.isAtLeast(1, 21), Mapped1_21Adapter::new));Mapped1_21Adapter can extend ReflectiveNmsAdapter to inherit the working primitives and only add what it needs:
public final class Mapped1_21Adapter extends ReflectiveNmsAdapter {
public Mapped1_21Adapter(MinecraftVersion version) { super(version); }
@Override public boolean supports(NmsFeature feature) { return feature == NmsFeature.PACKET_SENDING || super.supports(feature); }
@Override public void sendPacket(Player player, Object packet) { Object handle = nmsHandle(player); Object connection = Reflection.getFieldValue(handle, Reflection.field(handle.getClass(), "connection")); Reflection.invoke(connection, Reflection.method(connection.getClass(), "send", /* Packet type */ )); }}Because the factory runs only when its predicate matches, the 1.21-specific class is never linked on other versions.
Route 2 β Maven profile + provided server jar (deep NMS)
Section titled βRoute 2 β Maven profile + provided server jar (deep NMS)βWhen you want to compile against real, type-safe NMS instead of reflecting:
- Install a remapped server jar into your local Maven repo with Spigot BuildTools (
--remapped) for each target version. - Add it as a
provideddependency under a Maven profile so it never ships in your JAR and doesnβt pollute the default build. - Write a version-specific
NmsAdapteragainst those NMS types and register it viaNmsAdapters.register(...).
1.20.5+ caveat: Paper switched to Mojang mappings and dropped the relocated CraftBukkit package. A plugin compiled against Mojang-mapped NMS must be re-obfuscated (or run with a runtime remapper) to load on a Spigot-mapped server. This is the painful part of real-NMS on Maven and the reason Route 1 is preferred for most forks. (Gradle +
paperweight-userdevautomates this, but migrating the build is out of scope for the template.)
Either route, the plugin never changes β only which NmsAdapter the registry selects.
Where NMS code lives in a fork
Section titled βWhere NMS code lives in a forkβKeep version-specific code isolated behind the interface, mirroring the core layout. A fork organises its NMS package feature-first under the abstraction, not scattered through the codebase:
me.yourname.yourpluginβββ nms/β βββ NmsAdapter.java # extend or re-declare the contract your features needβ βββ NmsModule.java # registers your adapters + provides the chosen oneβ βββ version/β βββ Adapter1_16.java # one class per version (or version family)β βββ Adapter1_20.javaβ βββ Adapter1_21.javaβββ feature/ βββ β¦ feature modules resolve service(NmsAdapter.class), never a concrete Adapter β¦Rules of thumb:
- Feature code never imports a
version.*class or anynet.minecraft/ CraftBukkit type. It depends only onNmsAdapter+NmsFeature. This is what keeps the JVM from linking the wrong versionβs classes. - One adapter per version (or family). Group versions that share an implementation behind a single predicate (
v -> v.isAtLeast(1, 17) && v.isBefore(1, 20)). - Add capabilities as
NmsFeatureconstants + interface methods, then implement them per adapter. Unsupported versions reportsupports(...) == falseand degrade instead of crashing. - Route all reflection through
me.dzusill.core.nms.reflect.Reflectionso lookups are cached and class resolution stays consistent across the breakpoints.