Skip to content

Commit 2dc791c

Browse files
authored
Merge pull request #2916 from BentoBoxWorld/develop
Release 3.14 PI
2 parents bb4e2ea + c25e165 commit 2dc791c

File tree

82 files changed

+6432
-3003
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

82 files changed

+6432
-3003
lines changed

CLAUDE.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@ The main plugin class is `BentoBox.java` (extends `JavaPlugin`). Almost all subs
3232
### Key Packages
3333

3434
- **`api/`** — Public API surface for addons: events, commands, panels (GUIs), user management, flags, configuration
35-
- **`managers/`** — Core subsystems: `IslandsManager`, `PlayersManager`, `AddonsManager`, `LocalesManager`, `FlagsManager`, `BlueprintsManager`, `HooksManager`, `PlaceholdersManager`, `RanksManager`
36-
- **`database/`** — Database abstraction supporting MongoDB, MySQL, MariaDB, and PostgreSQL (via HikariCP)
35+
- **`managers/`** — Core subsystems: `IslandsManager`, `PlayersManager`, `AddonsManager`, `LocalesManager`, `FlagsManager`, `BlueprintsManager`, `BlueprintClipboardManager`, `HooksManager`, `PlaceholdersManager`, `RanksManager`, `CommandsManager`, `IslandDeletionManager`, `IslandChunkDeletionManager`, `MapManager`, `WebManager`
36+
- **`database/`** — Database abstraction supporting MongoDB, MySQL, MariaDB, PostgreSQL (via HikariCP), and SQLite
3737
- **`blueprints/`** — Island schematic handling and pasting
3838
- **`listeners/`** — Bukkit event handlers (teleport, death, join/leave, panel clicks, spawn protection)
3939
- **`commands/`** — Admin and user command implementations
4040
- **`panels/`** — Inventory GUI panel system
41-
- **`hooks/`** — Integrations with external plugins (Vault, PlaceholderAPI, MythicMobs, Multiverse, LuckPerms, etc.)
41+
- **`hooks/`** — Integrations with external plugins (Vault, PlaceholderAPI, MythicMobs, Multiverse, LuckPerms, ItemsAdder, Slimefun, Oraxen, ZNPCsPlus, FancyNpcs, BlueMap, Dynmap, LangUtils, etc.)
4242
- **`nms/`** — NMS (Native Minecraft Server) version-specific code
4343

4444
### Island Data Flow
@@ -118,9 +118,17 @@ public ImmutableSet<UUID> getMemberSet() { ... }
118118

119119
Guava (`ImmutableSet`, `ImmutableList`, etc.) is reliably available at runtime via Paper's bundled JARs and is safe to use in the public API.
120120

121+
## MiniMessage / legacy color round-trip
122+
123+
`User.getTranslation()` returns a legacy `§`-coded string for backwards compatibility, even when the locale entry is MiniMessage. UI code (`PanelItem.setDescription`, etc.) then re-parses that legacy string back into a Component via `Util.parseMiniMessageOrLegacy`. This MiniMessage → Component → legacy → Component round-trip is lossy by default because of an Adventure quirk:
124+
125+
**Adventure's `LegacyComponentSerializer` never emits `§r` to turn off a decoration when a sibling component clears it.** Legacy color codes have no "decoration off" code — only `§r` resets — but Adventure simply omits the decoration code on the next sibling instead of resetting. When that legacy string is re-parsed under correct legacy semantics (decorations persist until `§r`), the decoration leaks into the following segment. This bit bold, italic, underlined, strikethrough, and obfuscated equally (#2917).
126+
127+
`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`.
128+
121129
## Build Notes
122130

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

build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArt
4646
group = "world.bentobox" // From <groupId>
4747

4848
// Base properties from <properties>
49-
val buildVersion = "3.13.0"
49+
val buildVersion = "3.14.0"
5050
val buildNumberDefault = "-LOCAL" // Local build identifier
5151
val snapshotSuffix = "-SNAPSHOT" // Indicates development/snapshot version
5252

