You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
- MobSpawnReplacement.resolveOldEntityType /
resolveNewEntityType: use toUpperCase(Locale.ROOT) so config
parsing stays stable across server locales (Turkish 'i' vs 'I'
problem). Reviewer comments at MobSpawnReplacement.java:67 and :90.
- config.yml + README: rewrite the per-gamemode override note to
describe the actual fallback-on-miss semantics. The current
listener falls through to global chances when no per-gamemode rule
fires (wrong entity / chance roll failed), and that behaviour is
enshrined by testPerGameModeNetherChanceZeroFallsBackToGlobal /
testPerGameModeNetherReplacementEntityMismatchFallsBackToGlobal —
the docs were wrong, not the code. Reviewer comments at
config.yml:31 and README.md:78.
- CLAUDE.md: refresh stale build/test guidance for the 1.15.0
toolchain (Java 21 / Paper 1.21.11 / BentoBox 3.14.0-SNAPSHOT,
MockBukkit + JUnit 5, Pladdon entry point, per-gamemode rule
semantics with the test names to look for before changing the
listener). Reviewer comment at CLAUDE.md:15.
- .github/workflows/build.yml: split mvn verify from the Sonar
invocation, gate the Sonar step on the PR coming from the same
repo so PRs from forks (where SONAR_TOKEN is not exposed) still
get a green Maven build instead of failing during analysis.
Reviewer comment at build.yml:38.
42/42 tests still green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: CLAUDE.md
+11-7Lines changed: 11 additions & 7 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -4,31 +4,35 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
4
4
5
5
## Project
6
6
7
-
ExtraMobs is a BentoBox addon (Bukkit/Spigot plugin) that re-skins certain natural mob spawns inside GameMode-managed worlds: Zombified Piglins → Blaze/Wither Skeleton in the Nether, Endermen → Shulkers in the End, and Fish → Guardians in deep-ocean overworld biomes. The addon does not alter Minecraft's spawn rules — it listens for natural spawns and conditionally cancels + re-spawns a different entity.
7
+
ExtraMobs is a BentoBox addon (Paper plugin) that re-skins certain natural mob spawns inside GameMode-managed worlds: Zombified Piglins / Piglins → Blaze/Wither Skeleton in the Nether, Endermen → Shulkers in the End, and Fish → Guardians in deep-ocean overworld biomes. The addon does not alter Minecraft's spawn rules — it listens for natural spawns and conditionally cancels + re-spawns a different entity. As of 1.15.0 it also ships a Pladdon entry point so it can be loaded directly by Paper as well as via BentoBox.
-`mvn test -Dtest=MobsSpawnListenerTest` — single test class.
14
14
-`mvn test -Dtest=MobsSpawnListenerTest#methodName` — single test method.
15
-
- Java 17 (`<java.version>17</java.version>` in pom.xml). Targets Spigot 1.21.3 and BentoBox 2.7.1-SNAPSHOT.
15
+
- Java 21 (`<java.version>21</java.version>` in pom.xml). Targets Paper 1.21.11 and BentoBox 3.14.0-SNAPSHOT.
16
16
- Build versioning is driven by Maven profiles: `-LOCAL` by default, `-b<BUILD_NUMBER>` under Jenkins CI, and a clean release version when `GIT_BRANCH=origin/master`. Don't hand-edit version strings — change `build.version` in `pom.xml`.
17
17
18
18
## Architecture
19
19
20
-
Tiny codebase, three production classes:
20
+
Tiny codebase, four production classes:
21
21
22
22
-`ExtraMobsAddon` (`src/main/java/world/bentobox/extramobs/`) — extends `world.bentobox.bentobox.api.addons.Addon`. In `onLoad()` it loads `Settings` via BentoBox's `Config<>` (auto-creates `config.yml` from `src/main/resources/`). In `onEnable()` it iterates `getAddonsManager().getGameModeAddons()`, sets `hooked=true` if any GameMode is not in `disabledGameModes`, and registers `MobsSpawnListener`. If nothing hooks, the addon disables itself.
23
-
-`config.Settings` — `ConfigObject` with `@StoreAt(filename="config.yml", path="addons/ExtraMobs")`. Field annotations (`@ConfigEntry`, `@ConfigComment`) drive both YAML parsing and the on-disk comment block; getters/setters are mandatory for the BentoBox config framework to bind values.
24
-
-`listeners.MobsSpawnListener` — single `@EventHandler(priority=HIGHEST, ignoreCancelled=true)` on `CreatureSpawnEvent`. Only `SpawnReason.NATURAL` events are considered. Flow: resolve the GameMode via `plugin.getIWM().getAddon(world)`, bail if disabled, then branch by entity type + environment (`isIslandNether` / `isIslandEnd` / `World.Environment.NORMAL` + biome). Each branch checks a "suitable block" predicate (nether brick / purpur / prismarine, with slab+stairs variants) and rolls against the configured chance. On a successful roll the event is cancelled and `world.spawnEntity()` summons the replacement.
23
+
-`ExtraMobsPladdon` — extends `Pladdon`. Lets Paper load the addon directly as a plugin (paired with `src/main/resources/plugin.yml`); returns a fresh `ExtraMobsAddon` from `getAddon()`.
24
+
-`config.Settings` — `ConfigObject` with `@StoreAt(filename="config.yml", path="addons/ExtraMobs")`. Field annotations (`@ConfigEntry`, `@ConfigComment`) drive both YAML parsing and the on-disk comment block; getters/setters are mandatory for the BentoBox config framework to bind values. The `gamemode-settings` field is stored as `Map<String, Object>` because BentoBox's config layer doesn't model the nested-list-of-maps shape; `getReplacements(gameMode, env)` parses the raw structure into `MobSpawnReplacement` records on read.
25
+
-`config.MobSpawnReplacement` — POJO for one per-gamemode rule (`old` mob, `new` mob, `chance`). `resolveOldEntityType()` / `resolveNewEntityType()` upper-case via `Locale.ROOT` then call `EntityType.valueOf` defensively.
26
+
-`listeners.MobsSpawnListener` — single `@EventHandler(priority=HIGHEST, ignoreCancelled=true)` on `CreatureSpawnEvent`. Only `SpawnReason.NATURAL` events are considered. Flow: `resolveActiveGameMode(world)` returns the GameMode name (or `null` if absent / disabled), then `onEntitySpawn` dispatches by entity type + environment to `handleNetherSpawn` / `handleEndSpawn` / `handleOverworldSpawn`. Each handler first checks a "suitable block" predicate (nether brick / purpur / prismarine, with slab+stairs variants), then calls `applyGameModeReplacements` for per-gamemode rules, and only falls back to the global `nether-chances` / `end-chances` / `overworld-chance` values if no per-gamemode rule fires for that event. The four block sets are pre-computed `Set<Material>` statics on the class.
25
27
26
28
The "suitable location" helpers encode the design rule that drives the addon: replacement is gated on the player having built a themed structure. Changes to spawn rules almost always live in these predicates plus the dispatch branches in `onEntitySpawn`.
27
29
30
+
Per-gamemode rules **supplement** the globals rather than suppress them — a per-gamemode rule with `chance: 0.05` for `ZOMBIFIED_PIGLIN→WITHER_SKELETON` falls through to the global wither/blaze chances on a miss, and `PIGLIN` (different entity) always falls through. The tests `testPerGameModeNetherChanceZeroFallsBackToGlobal` and `testPerGameModeNetherReplacementEntityMismatchFallsBackToGlobal` enshrine this behaviour — keep it in mind before changing the listener.
31
+
28
32
## Testing notes
29
33
30
-
- JUnit 4 + Mockito + PowerMock (`@RunWith(PowerMockRunner.class)`). Bukkit's static `Server`/`Registry`/`Tag` are stubbed via `listeners/mocks/ServerMocks.newServer()`— call this in `@Before` whenever a test touches Bukkit statics. The surefire plugin's long `--add-opens` argLine is required for PowerMock under Java 17; don't strip it.
31
-
- Jacoco excludes `**/*Names*` and `org/bukkit/Material*` to avoid synthetic-field / "Material too large to mock" failures. New tests that need to mock `Material` should rely on `ServerMocks` rather than reintroducing PowerMock static-mocking on `Material`.
34
+
- JUnit 5 + Mockito 5 + MockBukkit (`org.mockbukkit.mockbukkit:mockbukkit-v1.21`). Test classes extend `CommonTestSetup` which calls `MockBukkit.mock()`in `@BeforeEach` and tears down in `@AfterEach`; it also injects the `BentoBox` singleton via `WhiteBox.setInternalState(BentoBox.class, "instance", plugin)` and statically stubs `Bukkit` + `Util`. The surefire plugin's long `--add-opens` argLine plus the leading `@{argLine}` (which late-binds the Jacoco prepare-agent javaagent) are required; don't strip either.
35
+
- Jacoco excludes `**/*Names*` and `org/bukkit/Material*` to avoid synthetic-field / "Material too large to mock" failures. Coverage is reported to SonarCloud via the GitHub Actions workflow.
Copy file name to clipboardExpand all lines: README.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -75,7 +75,7 @@ All chance values are decimals between `0.0` (never) and `1.0` (always). For the
75
75
76
76
### Per-Gamemode Overrides
77
77
78
-
You can define replacement rules that only apply to a specific game mode. Each gamemode may set rules for the `nether`, `end`, and/or `world` (overworld) environments. **When per-gamemode rules are present for an environment, the global defaults above are not applied for that gamemode.**
78
+
You can define replacement rules that only apply to a specific game mode. Each gamemode may set rules for the `nether`, `end`, and/or `world` (overworld) environments. Rules are tried in order before the global defaults. **If a rule matches the spawning entity and its chance roll succeeds, the replacement is applied and processing stops; otherwise the global defaults above are used as a fallback** — so per-gamemode rules supplement the globals rather than replace them, and you can keep the globals as a safety net for any entity the per-gamemode block doesn't cover.
0 commit comments