Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
962cddf
Update Modrinth game-versions to current MC range
tastybento Jun 3, 2026
afca312
Merge pull request #128 from BentoBoxWorld/chore/modrinth-game-versions
tastybento Jun 3, 2026
104efe6
Document using a custom map as the seed world
tastybento Jun 12, 2026
2d933b4
Document native BentoBox region deletion; demote Regionerator
tastybento Jun 12, 2026
b3c34f6
Bump BentoBox dependency to 3.17.0
tastybento Jun 12, 2026
8fe4aa9
Update CLAUDE.md to BentoBox 3.17.0
tastybento Jun 13, 2026
6538427
Add files via upload
tastybento Jun 28, 2026
1c2989b
ci: add CurseForge publish workflow (publishes the release asset)
tastybento Jun 28, 2026
fc8a754
ci: pin reusable workflow to SHA and enable Hangar publish
tastybento Jun 28, 2026
9515226
ci: bump pinned reusable workflow to 71bf927bce32586216baa6995f21852d…
tastybento Jun 28, 2026
d663004
Merge pull request #129 from BentoBoxWorld/ci/curseforge-publish
tastybento Jun 28, 2026
53061cf
ci: bump pinned reusable workflow to fe4b1f0
tastybento Jul 1, 2026
fb25f32
Merge pull request #130 from BentoBoxWorld/ci/bump-publish-workflow-f…
tastybento Jul 1, 2026
46b2903
ci: bump pinned publish-platforms.yml to ca2dcd1
tastybento Jul 3, 2026
58a8ae4
Merge pull request #131 from BentoBoxWorld/ci/bump-publish-platforms-…
tastybento Jul 3, 2026
6411884
ci: bump publish-platforms pin to the multipart-JSON fix
tastybento Jul 29, 2026
b12dc74
Merge pull request #132 from BentoBoxWorld/ci/publish-platforms-pin
tastybento Jul 30, 2026
0656408
Fix open SonarCloud issues (blockers through info)
tastybento Aug 15, 2026
285f5ef
Address remaining SonarCloud findings on PR #133
tastybento Aug 15, 2026
fe2bafa
Merge pull request #133 from BentoBoxWorld/sonar/fix-open-issues
tastybento Aug 15, 2026
7c58bb7
docs: clarify which deaths settings affect Level 2.29.0 island levels
tastybento Sep 7, 2026
1476a35
Merge pull request #134 from BentoBoxWorld/docs/deaths-settings-level…
tastybento Sep 7, 2026
2664b71
Fix "contloc out of spec" crash when continentalness < -1.2
tastybento Sep 25, 2026
1745ffb
Merge pull request #136 from BentoBoxWorld/fix/135-contloc-out-of-spec
tastybento Sep 25, 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
10 changes: 9 additions & 1 deletion .github/workflows/modrinth-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ jobs:
paper
spigot
game-versions: |-
1.21.4
1.21.5
1.21.6
1.21.7
1.21.8
1.21.9
1.21.10
1.21.11
26.1
26.1.1
26.1.2
files: /home/runner/work/Boxed/Boxed/target/Boxed-${{ github.event.release.tag_name }}.jar
30 changes: 30 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Boxed — .github/workflows/publish.yml
# Publishes the jar attached to a GitHub release to CurseForge and Hangar via the
# shared BentoBoxWorld/.github reusable workflow. Downloads the release asset instead of
# rebuilding from source. The reusable workflow is pinned to a commit SHA (Sonar
# githubactions:S7637). workflow_dispatch lets you (re)publish a given version.

name: Publish release to CurseForge and Hangar

on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: "Version to publish (e.g. 1.2.3)"
required: true
type: string

jobs:
publish:
uses: bentoboxworld/.github/.github/workflows/publish-platforms.yml@1f91a0edf72e8c86d671b3b8fdd3121ac6fb88e1 # master
with:
use_release_asset: "true" # publish the jar attached to the release; do not rebuild
hangar_slug: "Boxed" # blank = skip Hangar
curseforge_id: "1515492"
game_versions: "26.2,26.1.2,26.1.1,26.1,1.21.11,1.21.10,1.21.9,1.21.8,1.21.7,1.21.6,1.21.5"
version: ${{ inputs.version }} # empty on release events -> falls back to the release tag
secrets:
HANGAR_API_KEY: ${{ secrets.HANGAR_API_KEY }}
CURSEFORGE_TOKEN: ${{ secrets.CURSEFORGE_TOKEN }}
4 changes: 1 addition & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## Project