src/main/java/world/bentobox/bentobox/BentoBox.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import world.bentobox.bentobox.managers.CommandsManager;
3232
import world.bentobox.bentobox.managers.FlagsManager;
3333
import world.bentobox.bentobox.managers.HooksManager;
34+
import world.bentobox.bentobox.managers.ChunkPregenManager;
3435
import world.bentobox.bentobox.managers.IslandDeletionManager;
3536
import world.bentobox.bentobox.managers.IslandWorldManager;
3637
import world.bentobox.bentobox.managers.IslandsManager;
@@ -70,6 +71,7 @@ public class BentoBox extends JavaPlugin implements Listener {
7071
private PlaceholdersManager placeholdersManager;
7172
private MapManager mapManager;
7273
private IslandDeletionManager islandDeletionManager;
74+
private ChunkPregenManager chunkPregenManager;
7375
private WebManager webManager;
7476

7577
// Settings
@@ -283,6 +285,7 @@ private void registerListeners() {
283285
BentoBoxListenerRegistrar registrar = new BentoBoxListenerRegistrar(this);
284286
registrar.register();
285287
islandDeletionManager = registrar.getIslandDeletionManager();
288+
chunkPregenManager = registrar.getChunkPregenManager();
286289
}
287290

288291
@Override
@@ -302,7 +305,9 @@ public void onDisable() {
302305
if (islandsManager != null) {
303306
islandsManager.shutdown();
304307
}
305-
308+
if (chunkPregenManager != null) {
309+
chunkPregenManager.shutdown();
310+
}
306311

307312
}
308313

@@ -553,6 +558,14 @@ public IslandDeletionManager getIslandDeletionManager() {
553558
return islandDeletionManager;
554559
}
555560

561+
/**
562+
* @return the chunkPregenManager
563+
* @since 3.14.0
564+
*/
565+
public ChunkPregenManager getChunkPregenManager() {
566+
return chunkPregenManager;
567+
}
568+
556569
/**
557570
* @return an optional of the Bstats instance
558571
* @since 1.1

src/main/java/world/bentobox/bentobox/Settings.java

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,32 @@ public class Settings implements ConfigObject {
340340
@ConfigEntry(path = "island.deletion.slow-deletion", since = "1.19.1")
341341
private boolean slowDeletion = false;
342342

343+
// Chunk pre-generation settings
344+
@ConfigComment("")
345+
@ConfigComment("Chunk pre-generation settings.")
346+
@ConfigComment("Pre-generates chunks around predicted future island locations")
347+
@ConfigComment("to reduce lag when islands are created or reset.")
348+
@ConfigComment("")
349+
@ConfigComment("Enable or disable chunk pre-generation.")
350+
@ConfigEntry(path = "island.pregeneration.enabled", since = "3.14.0")
351+
private boolean pregenEnabled = false;
352+
353+
@ConfigComment("Number of future islands to pre-generate chunks for, per game mode world.")
354+
@ConfigComment("Higher values use more disk space but reduce the chance of lag on island creation.")
355+
@ConfigEntry(path = "island.pregeneration.islands-ahead", since = "3.14.0")
356+
private int pregenIslandsAhead = 3;
357+
358+
@ConfigComment("Maximum number of async chunk generation requests dispatched per tick batch.")
359+
@ConfigComment("Lower values reduce server impact. Paper handles these asynchronously.")
360+
@ConfigComment("Recommended range: 2-10.")
361+
@ConfigEntry(path = "island.pregeneration.chunks-per-tick", since = "3.14.0")
362+
private int pregenChunksPerTick = 4;
363+
364+
@ConfigComment("Ticks between pre-generation batches.")
365+
@ConfigComment("1 = every tick (fastest), 20 = once per second (gentlest).")
366+
@ConfigEntry(path = "island.pregeneration.tick-interval", since = "3.14.0")
367+
private int pregenTickInterval = 5;
368+
343369
@ConfigComment("By default, If the destination is not safe, the plugin will try to search for a safe spot around the destination,")
344370
@ConfigComment("then it will try to expand the y-coordinate up and down from the destination.")
345371
@ConfigComment("This setting limits how far the y-coordinate will be expanded.")
@@ -376,6 +402,13 @@ public class Settings implements ConfigObject {
376402
@ConfigEntry(path = "island.obsidian-scooping-cooldown", since = "3.11.4")
377403
private int obsidianScoopingCooldown = 1;
378404

405+
@ConfigComment("How long (in seconds) to show a hologram tip above newly formed obsidian")
406+
@ConfigComment("that can be scooped back into lava. The hologram reminds players they can")
407+
@ConfigComment("right-click obsidian with an empty bucket to recover lava.")
408+
@ConfigComment("Set to 0 or less to disable the tip entirely. Default is 5 seconds.")
409+
@ConfigEntry(path = "island.obsidian-scooping-lava-tip-duration", since = "3.14.0")
410+
private int obsidianScoopingLavaTipDuration = 5;
411+
379412
/* WEB */
380413
@ConfigComment("Toggle whether BentoBox can connect to GitHub to get data about updates and addons.")
381414
@ConfigComment("Disabling this will result in the deactivation of the update checker and of some other")
@@ -995,6 +1028,70 @@ public void setSlowDeletion(boolean slowDeletion) {
9951028
this.slowDeletion = slowDeletion;
9961029
}
9971030

1031+
/**
1032+
* @return whether chunk pre-generation is enabled
1033+
* @since 3.14.0
1034+
*/
1035+
public boolean isPregenEnabled() {
1036+
return pregenEnabled;
1037+
}
1038+
1039+
/**
1040+
* @param pregenEnabled whether chunk pre-generation is enabled
1041+
* @since 3.14.0
1042+
*/
1043+
public void setPregenEnabled(boolean pregenEnabled) {
1044+
this.pregenEnabled = pregenEnabled;
1045+
}
1046+
1047+
/**
1048+
* @return number of future islands to pre-generate per game mode
1049+
* @since 3.14.0
1050+
*/
1051+
public int getPregenIslandsAhead() {
1052+
return pregenIslandsAhead;
1053+
}
1054+
1055+
/**
1056+
* @param pregenIslandsAhead number of future islands to pre-generate
1057+
* @since 3.14.0
1058+
*/
1059+
public void setPregenIslandsAhead(int pregenIslandsAhead) {
1060+
this.pregenIslandsAhead = pregenIslandsAhead;
1061+
}
1062+
1063+
/**
1064+
* @return max async chunk generation requests per tick batch
1065+
* @since 3.14.0
1066+
*/
1067+
public int getPregenChunksPerTick() {
1068+
return pregenChunksPerTick;
1069+
}
1070+
1071+
/**
1072+
* @param pregenChunksPerTick max async chunk requests per tick
1073+
* @since 3.14.0
1074+
*/
1075+
public void setPregenChunksPerTick(int pregenChunksPerTick) {
1076+
this.pregenChunksPerTick = pregenChunksPerTick;
1077+
}
1078+
1079+
/**
1080+
* @return ticks between pre-generation batches
1081+
* @since 3.14.0
1082+
*/
1083+
public int getPregenTickInterval() {
1084+
return pregenTickInterval;
1085+
}
1086+
1087+
/**
1088+
* @param pregenTickInterval ticks between batches
1089+
* @since 3.14.0
1090+
*/
1091+
public void setPregenTickInterval(int pregenTickInterval) {
1092+
this.pregenTickInterval = pregenTickInterval;
1093+
}
1094+
9981095
/**
9991096
* Gets maximum pool size.
10001097
*
@@ -1153,6 +1250,28 @@ public void setObsidianScoopingCooldown(int obsidianScoopingCooldown) {
11531250
this.obsidianScoopingCooldown = Math.max(1, obsidianScoopingCooldown);
11541251
}
11551252

1253+
/**
1254+
* Gets the duration (in seconds) for showing the lava tip hologram above
1255+
* newly formed obsidian blocks that can be scooped.
1256+
*
1257+
* @return the lava tip duration in seconds; 0 or less means disabled
1258+
* @since 3.14.0
1259+
*/
1260+
public int getObsidianScoopingLavaTipDuration() {
1261+
return obsidianScoopingLavaTipDuration;
1262+
}
1263+
1264+
/**
1265+
* Sets the duration (in seconds) for showing the lava tip hologram above
1266+
* newly formed obsidian blocks that can be scooped.
1267+
*
1268+
* @param obsidianScoopingLavaTipDuration the duration in seconds; 0 or less disables
1269+
* @since 3.14.0
1270+
*/
1271+
public void setObsidianScoopingLavaTipDuration(int obsidianScoopingLavaTipDuration) {
1272+
this.obsidianScoopingLavaTipDuration = obsidianScoopingLavaTipDuration;
1273+
}
1274+
11561275
/**
11571276
* @return the islandNumber
11581277
* @since 2.0.0

src/main/java/world/bentobox/bentobox/api/addons/GameModeAddon.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,4 +199,21 @@ public boolean isFixIslandCenter() {
199199
public boolean isEnforceEqualRanges() {
200200
return true;
201201
}
202+
203+
/**
204+
* Returns the number of islands to pre-generate chunks for in this game mode.
205+
* <p>
206+
* Override this method to customize pre-generation for your addon:
207+
* <ul>
208+
* <li>{@code -1} — use the global BentoBox setting (default)</li>
209+
* <li>{@code 0} — disable pre-generation for this addon</li>
210+
* <li>positive value — pre-generate this many islands ahead</li>
211+
* </ul>
212+
*
213+
* @return number of islands ahead, -1 for global default, 0 to disable
214+
* @since 3.14.0
215+
*/
216+
public int getPregenIslandsAhead() {
217+
return -1;
218+
}
202219
}

