This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Boxed is a BentoBox GameModeAddon for Minecraft (Paper) where each player is confined to a small expandable box. Completing Minecraft advancements grows the box. Built against bentobox 3.13.0, Paper API 1.21.11, Java 21.
- Build (default goal is
clean package):mvn clean package - Run all tests:
mvn test - Run a single test class:
mvn test -Dtest=AdvancementsManagerTest - Run a single test method:
mvn test -Dtest=AdvancementsManagerTest#testMethodName - Jacoco coverage report is generated as part of the build (
target/site/jacoco/).
The build.version property in pom.xml is the canonical version. The ci and master Maven profiles activate from Jenkins env vars (BUILD_NUMBER, GIT_BRANCH=origin/master) to compute the final artifact name; don't hand-edit revision.
Surefire is configured with a long list of --add-opens JVM args required by Mockito's inline mock maker on Java 21+ — if you add new tests that touch JDK internals, extend that argLine in pom.xml rather than fighting module access errors locally. The maven-compiler-plugin runs with <fork>true</fork> to work around a JDK 25 in-process javac NPE (this.hashes is null).
Boxed is a BentoBox addon, not a standalone plugin. Boxed extends GameModeAddon and is loaded by BentoBox at runtime. BoxedPladdon is the Paper plugin-loader shim so Paper recognises the jar.
This is the core concept and touches almost everything:
- Seed world (
<worldname>/seed, and/seed_nether): a real vanilla-ish world generated once usingBoxedSeedChunkGenerator+SeedBiomeGenerator/NetherSeedBiomeGenerator. It's where Minecraft's normal terrain + structures (villages, shipwrecks, fortresses, etc.) actually get generated by the server. - Game world (
<worldname>,<worldname>_nether): the world players actually play in. It usesBoxedChunkGenerator(a subclass ofAbstractBoxedChunkGenerator) which does not generate terrain from scratch — instead,Boxed.copyChunks(...)pre-reads every chunk insideislandDistancefrom the seed world duringcreateWorlds()and stores them in the chunk generator. When the game world asks for a chunk, the generator serves back the pre-captured copy.
This is why first boot is extremely slow and RAM-hungry (see README.md warnings): the entire seed region is force-loaded up front. Any change to world generation, structure handling, or world naming must respect both worlds and the copy step in Boxed.copyChunks() / createOverWorld() / createNether(). The generatorMaps / generatorMap fields in Boxed.java route world names → generators for getDefaultWorldGenerator (used by Multiverse and similar world-management plugins) and for the hook in allLoaded() that calls WorldManagementHook.registerWorld.
isUsesNewChunkGeneration() returns true, which tells BentoBox this addon uses the modern chunk-generation API.
AdvancementsManager is the other key subsystem. Box growth is data-driven from advancements.yml: each advancement key maps to an integer "box growth" increment. AdvancementListener watches for player advancement events and asks the manager to update the island's protection-range. Per-island state lives in objects/IslandAdvancements.java (a BentoBox DataObject persisted via its database layer). AdvancementsManager.save() is called in onDisable() — any new cached state it holds should be flushed there too.
Because advancements are per-world in vanilla but Boxed runs multiple worlds on one server, the InvSwitcher addon is required at runtime to keep advancements separate between worlds. The code logs a warning if InvSwitcher/Border aren't installed but does not hard-fail.
NewAreaListener plus objects/IslandStructures.java, BoxedJigsawBlock, BoxedStructureBlock, and ToBePlacedStructures handle placing/tracking vanilla structures inside player boxes. AdminPlaceStructureCommand is the admin-side hook for manual placement. If you're adding structure logic, both the "captured from seed world" pathway and the "placed into player box" pathway need to stay in sync.
Boxed.MOVE_BOX (protection flag, owner-only by default) and Boxed.ALLOW_MOVE_BOX (world setting) gate the enderpearl-box-teleport feature implemented in EnderPearlListener. They are scoped to this game mode only via setGameModes(...) in onEnable(), and MOVE_BOX is conditionally registered/unregistered depending on ALLOW_MOVE_BOX — keep that conditional registration intact if you touch flag setup.
pom.xml filters src/main/resources but copies structures/*.nbt, locales/*.yml, and blueprints/*.blu|*.json unfiltered into the jar at specific targetPaths. New resource types need a matching <resource> block or they won't ship.
Tests use JUnit 5 (Jupiter) + Mockito 5 mockStatic + MockBukkit (v1.21-SNAPSHOT via jitpack.io). There is no PowerMock and no custom ServerMocks helper. All test classes extend CommonTestSetup, which:
- Calls
MockBukkit.mock()and registersMockito.mockStatic(Bukkit.class, RETURNS_DEEP_STUBS)— use the inheritedmockedBukkitfield to stubBukkit.*calls rather than creating your own. - Exposes ready-made
@Mockfields (plugin,mockPlayer,world,location,iwm,im,island,pim,itemFactory,inv,notifier,fm,spigot,hooksManager,bm,sch,lm,phm) plus the MockBukkitserverandmockedUtilstatics. Don't re-declare these in subclasses. - Forces
org.bukkit.Tag.LEAVESto initialise before the static Bukkit mock is installed — if you touch tag-related code and see stale deep-stubs across tests, broaden that list inCommonTestSetup.
A small WhiteBox helper (reflection-based private static field setter) replaces PowerMock's Whitebox.setInternalState. Any test that needs its own MockedStatic<...> (e.g. DatabaseSetup, User) must create it after super.setUp() and close it via closeOnDemand() before super.tearDown() — the parent's Mockito.framework().clearInlineMocks() in tearDown will otherwise corrupt the local static.
Jacoco excludes org/bukkit/Material* to avoid "class too large to mock" failures; keep that exclusion if you rearrange the build section.