Skills/nms/nms-packet-sender/SKILL.md
產生封包發送工具類,透過 ServerPlayer.connection 將 Clientbound 封包推送至客戶端(Paper NMS + Mojang-mapped)/ Generate packet sender utility to push Clientbound packets via ServerPlayer.connection
npx skillsauth add MrPippi/MPS nms-packet-senderInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
nms-packet-sender
產生標準的 NMS 封包發送工具類,涵蓋單人、多人、廣播、延遲發送等情境。所有發送點透過 ServerPlayer.connection.send(Packet<?>) 進入 Netty write queue。
| 參數 | 範例 | 說明 |
|------|------|------|
| package_name | com.example.network | 產出類別所在 package |
| class_name | PacketSender | 工具類名稱 |
| include_batch | true | 是否產生批次/廣播方法 |
| include_async | true | 是否產生延遲/非同步發送方法 |
PacketSender.java — 主工具類(static 方法)PacketBuilder.java(選)— 常見 Clientbound 封包建構器參見 Skills/paper-nms/PLATFORM.md。關鍵依賴:
dependencies {
paperweight.paperDevBundle('1.21.1-R0.1-SNAPSHOT')
}
PacketSender.javapackage com.example.network;
import net.minecraft.network.protocol.Packet;
import net.minecraft.server.level.ServerPlayer;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import java.util.Collection;
import java.util.Objects;
@SuppressWarnings("UnstableApiUsage")
public final class PacketSender {
private PacketSender() {}
/** 發送封包給單一玩家(任意執行緒皆可)。 */
public static void send(Player player, Packet<?> packet) {
Objects.requireNonNull(player, "player");
Objects.requireNonNull(packet, "packet");
ServerPlayer nms = ((CraftPlayer) player).getHandle();
if (nms.connection == null) return; // 玩家已離線
nms.connection.send(packet);
}
/** 批次發送給多位玩家。 */
public static void sendAll(Collection<? extends Player> players, Packet<?> packet) {
for (Player p : players) send(p, packet);
}
/** 對伺服器全體玩家廣播封包。 */
public static void broadcast(Packet<?> packet) {
sendAll(Bukkit.getOnlinePlayers(), packet);
}
/** 對指定世界的玩家廣播封包。 */
public static void broadcastWorld(World world, Packet<?> packet) {
sendAll(world.getPlayers(), packet);
}
/** 延遲 N tick 後在主執行緒發送。 */
public static void sendLater(Plugin plugin, Player player, Packet<?> packet, long delayTicks) {
Bukkit.getScheduler().runTaskLater(plugin, () -> send(player, packet), delayTicks);
}
/**
* 以非同步方式發送(封包需已建構完成,不可在此存取世界狀態)。
* 適合大量封包批次,不阻塞主執行緒。
*/
public static void sendAsync(Plugin plugin, Player player, Packet<?> packet) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> send(player, packet));
}
}
PacketBuilder.java(常見 Clientbound 範例)package com.example.network;
import io.netty.buffer.Unpooled;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.chat.MutableComponent;
import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket;
import net.minecraft.network.protocol.common.custom.CustomPacketPayload;
import net.minecraft.network.protocol.game.ClientboundSetActionBarTextPacket;
import net.minecraft.network.protocol.game.ClientboundSetTitleTextPacket;
import net.minecraft.resources.ResourceLocation;
@SuppressWarnings("UnstableApiUsage")
public final class PacketBuilder {
private PacketBuilder() {}
/** 建立 Action Bar 文字封包。 */
public static ClientboundSetActionBarTextPacket actionBar(Component message) {
String json = GsonComponentSerializer.gson().serialize(message);
MutableComponent nmsComponent = net.minecraft.network.chat.Component.Serializer
.fromJson(json, net.minecraft.core.RegistryAccess.EMPTY);
return new ClientboundSetActionBarTextPacket(nmsComponent);
}
/** 建立 Title 封包。 */
public static ClientboundSetTitleTextPacket title(Component title) {
String json = GsonComponentSerializer.gson().serialize(title);
MutableComponent nmsComponent = net.minecraft.network.chat.Component.Serializer
.fromJson(json, net.minecraft.core.RegistryAccess.EMPTY);
return new ClientboundSetTitleTextPacket(nmsComponent);
}
/** 建立自定義 Plugin Message 封包(CustomPayload)。 */
public static ClientboundCustomPayloadPacket customPayload(ResourceLocation channel, byte[] data) {
CustomPacketPayload payload = new CustomPacketPayload() {
@Override
public void write(FriendlyByteBuf buf) {
buf.writeBytes(data);
}
@Override
public ResourceLocation id() {
return channel;
}
};
return new ClientboundCustomPayloadPacket(payload);
}
}
src/main/java/com/example/
├── MyNmsPlugin.java
└── network/
├── PacketSender.java
└── PacketBuilder.java
PacketSender.send() 內部呼叫 connection.send(),可在任何執行緒呼叫(Netty 會自行排入 write queue)connection 欄位在玩家離線時為 null,send 前需檢查Skills/_shared/nms-threading.md| 錯誤 | 原因 | 解法 |
|------|------|------|
| NullPointerException: connection | 玩家已離線 | 加上 if (nms.connection == null) return; |
| NoSuchMethodError: send | NMS 版本不匹配 | 確認 paperweight.paperDevBundle 版本與伺服器一致 |
| 封包無效果 | 玩家 tab 未處於 game 階段 | 確認玩家已完成登入(等 PlayerJoinEvent) |
| ClassCastException: CraftPlayer | 其他外掛替換 Player 實作 | 改用 player.getClass().getMethod("getHandle") 反射取得 |
development
透過 NMS Scoreboard/Objective/Team API 操作 sidebar、tablist 顯示名稱與計分板(Paper NMS + Mojang-mapped)/ Operate sidebar, tablist, and scoreboard via NMS Scoreboard/Objective/Team API
research
操作 GameProfile 進行 skin 注入,用於 NPC 外觀設定與假玩家實體(Paper NMS + Mojang-mapped)/ Manipulate GameProfile for skin injection used in NPC appearance and fake player entities
tools
透過 ClientboundLevelParticlesPacket 實現進階 NMS 粒子效果:客戶端專屬、大量粒子、自定義參數(Paper NMS + Mojang-mapped)/ Advanced NMS particle effects via ClientboundLevelParticlesPacket with per-client and bulk support
documentation
直接操作 CompoundTag 讀寫物品、實體、方塊實體的 NBT 資料(Paper NMS + Mojang-mapped)/ Read and write NBT data on items, entities, and block entities via CompoundTag