This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Maven project, Java 21. Default goal is clean package.
- Build the addon jar:
mvn clean package(output intarget/, namedStrangerRealms-${build.version}-SNAPSHOT-LOCAL.jarlocally) - Run all tests:
mvn test - Run a single test class:
mvn test -Dtest=StrangerRealmsTest - Run a single test method:
mvn test -Dtest=StrangerRealmsTest#methodName - Skip tests during a build:
mvn package -DskipTests
Surefire is preconfigured with the --add-opens flags required by MockBukkit on JDK 21 — don't run tests outside Maven without replicating them. JaCoCo runs on the test phase and writes an XML report to target/site/jacoco/.
Versioning is driven by Maven properties: build.version (in pom.xml) sets the release version, and the master / ci profiles flip -SNAPSHOT and -bNNN build numbers automatically from Jenkins env vars. Bump build.version in pom.xml to cut a new release. CI runs on Jenkins at codemc.org (see pom.xml <ciManagement>) and on GitHub Actions (.github/workflows/build.yml).
StrangerRealms targets Paper 1.21.11 / BentoBox 3.10.0-SNAPSHOT. The mock-bukkit snapshot tracks the latest Paper API, so bumping Paper here when MockBukkit moves is part of routine maintenance.
StrangerRealms is a BentoBox GameModeAddon — it does not run as a standalone Bukkit/Paper plugin. The runtime entry point Paper sees is StrangerRealmsPladdon (a Pladdon), which constructs and returns the real addon, StrangerRealms. plugin.yml points Paper at the Pladdon; addon.yml points BentoBox at StrangerRealms. When changing entry points or lifecycle, both files matter.
BentoBox calls these in order — keep work in the right phase:
onLoad()— loadSettingsfromconfig.yml, construct theNetherChunkMaker(it's needed during world creation), and register player/admin command trees (ClaimCommand,UnclaimCommand,SpawnCommand,WorldBorderCommand).createWorlds()— creates the overworld, nether (Upside Down), and end viaWorldCreator. The nether uses the customNetherChunksChunkGenerator +NetherBiomeProvider. IfBukkit.getWorld(worldName + "_nether")is missing at this point the chunks database is cleared — i.e., deleting the nether world folder forces full regeneration.onEnable()— registers listeners (PlayerListener,NetherChunkMaker,TeamListener,NetherRedstoneListener), registers the Warped Compass recipe, and ensures a spawnIslandexists.allLoaded()— persists settings.
Settings implements BentoBox's WorldSettings, so it doubles as the world configuration object returned from getWorldSettings(). It is serialized via @ConfigEntry annotations and stored at addons/StrangerRealms/config.yml. Adding a new option means adding a field + annotations here, not editing config.yml directly (the file is regenerated from the class).
In user-facing strings and commands the unit of ownership is a claim, but in code (and in BentoBox APIs) it is an Island. They are the same thing — ClaimCommand reuses BentoBox's island-create flow and even pulls locale keys from the core "island" namespace. Don't try to rename Island to Claim in code; treat them as synonyms.
The nether is a darkened, distressed mirror of the Overworld, generated lazily:
NetherChunks(ChunkGenerator) lays down the base terrain — a sculk-laced deep-dark floor belowNETHER_FLOOR. Above that, the chunk is mostly air.NetherBiomeProviderassigns biomes;NetherChunkMaker.BIOME_MAPPINGmaps overworld biomes → crimson/warped/basalt variants.NetherChunkMaker(Listener) is the interesting piece: onChunkLoadEventin the nether world, it copies the corresponding overworld chunk's blocks into the nether chunk (with attrition / corruption controlled bySettings.attrition). The set of chunks it has already processed is persisted viaNetherChunksMade(a BentoBoxDataObjecttable namedStrangerChunks) so generation only happens once per chunk.- The Warped Compass (recipe registered in
StrangerRealms#registerWarpedCompassRecipe) is consumed during nether portal travel to force re-generation of surrounding chunks — that is, to clear them fromNetherChunksMadeso they get rebuilt from the current overworld state. NetherRedstoneListeneris the "Glimmer": with probabilitySettings.redstoneChance, button/lever interactions in the Upside Down replay at the same coordinates in the overworld.
StrangerRealms ships its own world-border implementation and conflicts with BentoBox's Border addon — onEnable() warns if Border is loaded. There are two strategies behind the BorderShower interface:
ShowBarrier— custom client-side barrier-block visualisation.ShowWorldBorder— uses the vanilla world-border packet.
PerPlayerBorderProxy is the entry point used by the rest of the addon; it currently fans calls out to both strategies (so each player sees both). Per-player border type selection (BorderType enum) is the intended extension point if you need to vary by player.
The world border size is dynamic: StrangerRealms#getBorderSize() returns barrierIncreaseBlocks × onlinePlayers unless manualBorderSize is set. Shrinks are animated gradually via a Bukkit scheduled task at barrierReductionSpeed seconds per block — there is at most one such task; calling getBorderSize() again replaces it.
src/main/resources/config.yml,addon.yml,plugin.yml— filtered (Maven replaces${...}placeholders).src/main/resources/locales/*.yml→./locales/— not filtered. ~22 translations live here.src/main/resources/blueprints/*.{blu,json}→./blueprints/— BentoBox blueprint bundle for new claim layouts.src/main/resources/structures/*.nbt→./structures/— directory may not exist yet; the resource entry is preconfigured.
Tests use JUnit 5 + Mockito + MockBukkit. Two reusable base classes live in src/test/java/world/bentobox/stranger/:
CommonTestSetup— sets upMockBukkit.mock(), a staticBukkitmock, a mockedBentoBoxplugin (injected viaWhiteBoxreflection intoBentoBox.instance), and a mockedIslandsManager/IslandWorldManagerwith one default island. Extend this for most listener/command tests.RanksManagerTestSetup— adds rank-manager setup on top ofCommonTestSetup.WhiteBox— utility for setting private static fields (e.g., the BentoBox singleton).TestWorldSettings— minimalWorldSettingsreturned fromiwm.getWorldSettings().
Always call super.setUp() / super.tearDown() from subclasses — the base class is responsible for closing the static Bukkit mock and calling MockBukkit.unmock(), and forgetting either causes test interference and leaked database directories.
StrangerRealmsTest exercises full addon loading; it builds a temporary addon.jar from src/main/resources/config.yml and feeds it through BentoBox's addon manager. Use it as the reference pattern when you need to test addon-level wiring rather than a single class.
- Do not install BentoBox's
Borderaddon alongside this — they fight.InvSwitcheris recommended (warned about on enable if missing). Real users typically also runWarps. - Spigot NMS is a required repository (
<repository id="nms-repo">) — it's used for world regeneration. Don't remove it frompom.xml.