Boxed is a BentoBox GameModeAddon for Minecraft (Paper) where each player is confined to a small expandable box. Completing Minecraft advancements grows the box. Built against `bentobox` 3.13.0, Paper API 1.21.11, Java 21.
Boxed is a BentoBox GameModeAddon for Minecraft (Paper) where each player is confined to a small expandable box. Completing Minecraft advancements grows the box. Built against `bentobox` 3.17.0, Paper API 1.21.11, Java 21.

## Build / Test

Expand All @@ -31,8 +31,6 @@ This is the core concept and touches almost everything:

This is why first boot is extremely slow and RAM-hungry (see `README.md` warnings): the entire seed region is force-loaded up front. Any change to world generation, structure handling, or world naming must respect both worlds and the copy step in `Boxed.copyChunks()` / `createOverWorld()` / `createNether()`. The `generatorMaps` / `generatorMap` fields in `Boxed.java` route world names → generators for `getDefaultWorldGenerator` (used by Multiverse and similar world-management plugins) and for the hook in `allLoaded()` that calls `WorldManagementHook.registerWorld`.

`isUsesNewChunkGeneration()` returns `true`, which tells BentoBox this addon uses the modern chunk-generation API.

### Advancements drive box size

`AdvancementsManager` is the other key subsystem. Box growth is data-driven from `advancements.yml`: each advancement key maps to an integer "box growth" increment. `AdvancementListener` watches for player advancement events and asks the manager to update the island's protection-range. Per-island state lives in `objects/IslandAdvancements.java` (a BentoBox `DataObject` persisted via its database layer). `AdvancementsManager.save()` is called in `onDisable()` — any new cached state it holds should be flushed there too.
Expand Down
124 changes: 111 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A game mode where you are boxed into a tiny space that only expands by completin

## BentoBox Requirements

