Skip to content

Commit 802dba3

Browse files
authored
Merge pull request #2871 from BentoBoxWorld/develop
Release 3.12.0
2 parents 582d3bc + bc8888d commit 802dba3

File tree

304 files changed

+14144
-3854
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

304 files changed

+14144
-3854
lines changed

.github/copilot-instructions.md

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# Copilot Instructions for BentoBox
2+
3+
## Project Overview
4+
5+
BentoBox is a powerful Bukkit library plugin for Minecraft servers that provides the core engine for island-style games (SkyBlock, AcidIsland, SkyGrid, etc.). Games and features are added via a modular **Addon system**, enabling admins to mix and match game modes. BentoBox exposes a rich public API for addon developers covering island protection, GUI panels, team management, and more.
6+
7+
## Build System
8+
9+
The project uses **Gradle** with the Kotlin DSL (`build.gradle.kts`).
10+
11+
### Key Commands
12+
13+
```bash
14+
# Build the project (produces shaded JAR in build/libs/)
15+
./gradlew build
16+
17+
# Run unit tests
18+
./gradlew test
19+
20+
# Generate JaCoCo code coverage report (HTML + XML in build/reports/jacoco/)
21+
./gradlew jacocoTestReport
22+
23+
# Build + test + coverage + SonarQube analysis (what CI runs)
24+
./gradlew build test jacocoTestReport sonar --info
25+
26+
# Produce the final shaded plugin JAR
27+
./gradlew shadowJar
28+
29+
# Generate Javadocs
30+
./gradlew javadoc
31+
```
32+
33+
### Build Output
34+
35+
- Shaded (production) JAR: `build/libs/bentobox-<version>.jar`
36+
- Plain JAR: `build/libs/bentobox-<version>-original.jar`
37+
- Test coverage report: `build/reports/jacoco/test/html/index.html`
38+
- Javadocs: `build/docs/javadoc/`
39+
40+
## Tech Stack
41+
42+
| Area | Technology |
43+
|------|------------|
44+
| Language | Java 21 (with `--enable-preview`) |
45+
| Plugin Framework | PaperMC / Bukkit API (1.21) |
46+
| Build Tool | Gradle (Kotlin DSL) |
47+
| Testing | JUnit 5 (Jupiter), Mockito 5, MockBukkit |
48+
| Coverage | JaCoCo |
49+
| Code Quality | SonarQube (runs on CI for PRs to `develop`) |
50+
| Null Safety | Eclipse JDT `@NonNull` / `@Nullable` annotations |
51+
52+
## Project Structure
53+
54+
```
55+
src/
56+
├── main/java/world/bentobox/bentobox/
57+
│ ├── api/ # Public API for addon developers (events, flags, commands, panels, etc.)
58+
│ ├── blueprints/ # Island blueprint/schematic system
59+
│ ├── commands/ # Player and admin commands
60+
│ ├── database/ # Database abstraction (MySQL, MariaDB, PostgreSQL, MongoDB)
61+
│ ├── hooks/ # Integrations with third-party plugins (Vault, PlaceholderAPI, etc.)
62+
│ ├── listeners/ # Bukkit event listeners
63+
│ ├── managers/ # Core managers (addons, islands, players, blueprints, etc.)
64+
│ ├── nms/ # Version-specific NMS (net.minecraft.server) code
65+
│ ├── panels/ # Built-in inventory GUI panels
66+
│ └── util/ # Utility classes
67+
├── main/resources/
68+
│ ├── config.yml # Default plugin configuration
69+
│ ├── locales/ # Default en-US locale and other translations
70+
│ └── panels/ # Panel layout YAML files
71+
└── test/java/world/bentobox/bentobox/
72+
└── (mirrors main package structure)
73+
```
74+
75+
## Coding Conventions
76+
77+
- **Java version**: Java 21; preview features are enabled.
78+
- **Packages**: all lowercase, rooted at `world.bentobox.bentobox`.
79+
- **Classes**: PascalCase. Test classes must end with `Test` (e.g., `IslandManagerTest`).
80+
- **Methods / fields**: camelCase.
81+
- **Constants**: `UPPER_SNAKE_CASE`.
82+
- **Encoding**: UTF-8 everywhere.
83+
- **Null safety**: Annotate method parameters and return types with `@NonNull` or `@Nullable` from `org.eclipse.jdt.annotation`.
84+
- **No formatting-only PRs**: Do not submit PRs that only reformat code; they will be rejected.
85+
- **Javadoc**: Public API methods and classes must have Javadoc. `Xdoclint:none` suppresses strict doc-lint warnings, but well-documented code is expected.
86+
87+
## Testing Guidelines
88+
89+
- Use **JUnit 5** (`@Test`, `@BeforeEach`, `@AfterEach`, `@ExtendWith`).
90+
- Use **Mockito** (`@Mock`, `@InjectMocks`, `MockitoExtension`) for mocking.
91+
- Use **MockBukkit** to mock Bukkit/Paper server objects when necessary.
92+
- Extend `CommonTestSetup` where a common Bukkit environment is needed.
93+
- Test classes live in the matching package under `src/test/java/`.
94+
- Run `./gradlew test` to execute all tests.
95+
- Run `./gradlew jacocoTestReport` to generate the coverage report and verify coverage.
96+
- The CI pipeline runs both on every PR to `develop`.
97+
98+
### Example Test Skeleton
99+
100+
```java
101+
import org.junit.jupiter.api.BeforeEach;
102+
import org.junit.jupiter.api.Test;
103+
import org.junit.jupiter.api.extension.ExtendWith;
104+
import org.mockito.Mock;
105+
import org.mockito.junit.jupiter.MockitoExtension;
106+
107+
import static org.junit.jupiter.api.Assertions.*;
108+
import static org.mockito.Mockito.*;
109+
110+
@ExtendWith(MockitoExtension.class)
111+
class MyClassTest {
112+
113+
@Mock
114+
private SomeDependency dependency;
115+
116+
private MyClass myClass;
117+
118+
@BeforeEach
119+
void setUp() {
120+
myClass = new MyClass(dependency);
121+
}
122+
123+
@Test
124+
void testSomeBehavior() {
125+
when(dependency.someMethod()).thenReturn("expected");
126+
String result = myClass.doSomething();
127+
assertEquals("expected", result);
128+
}
129+
}
130+
```
131+
132+
## Architecture Notes
133+
134+
- **Addon system**: Addons implement `GameModeAddon` or `Addon` and are loaded at startup. Each game-mode addon registers its own worlds, commands, and flags.
135+
- **Island protection**: The `Flags` system combined with `IslandProtectionManager` controls what players can do on/near islands.
136+
- **Database layer**: `AbstractDatabaseHandler<T>` abstracts YAML, JSON, MySQL, PostgreSQL, and MongoDB storage. New data classes must extend `DataObject`.
137+
- **Event-driven**: All major actions emit Bukkit events in the `world.bentobox.bentobox.api.events` hierarchy; addons should listen to these instead of low-level Bukkit events where possible.
138+
- **Panel system**: Inventory GUI panels are driven by YAML layouts (`src/main/resources/panels/`) combined with `PanelBuilder` / `PanelItem` classes in `world.bentobox.bentobox.api.panels`.
139+
- **NMS compatibility**: Version-specific code lives in `world.bentobox.bentobox.nms` and is kept minimal; favour Paper API over NMS when possible.
140+
141+
## CI / CD
142+
143+
- **Workflow**: `.github/workflows/build.yml` runs on pushes to `develop` and on all pull requests.
144+
- **Java**: JDK 21 (Zulu distribution) is used in CI.
145+
- **Quality gate**: SonarQube analysis must pass. The `SONAR_TOKEN` secret is required.
146+
- **Artifact**: The shaded JAR is the deployable artifact; it is published to the CodeMC Maven repository on successful builds.
147+
148+
## Pull Request Guidelines
149+
150+
- Target the **`develop`** branch for all contributions.
151+
- All tests must pass (`./gradlew test`).
152+
- Keep PRs focused — one feature or bug fix per PR.
153+
- Add or update unit tests for every changed behaviour.
154+
- Follow the existing code style; do not reformat unrelated code.
155+
- Ensure SonarQube quality gate passes (checked automatically on PRs).
156+
- Reference the related GitHub issue in the PR description.

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,3 +85,4 @@ nbactions.xml
8585
nb-configuration.xml
8686
.nb-gradle/
8787
/BentoBox/
88+
/placeholder-dump.md