src/main/java/world/bentobox/bentobox/api/commands/admin/AdminSettingsCommand.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import world.bentobox.bentobox.api.user.User;
2222
import world.bentobox.bentobox.database.objects.Island;
2323
import world.bentobox.bentobox.managers.RanksManager;
24+
import world.bentobox.bentobox.panels.settings.IslandDefaultSettingsTab;
2425
import world.bentobox.bentobox.panels.settings.SettingsTab;
2526
import world.bentobox.bentobox.panels.settings.WorldDefaultSettingsTab;
2627
import world.bentobox.bentobox.util.Util;
@@ -224,6 +225,7 @@ public boolean execute(User user, String label, List<String> args) {
224225
.world(getWorld())
225226
.tab(1, new SettingsTab(getWorld(), user, Flag.Type.WORLD_SETTING, Flag.Mode.EXPERT))
226227
.tab(2, new WorldDefaultSettingsTab(getWorld(), user))
228+
.tab(3, new IslandDefaultSettingsTab(getWorld(), user))
227229
.startingSlot(1)
228230
.size(54)
229231
.build().openPanel();

src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintListCommand.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,17 @@ public boolean execute(User user, String label, List<String> args)
5151
return false;
5252
}
5353

54-
FilenameFilter blueprintFilter = (File dir, String name) -> name.endsWith(BlueprintsManager.BLUEPRINT_SUFFIX);
54+
FilenameFilter blueprintFilter = (File dir, String name) ->
55+
name.endsWith(BlueprintsManager.BLUEPRINT_SUFFIX) || name.endsWith(BlueprintsManager.LEGACY_BLUEPRINT_SUFFIX);
5556

