Modern, versioned access to the Minecraft protocol through direct NMS (CraftPlayer.getHandle(), ServerPlayer.connection.send). Menu/container packets live in PacketUxUi; sign input lives in SignGUI. Optional Netty intercept uses an instance-scoped injection path that refuses to silently overwrite another PacketAPI copy.
- Per-version paperweight adapters (1.16 → 26.2) sharing one NMS implementation when the packet layout does not break
- Typed
PacketWraparound rawnet.minecraft.network.protocol.Packethandles - Raw NMS first: every packet is observable as
Objectonce injected — no per-packet wrapper allocation - Cached
PacketTypeidentity per class (PacketKind, domain, direction) for O(1) hot-path dispatch - Versioned views only where layout breaks: move, interact, dig (
PlayerAction), use-item - Packet domains, cases, and exemptions so PacketAPI does not fight companion libraries (SignGUI, PacketUxUi)
PacketSendEvent/PacketReceiveEventplus NMS player events (NmsPlayerJoinEvent,NmsSwingEvent, …)- Expanded facades:
packets,players,connections,metadata,protocol,classifier,cases - Folia / Paper / Canvas region-safe schedulers
- Ownership-aware
PacketListenerBus.ensureInjected(single inject entry point) that detects foreign shaded copies via pipeline markers and backs off instead of hijacking - No duplication of PacketUxUi window click / open / set-slot / cursor / book packets
Latest release: 1.0.2 — hosted on Reposilite.
Maven (pom.xml):
<repositories>
<repository>
<id>mastersmp-releases</id>
<url>http://repo.mastersmp.net/releases</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>net.opmasterleo</groupId>
<artifactId>PacketAPI</artifactId>
<version>1.0.2</version>
</dependency>
</dependencies>Gradle (Groovy) (build.gradle):
repositories {
maven {
name = "mastersmp-releases"
url = uri("http://repo.mastersmp.net/releases")
isAllowInsecureProtocol = true
}
}
dependencies {
implementation("net.opmasterleo:PacketAPI:1.0.2")
}Gradle (Kotlin DSL) (build.gradle.kts):
repositories {
maven {
name = "mastersmp-releases"
url = uri("http://repo.mastersmp.net/releases")
isAllowInsecureProtocol = true
}
}
dependencies {
implementation("net.opmasterleo:PacketAPI:1.0.2")
}import net.opmasterleo.packet.PacketAPI;
import net.opmasterleo.packet.PacketAPIConfig;
import net.opmasterleo.packet.event.PacketReceiveEvent;
import net.opmasterleo.packet.event.PacketSendEvent;
import net.opmasterleo.packet.packet.OwnedPacketPolicy;
import net.opmasterleo.packet.packet.PacketExemption;
import net.opmasterleo.packet.packet.PacketExemptions;
import net.kyori.adventure.text.Component;
PacketAPI api = PacketAPI.bootstrap(plugin, PacketAPIConfig.builder()
.autoInjectOnJoin(true)
.registerExemption(PacketExemption.builder("MyMenus")
.domains(net.opmasterleo.packet.packet.PacketDomain.UI_CONTAINER)
.nameContains("OpenScreen")
.build())
.build());
api.packets().setHealth(player, 20f, 20, 5f);
api.packets().keepAlive(player, System.currentTimeMillis());
api.cases().when(packet, view -> {
if (view.isUiOwned()) return;
if (view.isEntityLifecycle()) { /* ... */ }
});
@EventHandler
public void onReceive(PacketReceiveEvent event) {
if (event.getDomain() == net.opmasterleo.packet.packet.PacketDomain.UI_SIGN) {
return; // SignGUI owns this path
}
}Built-in exemptions (disable with respectExemptionsOnSend(false)):
PacketExemptions.SIGN_GUI— sign editor packetsPacketExemptions.PACKET_UX_UI— menus, containers, slots, books, cursors
Listen without touching Netty (enable autoInjectOnJoin or call api.listeners().ensureInjected(player)):
import net.opmasterleo.packet.event.PacketSendEvent;
import net.opmasterleo.packet.event.PacketReceiveEvent;
import net.opmasterleo.packet.event.NmsPlayerJoinEvent;
import net.opmasterleo.packet.nms.packet.PacketKind;
import net.opmasterleo.packet.nms.packet.PacketViews;
@EventHandler
public void onOutbound(PacketSendEvent event) {
// Raw NMS handle — zero-copy
Object raw = event.getPacket();
if (event.packetType().kind() == PacketKind.MOVE_PLAYER) {
PacketViews.MoveView move = PacketAPI.get().packets().parseMove(raw);
double x = move.x();
}
}
@EventHandler
public void onInbound(PacketReceiveEvent event) {
if (event.packetType().kind() == PacketKind.INTERACT) {
PacketViews.InteractView interact = PacketAPI.get().packets().parseInteract(event.getPacket());
}
}
@EventHandler
public void onPacket(PacketSendEvent event) {
if (event.getPacketName().contains("SetHealth")) {
event.setCancelled(true);
}
}
@EventHandler
public void onJoin(NmsPlayerJoinEvent event) {
Object serverPlayer = event.getPlayerHandle();
}Send any raw NMS packet (no factory required):
api.packets().send(player, myNmsPacket);PacketAPI does not generate typed wrappers for every Clientbound* / Serverbound* class. Completeness comes from Netty interception + raw handles; use parseMove / parseInteract / parsePlayerAction / parseUseItem only for the hot serverbound families whose fields drift across versions.
Optional Netty filters (single entry point — never call low-level inject helpers):
api.listeners().addOutbound(player, (viewer, packet) -> OutboundPacketResult.keep());
// Conflict / ownership probe for status/debug:
InjectionProbe.Result probe = api.detectInjectionConflicts(player);
String status = api.injectionStatus(player);./gradlew shadowJar
./gradlew publish # Reposilite releases
./gradlew publishSnapshot # Reposilite snapshots