CLAUDE.md

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
BentoBox is a Bukkit/Paper library plugin (Java 21) that provides the core platform for island-style Minecraft games (SkyBlock, AcidIsland, etc.) via an extensible addon system.
8+
9+
## Build Commands
10+
11+
```bash
12+
./gradlew build # Build the shaded JAR
13+
./gradlew test # Run all tests
14+
./gradlew clean build # Clean then build
15+
./gradlew jacocoTestReport # Generate coverage report (build/reports/jacoco/)
16+
```
17+
18+
### Running a Single Test
19+
20+
```bash
21+
# Run all tests in a class
22+
./gradlew test --tests "world.bentobox.bentobox.managers.IslandsManagerTest"
23+
24+
# Run a specific test method
25+
./gradlew test --tests "world.bentobox.bentobox.managers.IslandsManagerTest.testMethodName"
26+
```
27+
28+
## Architecture
29+
30+
The main plugin class is `BentoBox.java` (extends `JavaPlugin`). Almost all subsystems are accessed via singleton managers held by the plugin instance.
31+
32+
### Key Packages
33+
34+
- **`api/`** — Public API surface for addons: events, commands, panels (GUIs), user management, flags, configuration
35+
- **`managers/`** — Core subsystems: `IslandsManager`, `PlayersManager`, `AddonsManager`, `LocalesManager`, `FlagsManager`, `BlueprintsManager`, `HooksManager`, `PlaceholdersManager`, `RanksManager`
36+
- **`database/`** — Database abstraction supporting MongoDB, MySQL, MariaDB, and PostgreSQL (via HikariCP)
37+
- **`blueprints/`** — Island schematic handling and pasting
38+
- **`listeners/`** — Bukkit event handlers (teleport, death, join/leave, panel clicks, spawn protection)
39+
- **`commands/`** — Admin and user command implementations
40+
- **`panels/`** — Inventory GUI panel system
41+
- **`hooks/`** — Integrations with external plugins (Vault, PlaceholderAPI, MythicMobs, Multiverse, LuckPerms, etc.)
42+
- **`nms/`** — NMS (Native Minecraft Server) version-specific code
43+
44+
### Island Data Flow
45+
46+
Islands are the central domain object. `IslandsManager` owns the island cache and database layer. `IslandWorldManager` holds per-world configuration. Protection logic is handled via `FlagsManager` and a rank system (`RanksManager`).
47+
48+
### Addon System
49+
50+
Addons (separate plugins) hook into BentoBox through the `api/` package. They register commands, flags, events, and panels by accessing managers through `BentoBox.getInstance()`.
51+
52+
### Flag System
53+
54+
Flags are the core protection/setting mechanism. There are three types:
55+
- `PROTECTION` — player action blocked by rank (e.g., BLOCK_BREAK)
56+
- `SETTING` — island-level on/off toggle (e.g., ANIMAL_SPAWNING)
57+
- `WORLD_SETTING` — server-level toggle, admin only
58+
59+
To write a protection listener, extend `FlagListener`:
60+
61+
```java
62+
public class MyListener extends FlagListener {
63+
@EventHandler(priority = EventPriority.LOW, ignoreCancelled = true)
64+
public void onSomeEvent(SomeEvent e) {
65+
checkIsland(e, e.getPlayer(), e.getBlock().getLocation(), Flags.BLOCK_BREAK);
66+
}
67+
}
68+
```
69+
70+
`checkIsland()` handles rank comparison, event cancellation, and player notification automatically. All protection flag listeners live in `listeners/flags/protection/`.
71+
72+
### Key API Patterns
73+
74+
```java
75+
// Island permission check
76+
island.isAllowed(user, flag);
77+
78+
// Localized player messaging (never use player.sendMessage() directly)
79+
user.sendMessage("protection.protected");
80+
81+
// Island lookup
82+
Optional<Island> island = plugin.getIslands().getIslandAt(location);
83+
84+
// All managers accessed via singleton
85+
plugin.getIslands() // IslandsManager
86+
plugin.getIWM() // IslandWorldManager
87+
plugin.getFlagsManager() // FlagsManager
88+
```
89+
90+
## Testing Patterns
91+
92+
The test suite uses JUnit 5 + Mockito + MockBukkit. **Almost every test class extends `CommonTestSetup`**, which pre-wires ~20 mocks:
93+
94+
- `plugin` — mocked `BentoBox` instance
95+
- `mockPlayer`, `world`, `location`, `island` — standard game objects
96+
- `iwm` (`IslandWorldManager`), `im` (`IslandsManager`), `lm` (`LocalesManager`), `fm` (`FlagsManager`), `hooksManager`
97+
98+
Use `CommonTestSetup` as the base for new tests. Call `super.setUp()` in `@BeforeEach` and `super.tearDown()` in `@AfterEach` if overriding. The `checkSpigotMessage(String)` helper asserts messages sent to the player.
99+
100+
Test resources and temporary database files are cleaned up automatically by the base class teardown.
101+
102+
## Public API Compatibility
103+
104+
BentoBox is a **plugin platform** — its public API is compiled against by many external addons. Binary-incompatible changes cause `NoSuchMethodError` at runtime for all addons until they recompile.
105+
106+
### Binary-incompatible changes (avoid without a semver-major release)
107+
- Changing the return type of a public method (the JVM encodes return type in the method descriptor; two methods cannot share name+params with different return types)
108+
- Removing or renaming public methods/classes
109+
- Adding required parameters to existing public methods
110+
111+
### SonarCloud rules vs. API stability
112+
Automated rules (e.g. S4738 "Replace Guava types with Java stdlib") are appropriate for internal code but **not** for public API methods whose return type is part of the binary contract. Suppress selectively with a comment:
113+
114+
```java
115+
@SuppressWarnings("java:S4738") // ImmutableSet is intentional public API; changing return type is binary-incompatible
116+
public ImmutableSet<UUID> getMemberSet() { ... }
117+
```
118+
119+
Guava (`ImmutableSet`, `ImmutableList`, etc.) is reliably available at runtime via Paper's bundled JARs and is safe to use in the public API.
120+
121+
## Build Notes
122+
123+
- The Gradle build uses the Paper `userdev` plugin and Shadow plugin to produce a fat/shaded JAR at `build/libs/BentoBox-{version}.jar`.
124+
- `plugin.yml` and `config.yml` are filtered for the `${version}` placeholder at build time; locale files are copied without filtering.
125+
- Java preview features are enabled for both compilation and test execution.
126+
- Local builds produce version `3.11.2-LOCAL-SNAPSHOT`; CI builds append `-b{BUILD_NUMBER}-SNAPSHOT`; `origin/master` builds produce the bare version.

