Skills/nms/nms-block-entity/SKILL.md
實作自定義 NMS BlockEntity(含 NBT 序列化、Tick 邏輯、客戶端同步),比 Bukkit BlockState 更靈活(Paper NMS + Mojang-mapped)/ Implement custom NMS BlockEntity with NBT serialization, tick logic, and client sync
npx skillsauth add MrPippi/MPS nms-block-entityInstall 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-block-entity
繼承 NMS BlockEntity 實作自定義方塊實體,實現 NBT 讀寫、伺服器端 Tick 邏輯(BlockEntityTicker)、以及透過封包同步狀態至客戶端。
| 參數 | 範例 | 說明 |
|------|------|------|
| package_name | com.example.block | 產出類別所在 package |
| entity_class_name | GeneratorBlockEntity | BlockEntity 子類名 |
| has_ticker | true | 是否需要 tick 邏輯 |
| base_block | CHEST | 用哪個 NMS 方塊作為載體 |
CustomBlockEntity.java — BlockEntity 實作(含 NBT load/save)CustomBlockEntityTicker.java(選)— ServerLevel tick 邏輯BlockEntityHelper.java — 在世界中取得/設定 BlockEntity 工具參見 Skills/paper-nms/PLATFORM.md。關鍵依賴:
dependencies {
paperweight.paperDevBundle('1.21.1-R0.1-SNAPSHOT')
}
CustomBlockEntity.javapackage com.example.block;
import net.minecraft.core.BlockPos;
import net.minecraft.core.HolderLookup;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.game.ClientGamePacketListener;
import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.block.entity.BlockEntityType;
import net.minecraft.world.level.block.state.BlockState;
import javax.annotation.Nullable;
@SuppressWarnings("UnstableApiUsage")
public class CustomBlockEntity extends BlockEntity {
// 自定義欄位
private int storedEnergy = 0;
private String ownerName = "";
public CustomBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
super(type, pos, state);
}
// ─── NBT 序列化 ──────────────────────────────────────────────────
/** 儲存自定義資料到 NBT(世界儲存 + 封包同步)。 */
@Override
protected void saveAdditional(CompoundTag tag, HolderLookup.Provider provider) {
super.saveAdditional(tag, provider);
tag.putInt("storedEnergy", storedEnergy);
tag.putString("ownerName", ownerName);
}
/** 從 NBT 讀取自定義資料(世界載入 + 封包接收)。 */
@Override
public void loadAdditional(CompoundTag tag, HolderLookup.Provider provider) {
super.loadAdditional(tag, provider);
storedEnergy = tag.getInt("storedEnergy");
ownerName = tag.getString("ownerName");
}
// ─── 客戶端同步 ──────────────────────────────────────────────────
/** 產生傳送給客戶端的更新封包(BlockEntityDataPacket)。 */
@Override
@Nullable
public Packet<ClientGamePacketListener> getUpdatePacket() {
return ClientboundBlockEntityDataPacket.create(this);
}
/** 取得用於封包同步的 NBT(可只傳必要欄位)。 */
@Override
public CompoundTag getUpdateTag(HolderLookup.Provider provider) {
return saveWithoutMetadata(provider);
}
/** 通知客戶端狀態變更(呼叫後自動發送更新封包)。 */
public void markDirtyAndSync() {
setChanged();
if (level != null && !level.isClientSide) {
level.sendBlockUpdated(worldPosition, getBlockState(), getBlockState(), 3);
}
}
// ─── Getter/Setter ───────────────────────────────────────────────
public int getStoredEnergy() { return storedEnergy; }
public void setStoredEnergy(int energy) {
this.storedEnergy = energy;
markDirtyAndSync();
}
public String getOwnerName() { return ownerName; }
public void setOwnerName(String name) {
this.ownerName = name;
markDirtyAndSync();
}
}
CustomBlockEntityTicker.java(Tick 邏輯)package com.example.block;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.entity.BlockEntityTicker;
import net.minecraft.world.level.block.state.BlockState;
@SuppressWarnings("UnstableApiUsage")
public class CustomBlockEntityTicker implements BlockEntityTicker<CustomBlockEntity> {
private static final int TICK_INTERVAL = 20; // 每秒執行一次
private int tickCount = 0;
@Override
public void tick(Level level, BlockPos pos, BlockState state, CustomBlockEntity entity) {
if (level.isClientSide) return; // 只在伺服器端執行
tickCount++;
if (tickCount % TICK_INTERVAL != 0) return;
// 每 20 tick(1 秒)執行一次邏輯
if (entity.getStoredEnergy() < 1000) {
entity.setStoredEnergy(entity.getStoredEnergy() + 10);
}
}
}
BlockEntityHelper.java(世界操作工具)package com.example.block;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.entity.BlockEntity;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_21_R1.CraftWorld;
import java.util.Optional;
@SuppressWarnings("UnstableApiUsage")
public final class BlockEntityHelper {
private BlockEntityHelper() {}
/** 取得指定位置的 BlockEntity(若類型不匹配回傳 empty)。 */
@SuppressWarnings("unchecked")
public static <T extends BlockEntity> Optional<T> get(
Location loc, Class<T> type) {
net.minecraft.world.level.Level level = ((CraftWorld) loc.getWorld()).getHandle();
BlockPos pos = new BlockPos(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
BlockEntity be = level.getBlockEntity(pos);
if (type.isInstance(be)) return Optional.of(type.cast(be));
return Optional.empty();
}
/** 取得指定位置的自定義 BlockEntity。 */
public static Optional<CustomBlockEntity> getCustom(Location loc) {
return get(loc, CustomBlockEntity.class);
}
}
src/main/java/com/example/
├── MyNmsPlugin.java
└── block/
├── CustomBlockEntity.java
├── CustomBlockEntityTicker.java
└── BlockEntityHelper.java
markDirtyAndSync())必須在主執行緒呼叫tick() 由 NMS 在主執行緒呼叫,內部不可進行阻塞 IOlevel.isClientSide 必須在 tick 內檢查,防止在客戶端 Tick 執行伺服器邏輯Skills/_shared/nms-threading.md| 錯誤 | 原因 | 解法 |
|------|------|------|
| NBT 資料丟失 | saveAdditional 未呼叫 super | 確保呼叫 super.saveAdditional(tag, provider) |
| 客戶端不同步 | 未呼叫 markDirtyAndSync() | 每次修改欄位後呼叫 |
| getBlockEntity() 回傳 null | 方塊位置無 BlockEntity | 確認方塊類型有 BlockEntity 支援 |
| tick 未執行 | BlockEntityType 未正確注冊 | Paper plugin 環境需透過 RegistryAccess 注冊類型 |
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