Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
101 changes: 101 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,110 @@ Guava (`ImmutableSet`, `ImmutableList`, etc.) is reliably available at runtime v

`Util.componentToLegacy` is therefore **not** a thin wrapper around Adventure's serializer — it's a custom Component walker (`appendComponentLegacy` / `emitStyleTransition`) that tracks the last-emitted color and decorations and inserts `§r` whenever any decoration was on and is now off, then re-applies color afterwards. **Do not replace it with `LegacyComponentSerializer.serialize()` directly** without re-introducing the leak. The round-trip is exercised by `LegacyToMiniMessageTest`.

### Multi-line strings must be parsed as a single unit

`User.convertToLegacy` parses the **whole** translated string at once — never per-line. MiniMessage tags can span newlines (e.g. a `<green>...\n...</green>` block from a multi-line YAML entry, or a multi-line value substituted into a `<green>[description]</green>` template). Splitting on `\n` before parsing orphans close tags: the line `bar</green>` has no opening, and MiniMessage renders `</green>` as **literal text** in the lore. Adventure preserves newlines through `text.content()`, so a single parse handles everything correctly.

### Locale templates: do not wrap placeholders in MiniMessage tags

A template like `<green>[description]</green>` looks harmless but is a trap. Translation placeholders are substituted **as legacy `§`-coded strings** before re-parsing, and they may contain their own colors and newlines. Wrapping them re-introduces the multi-line orphaning problem above and forces the wrapper color over content that already has its own. Leave placeholders bare (`[description]`) and let the value bring its own colors. The `protection.panel.flag-item.{description,menu,setting}-layout` keys all follow this rule across every bundled locale.

### Splitting legacy strings on a literal character collapses same-color runs

`componentToLegacy` does not re-emit a color code when an adjacent text segment has the same color — it relies on the §-code carrying over within the contiguous string. Code that takes a translated legacy string and then `.split("\\|")` (or any literal-character split) breaks this carry-over: subsequent segments lose their color prefix and render in default. If a panel uses `|`-as-line-separator on a translated value, it must propagate the active `§color`/`§format` codes across the split itself, or set lore via Adventure `Component`s instead of legacy `String`s. (See `addon-level/.../DonationPanel.java#splitWithStyleCarryover` for a working pattern.) Bukkit's deprecated `meta.setLore(List<String>)` also does not suppress Minecraft's default lore italic — `meta.lore(List<Component>)` with the `removeDefaultItalic` helper does.

## Build Notes

- The Gradle build uses the Paper `userdev` plugin and Shadow plugin to produce a fat/shaded JAR at `build/libs/BentoBox-{version}.jar`.
- `plugin.yml` and `config.yml` are filtered for the `${version}` placeholder at build time; locale files are copied without filtering.
- Java preview features are enabled for both compilation and test execution.
- Local builds produce version `3.13.0-LOCAL-SNAPSHOT`; CI builds append `-b{BUILD_NUMBER}-SNAPSHOT`; `origin/master` builds produce the bare version.