build.gradle.kts

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArt
4646
group = "world.bentobox" // From <groupId>
4747

4848
// Base properties from <properties>
49-
val buildVersion = "3.11.2"
49+
val buildVersion = "3.12.0"
5050
val buildNumberDefault = "-LOCAL" // Local build identifier
5151
val snapshotSuffix = "-SNAPSHOT" // Indicates development/snapshot version
5252

@@ -60,7 +60,7 @@ var finalRevision = "$buildVersion$snapshotSuffix$finalBuildNumber"
6060
val envBuildNumber = System.getenv("BUILD_NUMBER")
6161
if (!envBuildNumber.isNullOrBlank()) {
6262
finalBuildNumber = "-b$envBuildNumber"
63-
finalRevision = "$buildVersion$finalBuildNumber$snapshotSuffix"
63+
finalRevision = "$buildVersion$snapshotSuffix"
6464
}
6565

6666
// 'master' profile logic: Activated when building from origin/master branch
@@ -108,6 +108,8 @@ val gsonRecordTypeAdapterFactoryVersion = "0.3.0"
108108
val jdtAnnotationVersion = "2.2.600"
109109
val multilibVersion = "1.1.13"
110110
val oraxenVersion = "1.193.1"
111+
val blueMapApiVersion = "v2.6.2"
112+
val dynmapApiVersion = "3.4"
111113

