Skills/nms/nms-version-adapter/SKILL.md
多版本 NMS 相容性 Adapter 模式:抽象介面 + 版本特定實作 + runtime dispatch,讓同一 plugin 支援多個 MC 版本 / Multi-version NMS compatibility adapter pattern
npx skillsauth add MrPippi/MPS nms-version-adapterInstall 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-version-adapter
建立抽象 Adapter 介面定義共通 NMS 操作,再為每個支援的 MC 版本提供具體實作,runtime 時根據伺服器版本自動選擇正確 adapter。適合需支援多版本發佈的商業/大型 plugin。
nms-reflection-bridge 或 multi-module Gradle build| 參數 | 範例 | 說明 |
|------|------|------|
| package_name | com.example.nms | 產出類別所在 package |
| adapter_interface | NmsAdapter | 抽象介面名稱 |
| supported_versions | 1.21, 1.21.1, 1.21.3 | 需支援的 MC 版本列表 |
NmsAdapter.java — 共通抽象介面AdapterRegistry.java — 版本偵測與 adapter 選擇器V1_21_Adapter.java — Paper 1.21/1.21.1 實作V1_21_3_Adapter.java — Paper 1.21.3 實作NmsVersion.java — 版本列舉建議採用 multi-module Gradle build,每個版本各自 module 編譯:
my-plugin/
├── build.gradle
├── core/ # 版本無關邏輯 + NmsAdapter 介面
├── adapter-v1_21/ # 使用 paperweight 1.21 編譯
├── adapter-v1_21_3/ # 使用 paperweight 1.21.3 編譯
└── plugin/ # 整合所有 adapter 並打包
NmsVersion.javapackage com.example.nms;
import org.bukkit.Bukkit;
public enum NmsVersion {
V1_21,
V1_21_1,
V1_21_3,
UNSUPPORTED;
public static NmsVersion detect() {
String version = Bukkit.getMinecraftVersion(); // e.g. "1.21.1"
return switch (version) {
case "1.21" -> V1_21;
case "1.21.1" -> V1_21_1;
case "1.21.3" -> V1_21_3;
default -> UNSUPPORTED;
};
}
}
NmsAdapter.javapackage com.example.nms;
import org.bukkit.entity.Player;
import org.bukkit.Location;
import net.kyori.adventure.text.Component;
/**
* 跨版本 NMS 操作介面。所有方法必須對所有支援版本給出等效結果。
*/
public interface NmsAdapter {
/** 回傳此 adapter 支援的 MC 版本。 */
NmsVersion version();
/** 發送 action bar 文字。 */
void sendActionBar(Player player, Component message);
/** 取得玩家網路延遲(ms)。 */
int getLatency(Player player);
/** 在指定座標生成粒子(純客戶端,不觸發 event)。 */
void spawnParticleClient(Location loc, String particleKey, int count);
/** 直接 tick 實體(用於強制更新)。 */
void forceTickEntity(org.bukkit.entity.Entity entity);
}
AdapterRegistry.javapackage com.example.nms;
import java.util.EnumMap;
import java.util.Map;
public final class AdapterRegistry {
private static final Map<NmsVersion, NmsAdapter> ADAPTERS = new EnumMap<>(NmsVersion.class);
private static NmsAdapter active;
private AdapterRegistry() {}
public static void register(NmsAdapter adapter) {
ADAPTERS.put(adapter.version(), adapter);
}
/** 在 plugin onEnable 呼叫,自動選擇當前版本的 adapter。 */
public static void initialize() {
NmsVersion detected = NmsVersion.detect();
if (detected == NmsVersion.UNSUPPORTED) {
throw new IllegalStateException(
"Unsupported MC version: " + org.bukkit.Bukkit.getMinecraftVersion());
}
active = ADAPTERS.get(detected);
if (active == null) {
throw new IllegalStateException(
"No adapter registered for version: " + detected);
}
}
/** 取得當前執行環境的 adapter。 */
public static NmsAdapter get() {
if (active == null) throw new IllegalStateException("AdapterRegistry not initialized");
return active;
}
}
V1_21_Adapter.java(範例實作)package com.example.nms.v1_21;
import com.example.nms.NmsAdapter;
import com.example.nms.NmsVersion;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minecraft.network.protocol.game.ClientboundSetActionBarTextPacket;
import net.minecraft.server.level.ServerPlayer;
import org.bukkit.craftbukkit.v1_21_R1.entity.CraftPlayer;
import org.bukkit.entity.Player;
import org.bukkit.Location;
@SuppressWarnings("UnstableApiUsage")
public class V1_21_Adapter implements NmsAdapter {
@Override
public NmsVersion version() {
return NmsVersion.V1_21_1;
}
@Override
public void sendActionBar(Player player, Component message) {
String json = GsonComponentSerializer.gson().serialize(message);
net.minecraft.network.chat.Component nmsComp =
net.minecraft.network.chat.Component.Serializer.fromJson(
json, net.minecraft.core.RegistryAccess.EMPTY);
ClientboundSetActionBarTextPacket packet = new ClientboundSetActionBarTextPacket(nmsComp);
ServerPlayer nms = ((CraftPlayer) player).getHandle();
if (nms.connection != null) nms.connection.send(packet);
}
@Override
public int getLatency(Player player) {
return ((CraftPlayer) player).getHandle().latency;
}
@Override
public void spawnParticleClient(Location loc, String particleKey, int count) {
// 1.21 版本實作:使用 ClientboundLevelParticlesPacket
// ...
}
@Override
public void forceTickEntity(org.bukkit.entity.Entity entity) {
// 1.21 版本實作
// ...
}
}
MyNmsPlugin.onEnable)@Override
public void onEnable() {
AdapterRegistry.register(new V1_21_Adapter());
AdapterRegistry.register(new V1_21_3_Adapter());
AdapterRegistry.initialize();
getLogger().info("Using NMS adapter: " + AdapterRegistry.get().version());
}
// 版本無關的業務邏輯
public void sendWelcome(Player player) {
AdapterRegistry.get().sendActionBar(player, Component.text("歡迎!"));
}
src/main/java/com/example/
├── MyNmsPlugin.java
└── nms/
├── NmsAdapter.java
├── NmsVersion.java
├── AdapterRegistry.java
└── v1_21/
└── V1_21_Adapter.java
└── v1_21_3/
└── V1_21_3_Adapter.java
AdapterRegistry 單次初始化後為 immutable,讀取執行緒安全_shared/nms-threading.md)AdapterRegistry.register() 只應在 onEnable() 執行一次,避免 race condition| 錯誤 | 原因 | 解法 |
|------|------|------|
| IllegalStateException: Unsupported MC version | 在未支援版本上啟動 | 在 NmsVersion.detect() 加 fallback 分支(嘗試最接近的版本) |
| ClassNotFoundException: v1_21_R1 | 發布版不含該版本的 CraftBukkit 類別 | 在 register() 外層用 try-catch,若失敗則不註冊該 adapter |
| AbstractMethodError | adapter 介面新增方法但舊 adapter 未實作 | 為介面方法加 default 實作 |
| Multi-module 打包遺漏 | shadowJar 未包含 adapter module | 在 plugin/build.gradle 加 shadow project(':adapter-v1_21') |
| 不同版本 NMS 簽名差異 | 1.21.1 方法移除或改名 | 用 reflection 在 adapter 內做版本分支(結合 nms-reflection-bridge) |
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