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
@@ -36,40 +36,74 @@ Limits is a **BentoBox addon** (not a standalone Spigot plugin). It depends on B
36
36
4. Registers three listeners: `BlockLimitsListener`, `JoinListener`, `EntityLimitListener`
37
37
5. Registers PlaceholderAPI placeholders for each material and entity type
38
38
39
-
### Three-Tier Limit System
39
+
### Per-Environment Tracking (since #43)
40
40
41
-
Limits are resolved in a priority hierarchy (highest wins):
41
+
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.
42
42
43
-
1.**Island-specific** (`IslandBlockCount.blockLimits` / `entityLimits`): set by player permissions, checked at join
44
-
2.**World-specific** (`BlockLimitsListener.worldLimitMap`): from `config.yml``worlds:` section
45
-
3.**Default** (`BlockLimitsListener.defaultLimitMap`): from `config.yml``blocklimits:` section
43
+
This means:
44
+
- Counts in nether and end are persistent — they don't go to zero when chunks unload (the original bug in #43).
45
+
- A single config-defined limit applies independently to each env (`HOPPER: 10` allows 10 in overworld, 10 in nether, 10 in end).
46
+
- Entity portal teleports decrement the source-env count and increment the destination-env count via `EntityPortalEvent`.
46
47
47
-
**Offsets** (`blockLimitsOffset`, `entityLimitsOffset`) are added on top of any limit found — used by the `offset` admin command to give per-island bonus allowances.
48
+
### Limit Resolution Order
49
+
50
+
For a block placement (or entity spawn) in environment `env`, the limit is resolved in priority order:
51
+
52
+
1.**Island-specific env limit** (`IslandBlockCount.envBlockLimits[env]`) — set by player permissions, checked at join.
53
+
2.**World-specific limit** (`BlockLimitsListener.worldLimitMap[world]`) — from the `worlds:` section. Each world is one env so this is implicitly env-scoped.
54
+
3.**Env default** (`BlockLimitsListener.envDefaultLimitMap[env]`) — from `blocklimits` (seeded into all envs) plus `blocklimits-nether` / `blocklimits-end` overrides.
55
+
56
+
**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.
57
+
58
+
### Persistent Entity Counts
59
+
60
+
`EntityLimitListener` no longer counts entities via `World.getNearbyEntities(island.getBoundingBox())`. Instead it maintains the count in `IslandBlockCount.envEntityCounts`:
61
+
62
+
- Increment on MONITOR-priority handlers for `CreatureSpawnEvent`, `VehicleCreateEvent`, `HangingPlaceEvent` (after our LOW-priority limit check has had its say).
63
+
- Decrement on `EntityRemoveEvent` whenever the cause is **not**`UNLOAD` (so death, despawn, drop, plugin removal, etc. all decrement; chunk unload does not).
This is Paper-only (the addon dropped Spigot support; `EntityRemoveEvent` is on Bukkit's API now but `EntityRemoveEvent.Cause` is needed).
48
67
49
68
### Key Classes
50
69
51
70
| Class | Role |
52
71
|---|---|
53
72
|`Limits`| Main addon; wires everything together, registers placeholders |
54
73
|`Settings`| Parses `config.yml`; holds entity limits, entity group limits, game mode list |
55
-
|`IslandBlockCount`| Per-island data object stored in BentoBox's database (`@Table("IslandBlockCount")`); tracks block counts, per-island block/entity limits, and offsets |
74
+
|`IslandBlockCount`| Per-island data object stored in BentoBox's database (`@Table("IslandBlockCount")`); tracks block counts, entity counts, limits, and offsets — all keyed by `Environment`|
75
+
|`EnvNamespacedKeyMapAdapter`| Gson type adapter for `Map<Environment, Map<NamespacedKey, Integer>>` — needed because `NamespacedKey` is not enum-keyed and Gson can't handle it natively |
56
76
|`BlockLimitsListener`| Core block tracking listener; handles all block place/break/grow/explode events; maintains the `islandCountMap` in memory; persists via BentoBox `Database<IslandBlockCount>`|
57
77
|`JoinListener`| Applies permission-based limits on player join, island creation/reset, and ownership change; fires `LimitsPermCheckEvent` / `LimitsJoinPermCheckEvent` for external cancellation |
58
78
|`EntityLimitListener`| Cancels entity spawn/breed/vehicle-create events when entity or group limits are exceeded; handles async golem/snowman spawning edge cases |
59
79
|`RecountCalculator`| Async chunk scanner used by the admin `calc` command to rebuild block counts from scratch; uses chunk snapshots and BentoBox's `Pipeliner`|
- 5-segment, all-env: `<gamemode>.island.limit.<KEY>.<NUMBER>` — applied independently to overworld, nether, and end.
85
+
- 6-segment, env-scoped: `<gamemode>.island.limit.<env>.<KEY>.<NUMBER>` where `<env>` ∈ `{overworld, nether, end}`.
66
86
67
-
`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.
87
+
`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.
68
88
69
89
### Block Material Normalization
70
90
71
91
`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`).
72
92
93
+
### Block State Transitions
94
+
95
+
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)`):
96
+
97
+
```java
98
+
process(e.getBlock(), false); // remove old
99
+
if (process(e.getBlock(), e.getNewState().getBlockData(), true) >-1) {
100
+
e.setCancelled(true);
101
+
process(e.getBlock(), true); // limit hit → restore old
102
+
}
103
+
```
104
+
105
+
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.
106
+
73
107
### Events API
74
108
75
109
Two cancellable events are fired so other plugins can intercept limit application:
`EntityLimitListener`'s private `justSpawned` list (deduplicates entity spawns) can be accessed via reflection to test debounce behavior.
183
+
184
+
## Dependency Source Lookup
185
+
186
+
When you need to inspect source code for a dependency (e.g., BentoBox, addons):
187
+
188
+
1.**Check local Maven repo first**: `~/.m2/repository/` — sources jars are named `*-sources.jar`
189
+
2.**Check the workspace**: Look for sibling directories or Git submodules that may contain the dependency as a local project (e.g., `../bentoBox`, `../addon-*`)
190
+
3.**Check Maven local cache for already-extracted sources** before downloading anything
191
+
4. Only download a jar or fetch from the internet if the above steps yield nothing useful
192
+
193
+
Prefer reading `.java` source files directly from a local Git clone over decompiling or extracting a jar.
194
+
195
+
In general, the latest version of BentoBox should be targeted.
196
+
197
+
## Project Layout
198
+
199
+
Related projects are checked out as siblings under `~/git/`:
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.
30
+
31
+
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.
25
32
26
33
### blocklimits
27
34
28
-
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.
35
+
The base block-limit section. Values here apply to every environment unless overridden by `blocklimits-nether` / `blocklimits-end` or by a `worlds.<worldname>` entry.
29
36
30
37
### worlds
31
38
32
-
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.
39
+
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.
33
40
34
41
### entitylimits
35
42
36
-
Coming soon!
43
+
Default entity-type limits applied independently per environment. Override per env via `entitylimits-nether` / `entitylimits-end`.
44
+
45
+
### entitygrouplimits
46
+
47
+
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.
37
48
38
49
## Permissions
39
50
40
-
Island owners can have exclusive permissions that override the default or world settings. The format is:
51
+
Island owners can have exclusive permissions that override the default or world settings. Two formats are supported:
41
52
42
-
Format is `GAME-MODE-NAME.island.limit.MATERIAL.LIMIT`
53
+
* All-environment: `<gamemode>.island.limit.<KEY>.<NUMBER>` — applies the limit independently to every environment.
54
+
* Per-environment: `<gamemode>.island.limit.<env>.<KEY>.<NUMBER>` — applies only to one environment, where `<env>` is `overworld`, `nether`, or `end`.
43
55
44
-
example: `bskyblock.island.limit.hopper.10`
56
+
Examples:
57
+
58
+
```
59
+
bskyblock.island.limit.hopper.10 # 10 hoppers in each env
60
+
bskyblock.island.limit.nether.hopper.5 # only 5 hoppers in nether
61
+
bskyblock.island.limit.end.enderman.50 # only 50 endermen in the end
62
+
```
45
63
46
64
Permissions activate when the player logs in.
47
65
66
+
### Migrating an existing server
67
+
68
+
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.
69
+
48
70
Usage permissions are (put the gamemode name, e.g. acidisland at the front):
0 commit comments