5657
List<String> blueprintList = Arrays.stream(Objects.requireNonNull(blueprints.list(blueprintFilter))).
57-
map(name -> name.substring(0, name.length() - BlueprintsManager.BLUEPRINT_SUFFIX.length())).
58+
map(name -> {
59+
if (name.endsWith(BlueprintsManager.BLUEPRINT_SUFFIX)) {
60+
return name.substring(0, name.length() - BlueprintsManager.BLUEPRINT_SUFFIX.length());
61+
}
62+
return name.substring(0, name.length() - BlueprintsManager.LEGACY_BLUEPRINT_SUFFIX.length());
63+
}).
64+
distinct().
5865
toList();
5966

6067
if (blueprintList.isEmpty())

src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintLoadCommand.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
public class AdminBlueprintLoadCommand extends CompositeCommand {
1818

1919
private static final FilenameFilter BLUEPRINT_FILTER = (File dir, String name) -> name
20-
.endsWith(BlueprintsManager.BLUEPRINT_SUFFIX);
20+
.endsWith(BlueprintsManager.BLUEPRINT_SUFFIX) || name.endsWith(BlueprintsManager.LEGACY_BLUEPRINT_SUFFIX);
2121

2222
public AdminBlueprintLoadCommand(AdminBlueprintCommand parent) {
2323
super(parent, "load");
@@ -54,11 +54,22 @@ public Optional<List<String>> tabComplete(User user, String alias, List<String>
5454
AdminBlueprintCommand parent = (AdminBlueprintCommand) getParent();
5555
File folder = parent.getBlueprintsFolder();
5656
if (folder.exists()) {
57-
options = Arrays.stream(Objects.requireNonNull(folder.list(BLUEPRINT_FILTER))).map(n -> n.substring(0, n.length() - 4)) // remove .blu from filename
57+
options = Arrays.stream(Objects.requireNonNull(folder.list(BLUEPRINT_FILTER)))
58+
.map(AdminBlueprintLoadCommand::removeBlueprintSuffix)
5859
.toList();
5960
}
6061
String lastArg = !args.isEmpty() ? args.getLast() : "";
6162

6263
return Optional.of(Util.tabLimit(options, lastArg));
6364
}
65+
66+
private static String removeBlueprintSuffix(String name) {
67+
if (name.endsWith(BlueprintsManager.BLUEPRINT_SUFFIX)) {
68+
return name.substring(0, name.length() - BlueprintsManager.BLUEPRINT_SUFFIX.length());
69+
}
70+
if (name.endsWith(BlueprintsManager.LEGACY_BLUEPRINT_SUFFIX)) {
71+
return name.substring(0, name.length() - BlueprintsManager.LEGACY_BLUEPRINT_SUFFIX.length());
72+
}
73+
return name;
74+
}
6475
}

src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintRenameCommand.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,9 @@ public boolean canExecute(User user, String label, List<String> args)
5656
// Check if the 'from' file exists
5757
AdminBlueprintCommand parent = (AdminBlueprintCommand) this.getParent();
5858
File fromFile = new File(parent.getBlueprintsFolder(), from + BlueprintsManager.BLUEPRINT_SUFFIX);
59+
File fromFileLegacy = new File(parent.getBlueprintsFolder(), from + BlueprintsManager.LEGACY_BLUEPRINT_SUFFIX);
5960

60-
if (!fromFile.exists())
61+
if (!fromFile.exists() && !fromFileLegacy.exists())
6162
{
6263
user.sendMessage("commands.admin.blueprint.no-such-file");
6364
return false;
@@ -78,8 +79,9 @@ public boolean execute(User user, String label, List<String> args)
7879

7980
// Check if the 'to' file exists
8081
File toFile = new File(parent.getBlueprintsFolder(), to + BlueprintsManager.BLUEPRINT_SUFFIX);
82+
File toFileLegacy = new File(parent.getBlueprintsFolder(), to + BlueprintsManager.LEGACY_BLUEPRINT_SUFFIX);
8183

82-
if (toFile.exists())
84+
if (toFile.exists() || toFileLegacy.exists())
8385
{
8486
// Ask for confirmation to overwrite
8587
this.askConfirmation(user,

src/main/java/world/bentobox/bentobox/api/commands/admin/blueprints/AdminBlueprintSaveCommand.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,9 @@ public boolean execute(User user, String label, List<String> args)
7272

7373
// Check if file exists
7474
File newFile = new File(parent.getBlueprintsFolder(), fileName + BlueprintsManager.BLUEPRINT_SUFFIX);
75+
File legacyFile = new File(parent.getBlueprintsFolder(), fileName + BlueprintsManager.LEGACY_BLUEPRINT_SUFFIX);
7576

76-
if (newFile.exists())
77+
if (newFile.exists() || legacyFile.exists())
7778
{
7879
this.askConfirmation(user,
7980
user.getTranslation("commands.admin.blueprint.file-exists"),

0 commit comments

Comments
 (0)