112114
// Store versions in extra properties for resource filtering (used in plugin.yml, config.yml)
113115
extra["java.version"] = javaVersion
@@ -186,6 +188,7 @@ repositories {
186188
maven("https://repo.fancyplugins.de/releases") { name = "FancyPlugins-Releases" }
187189
maven("https://repo.pyr.lol/snapshots") { name = "Pyr-Snapshots" }
188190
maven("https://maven.devs.beer/") { name = "MatteoDev" }
191+
maven("https://repo.mikeprimm.com/") { name = "Dynmap" }
189192
maven("https://repo.oraxen.com/releases") { name = "Oraxen" } // Custom items plugin
190193
maven("https://repo.codemc.org/repository/bentoboxworld/") { name = "BentoBoxWorld-Repo" }
191194
maven("https://repo.extendedclip.com/releases/") { name = "Placeholder-API-Releases" }
@@ -216,6 +219,7 @@ dependencies {
216219
// --- Compile Only Dependencies: Provided by the server at runtime ---
217220
compileOnly("org.mongodb:mongodb-driver:$mongodbVersion")
218221
compileOnly("com.zaxxer:HikariCP:$hikaricpVersion")
222+
testImplementation("com.zaxxer:HikariCP:$hikaricpVersion")
219223
compileOnly("com.github.MilkBowl:VaultAPI:$vaultVersion")
220224
compileOnly("me.clip:placeholderapi:$placeholderapiVersion")
221225
compileOnly("com.bergerkiller.bukkit:MyWorlds:$myworldsVersion") {
@@ -234,6 +238,16 @@ dependencies {
234238
compileOnly("de.oliver:FancyHolograms:$fancyHologramsVersion")
235239
compileOnly("world.bentobox:level:$levelVersion-SNAPSHOT")
236240
compileOnly("commons-lang:commons-lang:$commonsLangVersion")
241+
compileOnly("com.github.BlueMap-Minecraft:BlueMapAPI:$blueMapApiVersion")
242+
testImplementation("com.github.BlueMap-Minecraft:BlueMapAPI:$blueMapApiVersion")
243+
compileOnly("us.dynmap:DynmapCoreAPI:$dynmapApiVersion")
244+
compileOnly("us.dynmap:dynmap-api:$dynmapApiVersion") {
245+
exclude(group = "org.bukkit", module = "bukkit")
246+
}
247+
testImplementation("us.dynmap:DynmapCoreAPI:$dynmapApiVersion")
248+
testImplementation("us.dynmap:dynmap-api:$dynmapApiVersion") {
249+
exclude(group = "org.bukkit", module = "bukkit")
250+
}
237251
compileOnly("io.th0rgal:oraxen:$oraxenVersion") {
238252
exclude(group = "me.gabytm.util", module = "actions-spigot")
239253
exclude(group = "org.jetbrains", module = "annotations")
@@ -461,20 +475,22 @@ publishing {
461475
}
462476
}
463477

464-
// Configure publication target repository
478+
// Configure publication target repository (CodeMC Nexus)
465479
repositories {
466-
val mavenUrl: String? by project
467-
val mavenSnapshotUrl: String? by project
468-
469-
(if(version.toString().endsWith("SNAPSHOT")) mavenSnapshotUrl else mavenUrl)?.let { url ->
470-
maven(url) {
471-
val mavenUsername: String? by project
472-
val mavenPassword: String? by project
473-
if(mavenUsername != null && mavenPassword != null) {
474-
credentials {
475-
username = mavenUsername
476-
password = mavenPassword
477-
}
480+
val isSnapshot = version.toString().endsWith("SNAPSHOT")
481+
val repoUrl = if (isSnapshot) {
482+
"https://repo.codemc.io/repository/bentoboxworld/"
483+
} else {
484+
"https://repo.codemc.io/repository/bentoboxworld/"
485+
}
486+
487+
maven(repoUrl) {
488+
val mavenUsername: String? by project
489+
val mavenPassword: String? by project
490+
if (mavenUsername != null && mavenPassword != null) {
491+
credentials {
492+
username = mavenUsername
493+
password = mavenPassword
478494
}
479495
}
480496
}

0 commit comments

Comments
 (0)