Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
b013196
Separate block and entity limits per dimension (fixes #43)
tastybento May 6, 2026
8fce44e
Merge pull request #261 from BentoBoxWorld/worktree-nether-end-limits
tastybento May 6, 2026
5709b00
Clean up test code per SonarCloud findings
tastybento May 6, 2026
842a3c4
Remove unused field and redundant cast (Sonar S1068, S1905)
tastybento May 6, 2026
fe70bac
Refactor Settings entity-limit loading (Sonar S3776, S1192, S135)
tastybento May 6, 2026
ab9727b
Tidy limit-panel command classes (Sonar S107, S1172, S1192, S1640)
tastybento May 6, 2026
bda31d0
Reduce cognitive complexity in scan/limit hot paths (Sonar S3776)
tastybento May 6, 2026
0fbcc4f
Drop unused Island parameter from LimitTab constructor (Sonar S1172)
tastybento May 6, 2026
208fc84
Address null-flow and diamond-operator findings (Sonar S2637, S2293)
tastybento May 6, 2026
e40f45f
Drop public modifiers on JUnit 5 tests (Sonar S5786)
tastybento May 6, 2026
83b371a
Bump version to 1.28.2
tastybento May 6, 2026
245d18d
Pin MockBukkit to Maven Central 4.110.0 instead of jitpack snapshot
tastybento May 30, 2026
c00fb4f
Update Modrinth game-versions to current MC range
tastybento Jun 3, 2026
4b5bf11
Merge pull request #262 from BentoBoxWorld/chore/modrinth-game-versions
tastybento Jun 3, 2026
7f78a60
Add project key to SonarQube analysis command
tastybento Jun 12, 2026
c1a3e2b
Fix block count leaks and parallelize recount chunk loading
daniel-skopek Jun 10, 2026
6698824
Revert unbalanced BlockGrowEvent decrement; add spread/grow tests
tastybento Jun 12, 2026
7c95708
Merge pull request #264 from BentoBoxWorld/fix/block-count-leaks
tastybento Jun 12, 2026
e222088
Document block state-transition counting invariant and project layout…
tastybento Jun 12, 2026
580b66e
Count multi-block placements (beds, doors) once, not twice (#86)
tastybento Jun 12, 2026
30c5c0e
Remove 'public' modifier from test methods
tastybento Jun 12, 2026
03b1166
Merge pull request #265 from BentoBoxWorld/fix/86-bed-double-count
tastybento Jun 12, 2026
def4397
Anchor golem/snowman block removal on the pumpkin, not the spawn bloc…
tastybento Jun 12, 2026
8a5e874
Fix three entity counting bugs in EntityLimitListener
daniel-skopek Jun 12, 2026
e794fd7
Merge pull request #266 from BentoBoxWorld/fix/127-golem-block-removal
tastybento Jun 13, 2026
80705a3
Merge pull request #267 from daniel-skopek/fix-entity-counting
tastybento Jun 13, 2026
f78f97f
Deny spawn-egg use before consumption when at the entity limit (#134)
tastybento Jun 13, 2026
ccdf020
Merge pull request #268 from BentoBoxWorld/fix/134-spawn-egg-consumed
tastybento Jun 13, 2026
2a535e7
Resolve 1.21.9 copper materials by name to support older servers
tastybento Jun 13, 2026
6e655bb
Address SonarCloud issues across main and test sources
tastybento Jun 13, 2026
1ad244f
Sync locale keys and convert color codes to MiniMessage
tastybento Jun 13, 2026
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
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Needed to get PR information, if any
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar
run: mvn -B verify org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=BentoBoxWorld_Limits
3 changes: 3 additions & 0 deletions .github/workflows/modrinth-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ jobs:
1.21.9
1.21.10
1.21.11
26.1
26.1.1
26.1.2

# Path to the built JAR — Maven produces Limits-<version>.jar in target/
# Note: glob patterns are not supported; use the release tag directly.
Expand Down
145 changes: 134 additions & 11 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,40 +36,74 @@ Limits is a **BentoBox addon** (not a standalone Spigot plugin). It depends on B
4. Registers three listeners: `BlockLimitsListener`, `JoinListener`, `EntityLimitListener`
5. Registers PlaceholderAPI placeholders for each material and entity type

### Three-Tier Limit System
### Per-Environment Tracking (since #43)

Limits are resolved in a priority hierarchy (highest wins):
Block counts, entity counts, limits, and offsets are all tracked **per `World.Environment`** (overworld, nether, end). The data model in `IslandBlockCount` keys every map by `Environment` first, then by material/entity. Pre-env data on disk is migrated into `Environment.NORMAL` lazily on first access.

1. **Island-specific** (`IslandBlockCount.blockLimits` / `entityLimits`): set by player permissions, checked at join
2. **World-specific** (`BlockLimitsListener.worldLimitMap`): from `config.yml` `worlds:` section
3. **Default** (`BlockLimitsListener.defaultLimitMap`): from `config.yml` `blocklimits:` section
This means:
- Counts in nether and end are persistent — they don't go to zero when chunks unload (the original bug in #43).
- A single config-defined limit applies independently to each env (`HOPPER: 10` allows 10 in overworld, 10 in nether, 10 in end).
- Entity portal teleports decrement the source-env count and increment the destination-env count via `EntityPortalEvent`.

**Offsets** (`blockLimitsOffset`, `entityLimitsOffset`) are added on top of any limit found — used by the `offset` admin command to give per-island bonus allowances.
### Limit Resolution Order

For a block placement (or entity spawn) in environment `env`, the limit is resolved in priority order:

1. **Island-specific env limit** (`IslandBlockCount.envBlockLimits[env]`) — set by player permissions, checked at join.
2. **World-specific limit** (`BlockLimitsListener.worldLimitMap[world]`) — from the `worlds:` section. Each world is one env so this is implicitly env-scoped.
3. **Env default** (`BlockLimitsListener.envDefaultLimitMap[env]`) — from `blocklimits` (seeded into all envs) plus `blocklimits-nether` / `blocklimits-end` overrides.

**Offsets** (`envBlockLimitsOffset[env]`, `envEntityLimitsOffset[env]`) are added on top of any resolved limit. The legacy `/offset` admin command sets the same offset across all envs via the `setBlockLimitsOffsetAllEnvs` / `setEntityLimitsOffsetAllEnvs` helpers.

### Persistent Entity Counts

`EntityLimitListener` no longer counts entities via `World.getNearbyEntities(island.getBoundingBox())`. Instead it maintains the count in `IslandBlockCount.envEntityCounts`:

- Increment on MONITOR-priority handlers for `CreatureSpawnEvent`, `VehicleCreateEvent`, `HangingPlaceEvent` (after our LOW-priority limit check has had its say).
- Decrement on `EntityRemoveEvent` whenever the cause is **not** `UNLOAD` (so death, despawn, drop, plugin removal, etc. all decrement; chunk unload does not).
- `EntityPortalEvent` (MONITOR) handles env-to-env teleport: decrement source env, increment destination env.

This is Paper-only (the addon dropped Spigot support; `EntityRemoveEvent` is on Bukkit's API now but `EntityRemoveEvent.Cause` is needed).

### Key Classes

| Class | Role |
|---|---|
| `Limits` | Main addon; wires everything together, registers placeholders |
| `Settings` | Parses `config.yml`; holds entity limits, entity group limits, game mode list |
| `IslandBlockCount` | Per-island data object stored in BentoBox's database (`@Table("IslandBlockCount")`); tracks block counts, per-island block/entity limits, and offsets |
| `IslandBlockCount` | Per-island data object stored in BentoBox's database (`@Table("IslandBlockCount")`); tracks block counts, entity counts, limits, and offsets — all keyed by `Environment` |
| `EnvNamespacedKeyMapAdapter` | Gson type adapter for `Map<Environment, Map<NamespacedKey, Integer>>` — needed because `NamespacedKey` is not enum-keyed and Gson can't handle it natively |
| `BlockLimitsListener` | Core block tracking listener; handles all block place/break/grow/explode events; maintains the `islandCountMap` in memory; persists via BentoBox `Database<IslandBlockCount>` |
| `JoinListener` | Applies permission-based limits on player join, island creation/reset, and ownership change; fires `LimitsPermCheckEvent` / `LimitsJoinPermCheckEvent` for external cancellation |
| `EntityLimitListener` | Cancels entity spawn/breed/vehicle-create events when entity or group limits are exceeded; handles async golem/snowman spawning edge cases |
| `RecountCalculator` | Async chunk scanner used by the admin `calc` command to rebuild block counts from scratch; uses chunk snapshots and BentoBox's `Pipeliner` |

### Permission-Based Limits

Permission format: `<gamemode>.island.limit.<MATERIAL_OR_ENTITY_OR_GROUP>.<NUMBER>`

Example: `bskyblock.island.limit.HOPPER.20`
Two formats:
- 5-segment, all-env: `<gamemode>.island.limit.<KEY>.<NUMBER>` — applied independently to overworld, nether, and end.
- 6-segment, env-scoped: `<gamemode>.island.limit.<env>.<KEY>.<NUMBER>` where `<env>` ∈ `{overworld, nether, end}`.

`JoinListener.checkPerms()` clears all permission-based limits then re-applies them from scratch on each join. The highest value wins if multiple permissions grant the same limit. Wildcards are not supported.
`JoinListener.checkPerms()` clears all permission-based limits then re-applies them from scratch on each join. The highest value wins if multiple permissions grant the same limit for the same env. Wildcards are not supported.

### Block Material Normalization

`BlockLimitsListener.fixMaterial()` normalizes variant materials to their canonical form (e.g., `CHIPPED_ANVIL` → `ANVIL`, `REDSTONE_WALL_TORCH` → `REDSTONE_TORCH`, `PISTON_HEAD` → `PISTON`/`STICKY_PISTON`). Block config keys can use Bukkit Material names or Minecraft tag names (e.g., `minecraft:logs`).

### Block State Transitions

When one block becomes another in place — `BlockFormEvent`, `EntityBlockFormEvent`, `BlockSpreadEvent` (grass/mycelium/fire spread), `BlockGrowEvent` (crops, sugar cane, etc.) — the handlers must keep the count balanced: **decrement the replaced block, then add the new one with a limit check, and revert on cancel.** The canonical shape (see `onBlock(BlockFormEvent)`):

```java
process(e.getBlock(), false); // remove old
if (process(e.getBlock(), e.getNewState().getBlockData(), true) > -1) {
e.setCancelled(true);
process(e.getBlock(), true); // limit hit → restore old
}
```

Key invariant when editing these handlers: `process(block, true)` returns the limit **before** incrementing when the limit is hit, so it only ever adds to the count on the success path (`-1` return). Do **not** add a compensating `process(..., false)` in the cancel branch — nothing was added, so it would decrement a real block's count below its true value and let the next transition slip past the limit. `BlockLimitsListenerTest` guards this with the `*StateTransition`, `*AtLimitCancelsAndRestoresOld`, and `testBlockGrowAtNonZeroLimitDoesNotDecrement` cases.

### Events API

Two cancellable events are fired so other plugins can intercept limit application:
Expand Down Expand Up @@ -146,3 +180,92 @@ worldLimitField.setAccessible(true);
#### Testing Debounce (`justSpawned`)

`EntityLimitListener`'s private `justSpawned` list (deduplicates entity spawns) can be accessed via reflection to test debounce behavior.

## Dependency Source Lookup

When you need to inspect source code for a dependency (e.g., BentoBox, addons):

1. **Check local Maven repo first**: `~/.m2/repository/` — sources jars are named `*-sources.jar`
2. **Check the workspace**: Look for sibling directories or Git submodules that may contain the dependency as a local project (e.g., `../bentoBox`, `../addon-*`)
3. **Check Maven local cache for already-extracted sources** before downloading anything
4. Only download a jar or fetch from the internet if the above steps yield nothing useful

Prefer reading `.java` source files directly from a local Git clone over decompiling or extracting a jar.

In general, the latest version of BentoBox should be targeted.

## Project Layout

Related projects are checked out as siblings under `~/git/`:

**Core:**
- `bentobox/` — core BentoBox framework

**Game modes:**
- `addon-acidisland/` — AcidIsland game mode
- `addon-bskyblock/` — BSkyBlock game mode
- `Boxed/` — Boxed game mode (expandable box area)
- `CaveBlock/` — CaveBlock game mode
- `OneBlock/` — AOneBlock game mode
- `SkyGrid/` — SkyGrid game mode
- `RaftMode/` — Raft survival game mode
- `StrangerRealms/` — StrangerRealms game mode
- `Brix/` — plot game mode
- `parkour/` — Parkour game mode
- `poseidon/` — Poseidon game mode
- `gg/` — gg game mode

**Addons:**
- `addon-level/` — island level calculation
- `addon-challenges/` — challenges system
- `addon-welcomewarpsigns/` — warp signs
- `addon-limits/` — block/entity limits
- `addon-invSwitcher/` / `invSwitcher/` — inventory switcher
- `addon-biomes/` / `Biomes/` — biomes management
- `Bank/` — island bank
- `Border/` — world border for islands
- `Chat/` — island chat
- `CheckMeOut/` — island submission/voting
- `ControlPanel/` — game mode control panel
- `Converter/` — ASkyBlock to BSkyBlock converter
- `DimensionalTrees/` — dimension-specific trees
- `discordwebhook/` — Discord integration
- `Downloads/` — BentoBox downloads site
- `DragonFights/` — per-island ender dragon fights
- `ExtraMobs/` — additional mob spawning rules
- `FarmersDance/` — twerking crop growth
- `GravityFlux/` — gravity addon
- `Greenhouses-addon/` — greenhouse biomes
- `IslandFly/` — island flight permission
- `IslandRankup/` — island rankup system
- `Likes/` — island likes/dislikes
- `Limits/` — block/entity limits
- `lost-sheep/` — lost sheep adventure
- `MagicCobblestoneGenerator/` — custom cobblestone generator
- `PortalStart/` — portal-based island start
- `pp/` — pp addon
- `Regionerator/` — region management
- `Residence/` — residence addon
- `TopBlock/` — top ten for OneBlock
- `TwerkingForTrees/` — twerking tree growth
- `Upgrades/` — island upgrades (Vault)
- `Visit/` — island visiting
- `weblink/` — web link addon
- `CrowdBound/` — CrowdBound addon

**Data packs:**
- `BoxedDataPack/` — advancement datapack for Boxed

**Documentation & tools:**
- `docs/` — main documentation site
- `docs-chinese/` — Chinese documentation
- `docs-french/` — French documentation
- `BentoBoxWorld.github.io/` — GitHub Pages site
- `website/` — website
- `translation-tool/` — translation tool

Check these for source before any network fetch.

## Key Dependencies (source locations)

- `world.bentobox:bentobox` → `~/git/bentobox/src/`
40 changes: 31 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,32 +19,54 @@ There is a user command and an admin command called "limits". Admins can check t

The config.yml has the following sections:

* blocklimits
* worlds
* entitylimits
* `blocklimits`, `blocklimits-nether`, `blocklimits-end`
* `worlds`
* `entitylimits`, `entitylimits-nether`, `entitylimits-end`
* `entitygrouplimits`, `entitygrouplimits-nether`, `entitygrouplimits-end`

### Per-environment separation

Limits are tracked independently in the overworld, nether, and end. A `HOPPER: 10` limit means the player can place up to 10 hoppers in **each** of overworld, nether, and end — 30 hoppers total across the island. Likewise for entities and entity groups.

If you want a different limit in nether or end, add a corresponding `*-nether` or `*-end` section that overrides the env-default for those entries only.

### blocklimits

This section lists the maximum number of blocks allowed for each block material. Do not use non-block materials because they will not work. The limits apply to all game worlds.
The base block-limit section. Values here apply to every environment unless overridden by `blocklimits-nether` / `blocklimits-end` or by a `worlds.<worldname>` entry.

### worlds

This section lists block limits for specific worlds. You must name the world specifically, e.g. AcidIsland_world and then list the materials and the limit.
Per-world overrides. Name the world specifically (e.g. `AcidIsland_world`) and list the materials and limits. A world entry overrides the env-default for that exact world.

### entitylimits

Coming soon!
Default entity-type limits applied independently per environment. Override per env via `entitylimits-nether` / `entitylimits-end`.

### entitygrouplimits

Define named entity groups (icon, member set, default limit). Override the limit value per env with `entitygrouplimits-nether` / `entitygrouplimits-end`. The icon and member set are fixed in the base section — only the numeric limit can be overridden per env.

## Permissions

Island owners can have exclusive permissions that override the default or world settings. The format is:
Island owners can have exclusive permissions that override the default or world settings. Two formats are supported:

Format is `GAME-MODE-NAME.island.limit.MATERIAL.LIMIT`
* All-environment: `<gamemode>.island.limit.<KEY>.<NUMBER>` — applies the limit independently to every environment.
* Per-environment: `<gamemode>.island.limit.<env>.<KEY>.<NUMBER>` — applies only to one environment, where `<env>` is `overworld`, `nether`, or `end`.

example: `bskyblock.island.limit.hopper.10`
Examples:

```
bskyblock.island.limit.hopper.10 # 10 hoppers in each env
bskyblock.island.limit.nether.hopper.5 # only 5 hoppers in nether
bskyblock.island.limit.end.enderman.50 # only 50 endermen in the end
```

Permissions activate when the player logs in.

### Migrating an existing server

When upgrading from a pre-env version of Limits, existing per-island block counts are migrated to the overworld slot on first load. Counts in the nether/end will start at zero and be populated as entities and blocks change. Run `/<gamemode> admin limits calc <player>` to fully recount any island and redistribute its counts across environments.

Usage permissions are (put the gamemode name, e.g. acidisland at the front):

```
Expand Down
8 changes: 4 additions & 4 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
<!-- 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.28.1</build.version>
<build.version>1.28.2</build.version>
<sonar.projectKey>BentoBoxWorld_Limits</sonar.projectKey>
<sonar.organization>bentobox-world</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
Expand Down Expand Up @@ -135,9 +135,9 @@

<dependencies>
<dependency>
<groupId>com.github.MockBukkit</groupId>
<artifactId>MockBukkit</artifactId>
<version>v1.21-SNAPSHOT</version>
<groupId>org.mockbukkit.mockbukkit</groupId>
<artifactId>mockbukkit-v1.21</artifactId>
<version>4.110.0</version>
<scope>test</scope>
<exclusions>
<exclusion>
Expand Down
Loading
Loading