* Requires BentoBox 1.23.0 or later (Snapshots can be downloaded here: [https://ci.bentobox.world](https://ci.bentobox.world))
* Requires BentoBox 3.17.0 or later (Snapshots can be downloaded here: [https://ci.bentobox.world](https://ci.bentobox.world)). BentoBox 3.16.1+ is needed for native region-file cleanup of deleted boxes — see [Reclaiming Disk Space](#reclaiming-disk-space-deleted-island-chunks).
* InvSwitcher - keeps advancements, inventory, etc. separate between worlds on a server.
* Border - shows the box

Expand Down Expand Up @@ -52,6 +52,8 @@ Each player will have a land of their own to explore up to the limit of the isla
*World Seed*
The world seed is used to generate the lands. It is recommended to keep this value. If you change it the land may be very different. Note that changing the seed mid-game requires a full reset of your databases and worlds.

Want to use your own terrain or a custom-built map instead? See [Using a Custom Map as the Seed](#using-a-custom-map-as-the-seed).

*Key Boxed-specific settings:*

| Setting | Default | Description |
Expand Down Expand Up @@ -127,6 +129,82 @@ To undo the last placed structure: `/boxadmin place undo`
When a structure is placed via this command while standing in a player box, it is automatically saved to `structures.yml` and will be placed in all future boxes.


## Using a Custom Map as the Seed

Boxed does **not** have an "import map" button, but because of how it works you can still get your own custom terrain into player boxes. This section explains how.

### How seeding actually works

Boxed uses two worlds:

1. A hidden **seed world** — `<worldname>/seed` (overworld) and `<worldname>/seed_nether` (nether). This is a normal world generated from the numeric world seed.
2. The **game world** — `<worldname>` and `<worldname>_nether` — that players actually play in.

On **every server start**, Boxed reads the chunks around the centre of the seed world and copies them into the game world's generator. Each player's box is then served a copy of that captured terrain.

Two facts make custom maps possible:

* The copy reads the seed world **from disk**, live, on every boot — so anything you change in the seed world flows into newly generated boxes.
* The game world **copies the seed world's biomes as-is**. There is no biome remapping when a chunk is copied, so whatever biomes are in the seed world are what players get.

Two limits to keep in mind:

* The seed-world centre is fixed at **x = 0, z = 0** (y ≈ 64). Your custom content must be built around 0,0.
* Only a square of radius **island distance** (the `world.island-distance` value, default 320 → a 640 × 640 area) around 0,0 is copied. Anything outside that is just the surrounding seas and is never used.
* Once a game-world chunk has been generated and saved to disk, it is loaded from disk and is **no longer** taken from the seed world. So changing the seed world only affects boxes/areas that have **not yet been generated**. To push changes into already-explored areas you must regenerate those game-world chunks (see methods below).

> **Always back up your worlds before trying any of this.** These are unsupported, manual techniques.

### Method 1 — Match an existing world's seed (easiest)

If you just want the same *terrain* as a world you already like, set Boxed's seed to that world's numeric seed.

1. Find the seed of the world you like (`/seed` in that world).
2. In `config.yml` set:
```yaml
world:
generator:
seed: <that-number>
```
3. Optionally enable vanilla structures with `world.allow-structures: true`.
4. Start with **fresh** Boxed worlds (the seed cannot be changed mid-game — delete the Boxed worlds and database, or set this up before first boot).

Notes: this reproduces vanilla terrain for that seed. Boxed still applies its own biome overlay (`biomes.yml`) to the box area, and structures only appear if `world.allow-structures` is enabled. This does **not** let you import hand-built creations — for that use Method 2 or 3.

### Method 2 — Edit the seed world by hand

You can go into the seed world and build, paste, or remove whatever you like.

1. Use a world-management plugin (e.g. Multiverse) to teleport into `<worldname>/seed`.
2. Build or WorldEdit/paste your changes **around 0,0**, within the island-distance radius.
3. Restart the server. On boot, Boxed re-copies the seed world, so your edits appear in any box that is generated **after** the restart.

To apply your edits to boxes/areas that already exist, you must regenerate those game-world chunks while leaving the seed world untouched — for example by deleting the relevant region files in `<worldname>` / `<worldname>_nether`, or by resetting/deleting the affected islands so BentoBox reaps their region files (see [Reclaiming Disk Space](#reclaiming-disk-space-deleted-island-chunks)). When the chunks regenerate they are re-copied from the seed world, which is the source of truth.

### Method 3 — Drop in your own world as the seed

This replaces the generated seed world with a world you built or downloaded.

1. Build or obtain a normal (vanilla) Minecraft world. Arrange the terrain you want **centred on 0,0**, covering at least the island-distance radius (default 320 blocks in every direction from 0,0).
2. Stop the server.
3. Copy your world's region files into Boxed's seed world folders, replacing what's there:
* Overworld: your `region/` → `<worldname>/seed/region/`
* Nether: your `DIM-1/region/` → `<worldname>/seed_nether/region/`
4. Delete the game-world folders so they regenerate from your new seed world on next boot:
* `<worldname>` and `<worldname>_nether`
* Leave the seed worlds (`<worldname>/seed`, `<worldname>/seed_nether`) in place.
5. Start the server. Boxed copies your imported chunks — terrain **and biomes** — into the freshly generated game world.

Biomes: because the game world copies the seed world's biomes unchanged, your imported biomes carry over automatically. The `biomes.yml` overlay only affects chunks that Boxed *generates*; it does not touch the pre-existing chunks you dropped in, so your map's biomes are preserved as-is.

### Tips and gotchas

* The copy radius is `world.island-distance`. If you increase it, your custom content must cover the larger area.
* BentoBox's native region cleanup never touches the seed worlds, so your custom terrain is safe. If you run a third-party chunk-cleaner like Regionerator, exempt the seed worlds (see [Reclaiming Disk Space](#reclaiming-disk-space-deleted-island-chunks)) or your custom seed terrain may be deleted and boxes will stop matching.
* The first boot after importing is slow and RAM-hungry, just like a normal first start (see the warning near the top of this file).
* All boxes share the same captured terrain, so every player gets the same custom map layout.


## Flags

Boxed registers two flags unique to this gamemode.
Expand Down Expand Up @@ -163,27 +241,47 @@ To find out how to add custom advancements to your server, watch the tutorial vi
Download the official [Boxed DataPack](https://github.com/BentoBoxWorld/BoxedDataPack) for extra custom advancements.


## Using Regionerator
## Reclaiming Disk Space (Deleted Island Chunks)

**As of BentoBox 3.16.1, BentoBox reclaims disk space natively — you no longer need a third-party plugin for this.** (The region-file housekeeping sweep was added in 3.15.0; admin deletes were routed through it in 3.16.1.)

### How it works now

*Note: This plugin is designed to delete unused regions of your world! Make sure you take backups if you use it! Use at your own risk!*
When an island is deleted or reset, BentoBox does **not** wipe blocks immediately. Instead it *soft-deletes* the island: the owner is cleared, the box is locked, and the island is flagged `deletable` in the database. The actual world data is reclaimed later by deleting the `.mca` region files from disk — far cheaper than regenerating chunks block-by-block.

[Regionerator](https://github.com/Jikoo/Regionerator) is a plugin that gradually deletes unused chunks to keep world sizes low. It supports BentoBox and respects box boundaries. It can be used to delete box chunks so that they can be regenerated. As Boxed uses seed worlds to copy from, these can appear to be unused by Regionerator and deleted, which means that startup becomes very slow. To avoid this, set the seed worlds as exempt from its deletions by adding these entries to the `worlds` section of the Regionerator config file:
A background **housekeeping sweep** does the reaping automatically. It is on by default:

| Setting (BentoBox `config.yml`) | Default | Description |
|---------|---------|-------------|
| `island.deletion.housekeeping.deleted-sweep.enabled` | `true` | Reaps region files for islands BentoBox itself soft-deleted (e.g. `/box reset`, admin delete). Never touches active or unvisited islands. |
| `island.deletion.housekeeping.deleted-sweep.interval-hours` | `24` | How often the deleted-island sweep runs. |
| `island.deletion.housekeeping.age-sweep.enabled` | `false` | Opt-in: also reaps region files older than `min-age-days`, even if never reset. Use to reclaim abandoned boxes. |
| `island.deletion.housekeeping.age-sweep.min-age-days` | `60` | Minimum age before the age-based sweep will reap a region. |

A region is only reaped if **every** island overlapping it is deletable — a single active neighbour protects the whole region. To reap immediately instead of waiting for the next sweep, run:

```
/boxadmin purge deleted
```

### Your seed worlds are safe

The sweep only scans gamemode island worlds and only the region files belonging to **deletable islands**. Boxed's seed worlds (`boxed_world/seed`, `boxed_world/seed_nether`) contain no islands, so they are never scanned and never reaped — your captured seed terrain is left intact. No exemption configuration is required.

> The old advice to set `deletion.keep-previous-island-on-reset` no longer applies — that setting is ignored in current BentoBox; deletion is handled by the housekeeping sweep described above.

### Regionerator (legacy / optional)

You can still run [Regionerator](https://github.com/Jikoo/Regionerator) if you prefer, but it is no longer necessary for Boxed. If you do use it, exempt the seed worlds so it doesn't delete the terrain Boxed copies from (which would make startup very slow). Add these entries to the `worlds` section of the Regionerator config:

```yaml
worlds:
boxed_world/seed_base:
days-till-flag-expires: -1
boxed_world/seed:
days-till-flag-expires: -1
boxed_world/seed_nether:
days-till-flag-expires: -1
default:
days-till-flag-expires: 0
```

To get the most out of Regionerator, change the BentoBox `config.yml` to *not* delete chunks when an island is removed. This leaves deletion up to Regionerator and it will clean up the chunks if the unused area is large enough. Set `keep-previous-island-on-reset: true`:

```yaml
deletion:
keep-previous-island-on-reset: true
```


2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
<mock-bukkit.version>4.110.0</mock-bukkit.version>
<!-- More visible way how to change dependency versions -->
<paper.version>1.21.11-R0.1-SNAPSHOT</paper.version>
<bentobox.version>3.13.0</bentobox.version>
<bentobox.version>3.17.0</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. -->
Expand Down
12 changes: 0 additions & 12 deletions src/main/java/world/bentobox/boxed/AdvancementsManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -62,18 +62,6 @@ public AdvancementsManager(Boxed addon) {
addon.logError("advancements.yml cannot be found! " + e.getLocalizedMessage());
}
}
/*
// DEBUG - lists all advancements to console
int scoreTotal = 0;
Iterator<Advancement> ad = Bukkit.getServer().advancementIterator();
while (ad.hasNext()) {
Advancement a = ad.next();
int score = getScore(a);
BentoBox.getInstance().logDebug(" 'minecraft:" + a.getKey().getKey() + "': " + score);
scoreTotal += score;
}
BentoBox.getInstance().logDebug("Sum total = " + scoreTotal);
*/
}

/**
Expand Down
11 changes: 1 addition & 10 deletions src/main/java/world/bentobox/boxed/Boxed.java
Original file line number Diff line number Diff line change
Expand Up @@ -186,11 +186,7 @@ public void createWorlds() {
if (settings.isNetherGenerate()) {
createNether(worldName);
}
/*
// Make the end if it does not exist
if (settings.isEndGenerate()) {
//TODO
*/
// The End is not supported yet
}

private void createNether(String worldName) {
Expand Down Expand Up @@ -400,9 +396,4 @@ public void allLoaded() {
public AdvancementsManager getAdvManager() {
return advManager;
}

@Override
public boolean isUsesNewChunkGeneration() {
return true;
}
}
7 changes: 3 additions & 4 deletions src/main/java/world/bentobox/boxed/PlaceholdersManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,11 @@ public String getCount(User user) {
* @return string of advancement count
*/
public String getCountByLocation(User user) {
if (user != null && user.getUniqueId() != null && user.getLocation() != null) {
return addon.getIslands().getIslandAt(user.getLocation())
.map(i -> String.valueOf(addon.getAdvManager().getIsland(i).getAdvancements().size())).orElse("");
} else {
if (user == null || user.getUniqueId() == null) {
return "";
}
return addon.getIslands().getIslandAt(user.getLocation())
.map(i -> String.valueOf(addon.getAdvManager().getIsland(i).getAdvancements().size())).orElse("");
}


Expand Down
12 changes: 10 additions & 2 deletions src/main/java/world/bentobox/boxed/Settings.java
Original file line number Diff line number Diff line change
Expand Up @@ -441,18 +441,24 @@ public class Settings implements WorldSettings {

// Deaths
@ConfigComment("Whether deaths are counted or not.")
@ConfigComment("If false, BentoBox does not count deaths and the Level addon does not record deaths against areas.")
@ConfigEntry(path = "area.deaths.counted")
private boolean deathsCounted = true;

@ConfigComment("Maximum number of deaths to count. The death count can be used by add-ons.")
@ConfigComment("Since Level 2.29.0 this also caps how many deaths each member can contribute to an area's death penalty.")
@ConfigEntry(path = "area.deaths.max")
private int deathsMax = 10;

@ConfigComment("When a player joins a team, reset their death count")
@ConfigComment("When a player joins a team, reset their death count.")
@ConfigComment("This only affects BentoBox's own per-player death count, used by the %boxed_deaths% placeholder.")
@ConfigComment("Since Level 2.29.0, area levels use per-area death tracking and are not affected by this setting.")
@ConfigEntry(path = "area.deaths.team-join-reset")
private boolean teamJoinDeathReset = true;

@ConfigComment("Reset player death count when they start a new area or reset an area")
@ConfigComment("Reset player death count when they start a new area or reset an area.")
@ConfigComment("This only affects BentoBox's own per-player death count.")
@ConfigComment("Since Level 2.29.0 the Level addon clears an area's own death record automatically when it is reset or deleted.")
@ConfigEntry(path = "area.deaths.reset-on-new-area")
private boolean deathsResetOnNewIsland = true;

Expand Down Expand Up @@ -1777,6 +1783,7 @@ public void setIgnoreAdvancements(boolean ignoreAdvancements) {
/**
* @return the concurrentIslands
*/
@Override
public int getConcurrentIslands() {
if (concurrentIslands <= 0) {
return BentoBox.getInstance().getSettings().getIslandNumber();
Expand All @@ -1794,6 +1801,7 @@ public void setConcurrentIslands(int concurrentIslands) {
/**
* @return the disallowTeamMemberIslands
*/
@Override
public boolean isDisallowTeamMemberIslands() {
return disallowTeamMemberIslands;
}
Expand Down
Loading
Loading