feat: add Nukkit platform support - #3474
Conversation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…gns in NukkitPlayer Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
AFAIK translation is handled through Crowdin |
Removed, thanks for the reminder. |
|
There's quite a lot of stuff that feels overly tied to specifically nukkitmot, I'm still not convinced this is necessarily the most widely-used fork looking at the repo and comparing to (e.g. powernukkitx). There's also quite a lot of "lazy" coding, e.g. constant calls to server#getChunk rather than caching (and perhaps there are more appropriate methods to get the chunk anyway?!). Another example is the tree generation - this won't work with history and only works with three tree types. If it's an issue of no other trees supported by the server implementation then nukkit-mot wouldn't be mature enough to have an FAWE implementation yet (I also see that there is not actually a version, just Lastly on my quick look-through - we do not need to use the sk89q.worldedit classpath - and some of the choices of what to put here vs fastasyncworldedit.nukkit are a bit strange. Ultimately it falls on us to maintain the inclusion of nukkit going into the future, so we would generally want to be able to have more confidence/want to be sure that all features not yet implemented throw appropriate errors, and are clearly marked/labelled (with some explanation why) and preferably javadocs at the top of every "core" class (e.g. adapter, getblocks, etc.) describing why certain decisions have been made that offer quite a different architectural/"workflow" implementation (i.e. where we don't cache chunk, have no real concept of chunk sections, etc.) to other FAWE implementations. I do appreciate a differentiation between mot and Nkx implementations though, and this does help to address to nukkit-mot vs powernukkitx differences, but then not having unique getblocks classes and instead offloading to the adapter (with a different design pattern ( |
Thank you for your review suggestions, and I apologize for not checking GitHub over the past few days and not being able to reply in time. |
…ackage cleanup, explicit errors, and architecture docs
- Migrate platform classes from com.sk89q.worldedit.nukkit to com.fastasyncworldedit.nukkit and update plugin.yml main class - Rename adapters to consistent convention: MotNukkitAdapter -> NukkitMOTAdapter, NkxNukkitAdapter -> NukkitAdapter (nkx fork) - Add adapter-pnx module with PowerNukkitXAdapter; register it in settings.gradle.kts - Add NukkitTreeTypes util and BlockEntityIdMapping helper for JE tile entity ids when pasting schematics - Thread-safety: synchronize NukkitAdapter world/player caches - Update NukkitWorld, NukkitGetBlocks, NukkitRelighter, mappings and listeners; harden worldedit-libs build against auto-discovered subprojects without a build task - Add Nukkit test suite (platform compat, tree generation, get-blocks behavior, adapter detection) plus Nukkit API test stubs - Add CLAUDE.md to .gitignore as a local-only AI guidance file
Nukkit's getCapabilities() omitted Capability.WORLDEDIT_CUI, unlike Bukkit/Forge/Sponge/Fabric. PlatformManager.createProxyActor calls queryCapability(WORLDEDIT_CUI) on every block interaction, which threw NoCapablePlatformException and broke WorldEdit selection tools (block left-click/break events failed to dispatch).
NukkitGetBlocks.call() previously performed block-entity close/create, entity spawn/remove and chunk resend on the FAWE async worker thread. Nukkit-MOT's chunk, block-entity registry and entity system are main-thread-only, so running these off-thread could corrupt chunk state or deadlock the server, surfacing only as a clean disable sequence (the onDisable log lines) with no preceding error. Mirror the Bukkit adapter's split: keep raw block/biome array writes on the async thread, but route tile lifecycle, entity spawn/remove and chunk resend through handleCallFinalizer, which dispatches them to the main thread via QueueHandler.sync(...) when off-thread and runs them inline when already on the main thread (or in unit tests, which have no FAWE instance). The fast path for pure block edits (no tiles/entities) is unchanged: callback and finalizer run inline and a completed future is returned. The finalizer is owned either by handleCallFinalizer (dispatch paths) or the caller's finally (early-return/exception paths), guarded by a finalizerOwned flag to avoid double execution.
NukkitGetBlocks.update(int layer, ...) computed `int baseY = layer << 4` from its `layer` argument, but CharBlocks.Section.update passes a normalized array index (absoluteLayer - minSectionPosition), not an absolute section position. The resulting baseY was off by |minSectionPosition| * 16 blocks whenever minSectionPosition != 0. For PNX/MOT overworld (minY=-64, minSectionPosition=-4) the off-by-one range reads escaped the chunk's sections[] array and surfaced as `cn.nukkit.utils.ChunkException: Invalid section N` thrown from HeightmapProcessor -> CharBlocks.load -> update -> FullChunk.getSection. NKX overworld (minY=0) was unaffected because the normalized index coincides with the absolute section position there, which is why the existing unit tests (all minY=0) never caught it. Recompute baseY as `(layer + getMinSectionPosition()) << 4` to convert the normalized index back to absolute world Y before forwarding it to FullChunk.getFullBlock. The minY==0 path is unchanged. Add NukkitGetBlocksBehaviorTest.loadUsesAbsoluteWorldYForNegativeMinSectionPosition covering the minY=-64 / maxY=319 layout: load(absoluteSection=0) must read world y=0..15 and never request the buggy y=64..79 range.
beToJe(Item) routed air through the BE_TO_JE lookup table, which never contains air because createItemData intentionally rejects null items (air is a null item on every fork). The late air-rescue branch was dead code: it re-called the same createItemData, which still rejected air, so holding nothing threw UnsupportedOperationException on every PlayerItemHeldEvent. - Short-circuit air at the top of beToJe(Item): it is an identical JE/BE constant and has no business in the lookup table. Covers MOT and NKX, which share this static method. - Drop the dead air-rescue branch. - Compute the lookup key via safeItemKey so an unstable platform key lookup (e.g. MOT getNamespaceId over runtime-unregistered items) degrades to a stable id:meta key instead of masking the unmapped-item diagnostic with a raw exception. - Add tests: air short-circuits to minecraft:air; beToJe survives an unstable key lookup and still throws UnsupportedOperationException.
BrushTool implements TraceTool, not BlockTool/DoubleActionBlockTool, so handleBlockLeftClick (HIT) and handleBlockRightClick (OPEN) do not route to it. Bukkit relies on the || fallback to handleArmSwing/handleRightClick (PRIMARY/SECONDARY input events) where brushes actually fire; Nukkit was missing this fallback, so left/right-clicking a block with a brush did nothing (only aiming at air worked). - LEFT_CLICK_BLOCK/RIGHT_CLICK_BLOCK: add || handleArmSwing/handleRightClick fallback, matching Bukkit WorldEditListener - onBlockBreak: add the same fallback for creative/server-authoritative breaking where only BlockBreakEvent fires - Preserve the handledLeftClick cache so BlockBreakEvent does not double- fire the brush after PlayerInteractEvent - Add InteractionDebouncer to dedupe same-tick duplicate interactions, aligning with Bukkit
Overview
add Nukkit platform support
Description
Added support for the Nukkit platform, and is also compatible with the Nukkit-MOT branch.