Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build & test

Maven project, Java 21. Default goal is `clean package`.

- Build the addon jar: `mvn clean package` (output in `target/`, named `StrangerRealms-${build.version}-SNAPSHOT-LOCAL.jar` locally)
- 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`).

## Architecture

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.

### Addon lifecycle (StrangerRealms.java)

BentoBox calls these in order — keep work in the right phase:

1. `onLoad()` — load `Settings` from `config.yml`, construct the `NetherChunkMaker` (it's needed during world creation), and register player/admin command trees (`ClaimCommand`, `UnclaimCommand`, `SpawnCommand`, `WorldBorderCommand`).
2. `createWorlds()` — creates the overworld, nether (Upside Down), and end via `WorldCreator`. The nether uses the custom `NetherChunks` ChunkGenerator + `NetherBiomeProvider`. If `Bukkit.getWorld(worldName + "_nether")` is missing at this point the chunks database is cleared — i.e., deleting the nether world folder forces full regeneration.
3. `onEnable()` — registers listeners (`PlayerListener`, `NetherChunkMaker`, `TeamListener`, `NetherRedstoneListener`), registers the Warped Compass recipe, and ensures a spawn `Island` exists.
4. `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).

### Domain vocabulary

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.

### Upside Down generation (generator/)

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 below `NETHER_FLOOR`. Above that, the chunk is mostly air.
- `NetherBiomeProvider` assigns biomes; `NetherChunkMaker.BIOME_MAPPING` maps overworld biomes → crimson/warped/basalt variants.
- `NetherChunkMaker` (Listener) is the interesting piece: on `ChunkLoadEvent` in the nether world, it copies the corresponding overworld chunk's blocks into the nether chunk (with attrition / corruption controlled by `Settings.attrition`). The set of chunks it has already processed is persisted via `NetherChunksMade` (a BentoBox `DataObject` table named `StrangerChunks`) 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 from `NetherChunksMade` so they get rebuilt from the current overworld state.
- `NetherRedstoneListener` is the "Glimmer": with probability `Settings.redstoneChance`, button/lever interactions in the Upside Down replay at the same coordinates in the overworld.

### Border system (border/)

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.

### Resources packaged into the jar (pom.xml `<resources>`)

- `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.

## Testing patterns

Tests use JUnit 5 + Mockito + MockBukkit. Two reusable base classes live in `src/test/java/world/bentobox/stranger/`:

- `CommonTestSetup` — sets up `MockBukkit.mock()`, a static `Bukkit` mock, a mocked `BentoBox` plugin (injected via `WhiteBox` reflection into `BentoBox.instance`), and a mocked `IslandsManager` / `IslandWorldManager` with one default island. Extend this for most listener/command tests.
- `RanksManagerTestSetup` — adds rank-manager setup on top of `CommonTestSetup`.
- `WhiteBox` — utility for setting private static fields (e.g., the BentoBox singleton).
- `TestWorldSettings` — minimal `WorldSettings` returned from `iwm.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.

## Companion addons & integration notes

- **Do not** install BentoBox's `Border` addon alongside this — they fight. `InvSwitcher` is recommended (warned about on enable if missing). Real users typically also run `Warps`.
- Spigot NMS is a required repository (`<repository id="nms-repo">`) — it's used for world regeneration. Don't remove it from `pom.xml`.
63 changes: 41 additions & 22 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,18 @@
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>21</java.version>
<!-- Non-minecraft related dependencies -->
<powermock.version>2.0.9</powermock.version>
<junit.version>5.10.2</junit.version>
<mockito.version>5.11.0</mockito.version>
<mock-bukkit.version>v1.21-SNAPSHOT</mock-bukkit.version>
<!-- More visible way how to change dependency versions -->
<paper.version>1.21.10-R0.1-SNAPSHOT</paper.version>
<paper.version>1.21.11-R0.1-SNAPSHOT</paper.version>
<bentobox.version>3.10.0-SNAPSHOT</bentobox.version>
<!-- Revision variable removes warning about dynamic version -->
<revision>${build.version}-SNAPSHOT</revision>
<!-- Do not change unless you want different name for local builds. -->
<build.number>-LOCAL</build.number>
<!-- This allows to change between versions. -->
<build.version>1.0.3</build.version>
<build.version>1.0.4</build.version>
<sonar.organization>bentobox-world</sonar.organization>
</properties>

Expand Down Expand Up @@ -108,6 +110,10 @@
</profiles>

<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<repository>
<id>papermc</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
Expand Down Expand Up @@ -136,32 +142,46 @@
</repositories>

<dependencies>
<!-- Paper API-->
<!-- Mock Bukkit - used to mock the server in tests -->
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>${paper.version}</version>
<scope>provided</scope>
</dependency>
<!-- Mockito (Unit testing) -->
<groupId>com.github.MockBukkit</groupId>
<artifactId>MockBukkit</artifactId>
<version>${mock-bukkit.version}</version>
<scope>test</scope>
</dependency>
<!-- Mockito (Unit testing) This goes at the top to ensure the dependencies are accurate. -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>5.11.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.11.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>
<!-- Paper API-->
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-mockito2</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>${paper.version}</version>
<scope>provided</scope>
</dependency>
<!-- Mockito (Unit testing) -->
<dependency>
<groupId>world.bentobox</groupId>
<artifactId>bentobox</artifactId>
Expand Down Expand Up @@ -246,7 +266,6 @@
<!--suppress MavenModelInspection -->
<configuration>
<argLine>
${argLine}
--add-opens java.base/java.lang=ALL-UNNAMED
--add-opens java.base/java.math=ALL-UNNAMED
--add-opens java.base/java.io=ALL-UNNAMED
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/world/bentobox/stranger/StrangerRealms.java
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ private boolean loadSettings() {
public void onEnable() {
// Check for recommended addons
if (this.getPlugin().getAddonsManager().getAddonByName("Border").isPresent()) {
this.logWarning("StrangerRealms has its own Border, so do not use Border in the Crowdbound world.");
this.logWarning("StrangerRealms has its own Border, so do not use Border addon.");
}
if (this.getPlugin().getAddonsManager().getAddonByName("InvSwitcher").isEmpty()) {
this.logWarning("StrangerRealms recommends the InvSwitcher addon.");
Expand Down Expand Up @@ -164,6 +164,7 @@ public void onEnable() {

@Override
public void onDisable() {
// Nothing to do
}

@Override
Expand Down Expand Up @@ -290,7 +291,7 @@ public double getBorderSize() {
Math.max(getSettings().getBarrierIncreaseBlocks(), (this.getSettings().getBarrierIncreaseBlocks() * Bukkit.getServer().getOnlinePlayers().size()));
if (newBorderSize < borderSize) {
// End any current task to replace it
task.cancel();
cancelBorderTask();
// Trigger gradual reduction of border
task = Bukkit.getScheduler().runTaskTimer(getPlugin(), () -> {
if (borderSize > newBorderSize) {
Expand Down
Loading
Loading