nms-chunk-access

star 1

透過 NMS LevelChunk 直接讀寫方塊、高度圖、ChunkSection 資料,比 Bukkit Chunk API 更快更底層(Paper NMS + Mojang-mapped)/ Direct LevelChunk block, heightmap, and ChunkSection access for high-performance operations

MrPippi By MrPippi schedule Updated 5/1/2026

name: nms-chunk-access description: "透過 NMS LevelChunk 直接讀寫方塊、高度圖、ChunkSection 資料,比 Bukkit Chunk API 更快更底層(Paper NMS + Mojang-mapped)/ Direct LevelChunk block, heightmap, and ChunkSection access for high-performance operations"

NMS Chunk Access / NMS 區塊直接存取

技能名稱 / Skill Name

nms-chunk-access

目的 / Purpose

透過 NMS LevelChunkChunkAccessLevelChunkSection 直接讀寫方塊狀態與高度圖,繞過 Bukkit Chunk.getBlock() 的逐格開銷,實現高效能的大範圍方塊操作(如結構生成、地圖掃描)。

NMS 版本需求 / NMS Version Requirements

  • Paper 1.21 – 1.21.3
  • Paperweight userdev 1.7.2+
  • Mojang mappings(已由 Paper 1.20.5+ 原生支援)

觸發條件 / Triggers

  • 「chunk access」「LevelChunk」「區塊操作」「chunk data」「直接讀寫方塊」
  • 「ChunkSection」「heightmap」「高度圖」「chunk NMS」「bulk block」
  • 「大量方塊」「高效能方塊操作」「structure paste」

輸入參數 / Inputs

參數 範例 說明
package_name com.example.world 產出類別所在 package
class_name ChunkAccessUtil 工具類名稱

輸出產物 / Outputs

  • ChunkAccessUtil.java — LevelChunk 讀寫工具
  • BulkBlockEditor.java — 批次方塊操作(最小化客戶端更新)

Paperweight 建置設定 / Build Setup

參見 Skills/paper-nms/PLATFORM.md。關鍵依賴:

dependencies {
    paperweight.paperDevBundle('1.21.1-R0.1-SNAPSHOT')
}

代碼範本 / Code Template

ChunkAccessUtil.java

package com.example.world;

import net.minecraft.core.BlockPos;
import net.minecraft.core.SectionPos;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.world.level.levelgen.Heightmap;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_21_R1.CraftChunk;
import org.bukkit.craftbukkit.v1_21_R1.CraftWorld;

@SuppressWarnings("UnstableApiUsage")
public final class ChunkAccessUtil {

    private ChunkAccessUtil() {}

    /** 取得 NMS LevelChunk。 */
    public static LevelChunk getChunk(Chunk chunk) {
        return ((CraftChunk) chunk).getHandle(net.minecraft.world.level.chunk.status.ChunkStatus.FULL);
    }