## 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/`
55 changes: 31 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,39 @@
[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=BentoBoxWorld_BentoBox&metric=security_rating)](https://sonarcloud.io/dashboard?id=BentoBoxWorld_BentoBox)
[![Bugs](https://sonarcloud.io/api/project_badges/measure?project=BentoBoxWorld_BentoBox&metric=bugs)](https://sonarcloud.io/dashboard?id=BentoBoxWorld_BentoBox)

## About BentoBox
# SkyBlock, OneBlock, AcidIsland, and more - all in one plugin

### Description
[![Discord](https://img.shields.io/discord/272499714048524288.svg?logo=discord)](https://discord.bentobox.world)

BentoBox powers island-style game modes for Paper servers. Pick the game modes you want, drop them in, and you're running. No forks, no outdated code — one actively maintained platform that stays current with every Minecraft release.

**Game modes available:**

- **BSkyBlock** — classic SkyBlock, successor to the original ASkyBlock
- **AOneBlock** — the popular OneBlock experience
- **AcidIsland** — survive in a sea of acid
- **Boxed** — expand your world by completing advancements
- **CaveBlock** — underground survival
- **SkyGrid** — scattered blocks, maximum adventure
- **Poseidon** — underwater island challenge
- And more community-created game modes

**Why server admins choose BentoBox:**

BentoBox is a powerful Bukkit library plugin that provides core features for island-style games like SkyBlock, AcidIsland, SkyGrid and others.
These games are added to it via its **unique Addon system**. Further, non-game addons can provide features across games, such as challenges or warps. This enables admins to mix and match games and features to customize their server. It also enables the same code to be run
across games, reducing bugs and speeding updates across all games. For coders,
BentoBox has a **powerful API** allows for quick and easy development of these addons and simplifies complex aspects such as island protection, GUIs, and team management.
- Run multiple game modes on one server with shared features (challenges, warps, levels, leaderboards)
- 20+ addons let you customize exactly the experience you want
- Actively maintained and always up to date with the latest Minecraft version
- Free and open source — used on 1,100+ servers worldwide
- Rich API for developers who want to build custom addons

BentoBox is **[free](https://www.gnu.org/philosophy/free-sw.en.html) and open-source software** so join us to make this platform grow, become even more powerful and popular! Admins can pay to support BentoBox and Addons via donations and sponsorship.
[Full Documentation](https://docs.bentobox.world)

Start now to create the server you've dreamed of!
# Installation

1. Place the BentoBox jar in your plugins folder
2. Start the server
3. Download the game mode and feature addons you want from [this site](https://hangar.papermc.io/BentoboxWorld/) or [download.bentobox.world](https://download.bentobox.world) and place them in the `plugins/BentoBox/addons` folder
4. Restart the server — you're good to go

## Addons
These are some popular Gamemodes:
Expand All @@ -39,13 +60,8 @@ There are also plenty of other official or community-made Addons you can try and
* Start reading: [https://docs.bentobox.world](https://docs.bentobox.world)
* For developers: [Javadocs](https://ci.codemc.io/job/BentoBoxWorld/job/BentoBox/ws/target/apidocs/index.html)

## Downloads

### Webtool
A [webtool](https://download.bentobox.world/) is currently being developed to allow you to easily setup BentoBox and Addons on your server.

### Direct links
* [Download](https://github.com/BentoBoxWorld/BentoBox/releases)
## Bugs or Issues
[File bugs on GitHub](https://github.com/BentoBoxWorld/BentoBox/issues). Confused? Ask on Discord. Note: we are **not** a company, so please be kind with your requests.

### Developers
* [Jenkins](https://ci.codemc.org/job/BentoBoxWorld/job/BentoBox/) (**untested and mostly unstable builds**)
Expand Down Expand Up @@ -116,12 +132,3 @@ dependencies {
```
**Note:** Due to a Gradle issue with versions for Maven, you need to use -SNAPSHOT at the end.

### History

[tastybento](https://github.com/tastybento) created ASkyBlock and AcidIsland that shared the same codebase. These plugins became very popular but became hard to maintain.
[Poslovitch](https://github.com/Poslovitch) was running a Skyblock server before starting to contribute regularly to ASkyBlock's codebase. He proposed the idea of completely rewriting ASkyBlock
to make it easier to maintain and richer in features. In May 2017, this became the *BSkyBlock* project. As development progressed it became clear that a lot of the new core features could be used by other
island-style games and so that core functionality was split off and renamed *BentoBox* and the addon system was created. The addons for BSkyBlock and AcidIsland became very simple to develop and much smaller.
The community started to grow and we added new game modes like SkyGrid and CaveBlock by BONNe. BONNe also took over maintenance of Challenges and Biomes and contributed to other addons.

In December 2019, Poslovitch launched the BentoBox collection on SpigotMC and the story continues!
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArt
group = "world.bentobox" // From <groupId>

// Base properties from <properties>
val buildVersion = "3.14.0"
val buildVersion = "3.14.1"
val buildNumberDefault = "-LOCAL" // Local build identifier
val snapshotSuffix = "-SNAPSHOT" // Indicates development/snapshot version

Expand Down
41 changes: 40 additions & 1 deletion src/main/java/world/bentobox/bentobox/api/flags/Flag.java
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ public enum HideWhen {
private final Type type;
private boolean setting;
private final int defaultRank;
private final int minimumRank;
private final PanelItem.ClickHandler clickHandler;
private final boolean subPanel;
private Set<GameModeAddon> gameModes = new HashSet<>();
Expand All @@ -161,6 +162,7 @@ private Flag(Builder builder) {
this.type = builder.type;
this.setting = builder.defaultSetting;
this.defaultRank = builder.defaultRank;
this.minimumRank = builder.minimumRank;
this.clickHandler = builder.clickHandler;
this.subPanel = builder.usePanel;
if (builder.gameModeAddon != null) {
Expand Down Expand Up @@ -284,6 +286,23 @@ public int getDefaultRank() {
return defaultRank;
}

/**
* @return the minimum rank that may be selected for this flag in the settings
* cycle click. Defaults to {@link RanksManager#VISITOR_RANK}.
* @since 3.13.0
*/
public int getMinimumRank() {
return minimumRank;
}

/**
* @return the click handler associated with this flag's panel item
* @since 3.13.0
*/
public PanelItem.ClickHandler getClickHandler() {
return clickHandler;
}

/**
* @return whether the flag uses a subpanel or not
*/
Expand Down Expand Up @@ -592,6 +611,7 @@ public static class Builder {
// Default settings
private boolean defaultSetting = false;
private int defaultRank = RanksManager.MEMBER_RANK;
private int minimumRank = RanksManager.VISITOR_RANK;

// ClickHandler - default depends on the type
private PanelItem.ClickHandler clickHandler;
Expand Down Expand Up @@ -677,6 +697,19 @@ public Builder defaultRank(int defaultRank) {
return this;
}

/**
* Set the minimum rank that may be selected for this {@link Type#PROTECTION} flag
* in the settings cycle click. The default is {@link RanksManager#VISITOR_RANK}.
* The cycle click listener will not allow ranks below this value to be chosen.
* @param minimumRank minimum rank value (e.g. {@link RanksManager#MEMBER_RANK})
* @return Builder
* @since 3.13.0
*/
public Builder minimumRank(int minimumRank) {
this.minimumRank = minimumRank;
return this;
}

/**
* Set that this flag icon will open up a sub-panel
* @param usePanel - true or false
Expand Down Expand Up @@ -764,12 +797,18 @@ public Builder hideWhen(HideWhen hideWhen) {
* @return Flag
*/
public Flag build() {
// Ensure the default rank is not below the minimum selectable rank
if (defaultRank < minimumRank) {
BentoBox.getInstance().logWarning("Flag " + id + " defaultRank (" + defaultRank
+ ") is below minimumRank (" + minimumRank + "); raising defaultRank to minimumRank.");
defaultRank = minimumRank;
}
// If no clickHandler has been set, then apply default ones
if (clickHandler == null) {
clickHandler = switch (type) {
case SETTING -> new IslandToggleClick(id);
case WORLD_SETTING -> new WorldToggleClick(id);
default -> new CycleClick(id);
default -> new CycleClick(id, minimumRank, RanksManager.OWNER_RANK);
};
}
Flag flag = new Flag(this);
Expand Down
26 changes: 10 additions & 16 deletions src/main/java/world/bentobox/bentobox/api/user/User.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package world.bentobox.bentobox.api.user;

import java.util.Arrays;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
Expand Down Expand Up @@ -509,34 +508,29 @@ public String getTranslation(String reference, String... variables) {
* @return legacy §-coded string
*/
private String convertToLegacy(String raw) {
// Process each line independently to preserve newlines
if (raw.contains("\n")) {
return Arrays.stream(raw.split("\n", -1))
.map(this::convertLineToLegacy)
.collect(Collectors.joining("\n"));
}
return convertLineToLegacy(raw);
}

private String convertLineToLegacy(String line) {
boolean hasLegacy = Util.isLegacyFormat(line);
boolean hasMiniMessage = line.contains("<") && line.contains(">");
boolean hasLegacy = Util.isLegacyFormat(raw);
boolean hasMiniMessage = raw.contains("<") && raw.contains(">");
if (hasLegacy && !hasMiniMessage) {
// Pure legacy — use the old path
@SuppressWarnings("deprecation")
String result = Util.translateColorCodes(line);
String result = Util.translateColorCodes(raw);
return result;
}
// Process the whole string at once (not per line). MiniMessage tags can span
// newlines — splitting first would orphan close tags (e.g. <green>foo\nbar</green>
// becomes "<green>foo" and "bar</green>", and "</green>" with no opening would
// be rendered as literal text). Component text preserves newlines through
// serialization, so a single parse is correct.
if (hasLegacy) {
// Mixed content: MiniMessage tags + legacy & codes.
// Replace legacy codes with MiniMessage opening tags inline (no closing tags).
// MiniMessage handles unclosed tags correctly — they apply until overridden.
// Using legacyToMiniMessage() would produce wrong nesting (e.g.,
// <bold><yellow>text</bold></yellow> where </yellow> leaks as literal text).
line = Util.replaceLegacyCodesInline(line);
raw = Util.replaceLegacyCodesInline(raw);
}
// Parse as MiniMessage and serialize to legacy
return Util.componentToLegacy(Util.parseMiniMessage(line));
return Util.componentToLegacy(Util.parseMiniMessage(raw));
}

/**
Expand Down
6 changes: 3 additions & 3 deletions src/main/resources/locales/cs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1820,16 +1820,16 @@ protection:
<green>Vyberte hodnost, která může</green>
<green>použít příkaz Border</green>
description-layout: |
<green>[description]</green>
[description]

<gray>Povoleno pro:</gray>
allowed-rank: '<dark_aqua>- </dark_aqua><green>[rank]</green>'
blocked-rank: '<dark_aqua>- </dark_aqua><red>[rank]</red>'
minimal-rank: '<dark_aqua>- </dark_aqua><dark_green>[rank]</dark_green>'
menu-layout: '<green>[description]</green>'
menu-layout: '[description]'
setting-cooldown: '<red>Nastavení má cooldown</red>'
setting-layout: |
<green>[description]</green>
[description]

<gray>Nynější nastavení: [setting]</gray>
setting-active: '<green>Povoleno</green>'
Expand Down
6 changes: 3 additions & 3 deletions src/main/resources/locales/de.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1898,16 +1898,16 @@ protection:
<green>Wähle den Rang, der</green>
<green>den Border-Befehl nutzen darf</green>
description-layout: |-
<green>[description]</green>
[description]

<gray>Erlaubt für:</gray>
allowed-rank: '<dark_aqua>- </dark_aqua><green>[rank]</green>'
blocked-rank: '<dark_aqua>- </dark_aqua><red>[rank]</red>'
minimal-rank: '<dark_aqua>- </dark_aqua><dark_green>[rank]</dark_green>'
menu-layout: '<green>[description]</green>'
menu-layout: '[description]'
setting-cooldown: '<red>Einstellung ist auf Abklingzeit</red>'
setting-layout: |-
<green>[description]</green>
[description]

<gray>Aktuelle Einstellung: [setting]</gray>
setting-active: '<green>Aktiviert</green>'
Expand Down
Loading
Loading