    /** 取得指定世界座標的 NMS BlockState(不觸發光照更新)。 */
    public static BlockState getBlockState(Location loc) {
        net.minecraft.world.level.Level level = ((CraftWorld) loc.getWorld()).getHandle();
        BlockPos pos = new BlockPos(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
        return level.getChunk(pos).getBlockState(pos);
    }

    /**
     * 直接設定方塊狀態(跳過部分 Bukkit 處理,效能較高)。
     * flags: 1=update neighbors, 2=send to clients, 3=both
     * 必須在主執行緒呼叫。
     */
    public static void setBlockState(Location loc, BlockState state, int flags) {
        net.minecraft.world.level.Level level = ((CraftWorld) loc.getWorld()).getHandle();
        BlockPos pos = new BlockPos(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
        level.setBlock(pos, state, flags);
    }

    /** 取得指定 X/Z 位置的地面高度(WORLD_SURFACE 高度圖)。 */
    public static int getSurfaceHeight(World world, int x, int z) {
        net.minecraft.world.level.Level level = ((CraftWorld) world).getHandle();
        BlockPos pos = new BlockPos(x, 0, z);
        LevelChunk chunk = level.getChunkAt(pos);
        return chunk.getHeight(Heightmap.Types.WORLD_SURFACE, x & 15, z & 15);
    }

    /** 取得指定 X/Z 位置的動態高度(MOTION_BLOCKING,含水)。 */
    public static int getMotionBlockingHeight(World world, int x, int z) {
        net.minecraft.world.level.Level level = ((CraftWorld) world).getHandle();
        BlockPos pos = new BlockPos(x, 0, z);
        LevelChunk chunk = level.getChunkAt(pos);
        return chunk.getHeight(Heightmap.Types.MOTION_BLOCKING, x & 15, z & 15);
    }

    /** 取得 ChunkSection(16 格高的子區段),sectionY 為 section index(非方塊 Y)。 */
    public static LevelChunkSection getSection(Chunk chunk, int sectionY) {
        LevelChunk nms = getChunk(chunk);
        return nms.getSections()[sectionY - nms.getMinSection()];
    }

    /** 讀取 ChunkSection 中的方塊狀態(localX/Y/Z 為 0-15 相對座標)。 */
    public static BlockState getSectionBlockState(
            LevelChunkSection section, int localX, int localY, int localZ) {
        return section.getBlockState(localX, localY, localZ);
    }

    /** 取得指定位置的光照等級(SKY 或 BLOCK)。 */
    public static int getLightLevel(Location loc, LightLayer layer) {
        net.minecraft.world.level.Level level = ((CraftWorld) loc.getWorld()).getHandle();
        BlockPos pos = new BlockPos(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
        return level.getBrightness(layer, pos);
    }
}

BulkBlockEditor.java(批次方塊操作)

package com.example.world;

import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.LevelChunk;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_21_R1.CraftWorld;

import java.util.HashMap;
import java.util.Map;

/**
 * 批次方塊操作工具。
 * 收集所有修改後一次性提交,減少客戶端更新封包數量。
 * 必須在主執行緒使用。
 */
@SuppressWarnings("UnstableApiUsage")
public class BulkBlockEditor {

    private final net.minecraft.world.level.Level level;
    private final Map<BlockPos, BlockState> pending = new HashMap<>();

    public BulkBlockEditor(World world) {
        this.level = ((CraftWorld) world).getHandle();
    }

    /** 排隊一個方塊修改。 */
    public BulkBlockEditor set(int x, int y, int z, BlockState state) {
        pending.put(new BlockPos(x, y, z), state);
        return this;
    }

    /** 排隊填充一個立方體範圍(minX..maxX, minY..maxY, minZ..maxZ)。 */
    public BulkBlockEditor fill(int x1, int y1, int z1, int x2, int y2, int z2, BlockState state) {
        for (int x = x1; x <= x2; x++)
            for (int y = y1; y <= y2; y++)
                for (int z = z1; z <= z2; z++)
                    pending.put(new BlockPos(x, y, z), state);
        return this;
    }

    /**
     * 提交所有修改。
     * flags=2 只發送封包給客戶端,不觸發鄰居更新(效能最高)。
     * flags=3 觸發鄰居更新 + 發送封包(物理效果正確但較慢)。
     */
    public void commit(int flags) {
        for (Map.Entry<BlockPos, BlockState> entry : pending.entrySet()) {
            level.setBlock(entry.getKey(), entry.getValue(), flags);
        }
        pending.clear();
    }

    /** 清除所有排隊的修改。 */
    public void clear() {
        pending.clear();
    }

    public int pendingCount() {
        return pending.size();
    }
}

推薦目錄結構 / Recommended Directory Structure

src/main/java/com/example/
├── MyNmsPlugin.java
└── world/
    ├── ChunkAccessUtil.java
    └── BulkBlockEditor.java

執行緒安全注意事項 / Thread Safety

  • ⚠️ 所有 Chunk/Block 讀寫操作必須在主執行緒呼叫
  • ⚠️ BulkBlockEditor.commit() 亦需在主執行緒呼叫
  • ✅ 唯讀操作(getBlockStategetHeight)在不修改世界的前提下可在 async 讀取,但 Paper 不保證一致性
  • ⚠️ 批次大量方塊修改可能造成 TPS 下降,建議每 tick 分批處理(每 tick ≤ 500 格)
  • 詳見 Skills/_shared/nms-threading.md

失敗回退 / Fallback

錯誤 原因 解法
getChunk() 回傳未載入狀態 區塊未完全生成 呼叫前先 world.loadChunk(cx, cz) 確保載入
ArrayIndexOutOfBoundsException sectionY 超出範圍 確認 sectionY 在 chunk.getMinSection()getMaxSection() 之間
方塊更新後客戶端無反應 flags=0 或未呼叫 commit() 改用 flags=2(SEND_TO_CLIENTS
大量更新造成伺服器卡頓 單 tick 修改過多方塊 分批執行(Bukkit scheduler runTaskTimer)
Install via CLI
npx skills add https://github.com/MrPippi/MJP-Claude-Skills --skill nms-chunk-access
Repository Details
star Stars 1
call_split Forks 0
navigation Branch main
article Path SKILL.md
More from Creator