Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
5 changes: 4 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ paperweight.reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArt
group = "world.bentobox" // From <groupId>

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

Expand Down Expand Up @@ -120,6 +120,7 @@ val jdtAnnotationVersion = "2.2.600"
val multilibVersion = "1.1.13"
val oraxenVersion = "1.193.1"
val craftEngineVersion = "26.5"
val nexoVersion = "1.19.1"
val blueMapApiVersion = "v2.6.2"
val dynmapApiVersion = "3.4"

Expand Down Expand Up @@ -198,6 +199,7 @@ repositories {
maven("https://mvn.lumine.io/repository/maven-public/") { name = "Lumine-Releases" } // Mythic mobs
maven("https://maven.devs.beer/") { name = "MatteoDev" }
maven("https://repo.oraxen.com/releases") { name = "Oraxen" } // Custom items plugin
maven("https://repo.nexomc.com/releases") { name = "Nexo" } // Custom items/blocks plugin
maven("https://repo.codemc.org/repository/bentoboxworld/") { name = "BentoBoxWorld-Repo" }
maven("https://repo.extendedclip.com/releases/") { name = "Placeholder-API-Releases" }

Expand Down Expand Up @@ -292,6 +294,7 @@ dependencies {
}
compileOnly("net.momirealms:craft-engine-bukkit:$craftEngineVersion")
compileOnly("net.momirealms:craft-engine-core:$craftEngineVersion")
compileOnly("com.nexomc:nexo:$nexoVersion")
compileOnly("io.th0rgal:oraxen:$oraxenVersion") {
exclude(group = "me.gabytm.util", module = "actions-spigot")
exclude(group = "org.jetbrains", module = "annotations")
Expand Down
13 changes: 13 additions & 0 deletions src/main/java/world/bentobox/bentobox/BentoBox.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
import world.bentobox.bentobox.managers.AddonsManager;
import world.bentobox.bentobox.managers.BlueprintsManager;
import world.bentobox.bentobox.managers.CommandsManager;
import world.bentobox.bentobox.suggestions.SuggestionsManager;
import world.bentobox.bentobox.managers.FlagsManager;
import world.bentobox.bentobox.managers.HooksManager;
import world.bentobox.bentobox.managers.ChunkPregenManager;
Expand Down Expand Up @@ -65,6 +66,7 @@ public class BentoBox extends JavaPlugin implements Listener {

// Managers
private CommandsManager commandsManager;
private SuggestionsManager suggestionsManager;
private LocalesManager localesManager;
private AddonsManager addonsManager;
private FlagsManager flagsManager;
Expand Down Expand Up @@ -164,6 +166,9 @@ public void onEnable(){
// Set up command manager
commandsManager = new CommandsManager();

// Did-you-mean command suggestions
suggestionsManager = new SuggestionsManager(this);

// Load BentoBox commands
commandsManager.registerDefaultCommands();

Expand Down Expand Up @@ -394,6 +399,14 @@ public CommandsManager getCommandsManager() {
return commandsManager;
}

/**
* @return the did-you-mean command suggestions manager
* @since 3.20.0
*/
public SuggestionsManager getSuggestionsManager() {
return suggestionsManager;
}

/**
* @return the Locales manager
*/
Expand Down
78 changes: 78 additions & 0 deletions src/main/java/world/bentobox/bentobox/Settings.java
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,18 @@ public class Settings implements ConfigObject {
@ConfigEntry(path = "general.teleport-remove-mobs")
private boolean teleportRemoveMobs = false;

@ConfigComment("Did-you-mean command suggestions.")
@ConfigComment("When a player types an unknown command, e.g. /teams instead of /oneblock team,")
@ConfigComment("BentoBox suggests the closest matching BentoBox command. The player can click the")
@ConfigComment("suggestion or type 'yes' to run it.")
@ConfigEntry(path = "general.did-you-mean.unknown-commands", since = "3.20.0")
private boolean didYouMeanUnknownCommands = true;

@ConfigComment("When a player types an unknown subcommand, e.g. /island invit instead of /island team invite,")
@ConfigComment("BentoBox suggests the closest matching subcommand instead of showing an error.")
@ConfigEntry(path = "general.did-you-mean.subcommands", since = "3.20.0")
private boolean didYouMeanSubcommands = true;

/* PANELS */
@ConfigComment("Panel click cooldown. Value is in milliseconds. Prevents players spamming button presses in GUIs.")
@ConfigEntry(path = "panel.click-cooldown-ms")
Expand Down Expand Up @@ -492,6 +504,23 @@ public class Settings implements ConfigObject {
@ConfigEntry(path = "island.obsidian-scooping-lava-tip-duration", since = "3.14.0")
private int obsidianScoopingLavaTipDuration = 5;

/* WORLD */
@ConfigComment("Vanilla structures disabled by default in EVERY BentoBox game mode world")
@ConfigComment("(overworld, nether and end). List the structure keys to stop generating and to")
@ConfigComment("skip in structure searches (/locate, Eyes of Ender, explorer/treasure maps,")
@ConfigComment("dolphins and villager cartographer trades). Suppressing a structure this way also")
@ConfigComment("prevents those searches scanning to the world border and freezing the server.")
@ConfigComment("Keys may use '-' or '_' and any case, e.g. trial_chambers, ancient-city.")
@ConfigComment("A game mode can override this per structure in its own config, both to disable")
@ConfigComment("more structures and to force-enable one that this list disables.")
@ConfigComment("Empty (the default) disables nothing, so behaviour is unchanged.")
@ConfigComment("Example:")
@ConfigComment(" disabled-structures:")
@ConfigComment(" - trial_chambers")
@ConfigComment(" - ancient_city")
@ConfigEntry(path = "world.disabled-structures", since = "3.19.1")
private List<String> disabledStructures = new ArrayList<>();

/* WEB */
@ConfigComment("Toggle whether BentoBox can connect to GitHub to get data about updates and addons.")
@ConfigComment("Disabling this will result in the deactivation of the update checker and of some other")
Expand Down Expand Up @@ -1753,4 +1782,53 @@ public void setIslandPurgeLevel(int islandPurgeLevel) {
this.islandPurgeLevel = islandPurgeLevel;
}

/**
* Vanilla structures disabled by default in every BentoBox game mode world.
* @return the list of disabled structure keys, never {@code null}
* @since 3.19.1
*/
public List<String> getDisabledStructures() {
return disabledStructures == null ? new ArrayList<>() : disabledStructures;
}

/**
* @param disabledStructures the disabledStructures to set
* @since 3.19.1
*/
public void setDisabledStructures(List<String> disabledStructures) {
this.disabledStructures = disabledStructures;
}

/**
* @return whether unknown root commands trigger did-you-mean suggestions
* @since 3.20.0
*/
public boolean isDidYouMeanUnknownCommands() {
return didYouMeanUnknownCommands;
}

/**
* @param didYouMeanUnknownCommands the didYouMeanUnknownCommands to set
* @since 3.20.0
*/
public void setDidYouMeanUnknownCommands(boolean didYouMeanUnknownCommands) {
this.didYouMeanUnknownCommands = didYouMeanUnknownCommands;
}

/**
* @return whether unknown subcommands trigger did-you-mean suggestions
* @since 3.20.0
*/
public boolean isDidYouMeanSubcommands() {
return didYouMeanSubcommands;
}

/**
* @param didYouMeanSubcommands the didYouMeanSubcommands to set
* @since 3.20.0
*/
public void setDidYouMeanSubcommands(boolean didYouMeanSubcommands) {
this.didYouMeanSubcommands = didYouMeanSubcommands;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,18 @@ public boolean execute(@NonNull CommandSender sender, @NonNull String label, Str
CompositeCommand cmd = getCommandFromArgs(args);
String cmdLabel = (cmd.subCommandLevel > 0) ? args[cmd.subCommandLevel - 1] : label;
List<String> cmdArgs = Arrays.asList(args).subList(cmd.subCommandLevel, args.length);
// Did-you-mean: the walk stopped at a command that has subcommands but with
// unconsumed args, i.e. the first leftover arg matched none of them. Suggest
// what the player probably meant instead of dead-ending into an error.
if (!cmdArgs.isEmpty() && cmd.hasSubCommands() && user.isPlayer()
&& plugin.getSettings().isDidYouMeanSubcommands() && plugin.getSuggestionsManager() != null) {
String typedPrefix = "/" + label + (cmd.subCommandLevel > 0
? " " + String.join(" ", Arrays.asList(args).subList(0, cmd.subCommandLevel))
: "");
if (plugin.getSuggestionsManager().suggestSubcommand(user, cmd, typedPrefix, cmdArgs)) {
return true;
}
}
return cmd.call(user, cmdLabel, cmdArgs);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -697,4 +697,29 @@ default boolean isDisallowTeamMemberIslands() {
default boolean isTeamsDisabled() {
return false;
}

/**
* Per-world override for vanilla structure generation and searching.
* <p>
* The key is a structure name, e.g. {@code "trial_chambers"} or {@code "ancient_city"}
* (with {@code '-'} or {@code '_'} separators and any case). The value is whether that
* structure should generate:
* <ul>
* <li>{@code false} — disable the structure in this world: it will not generate and is
* removed from structure searches ({@code /locate}, Eyes of Ender, explorer/treasure
* maps, dolphins, villager cartographer trades), which also stops those searches
* scanning to the world border and freezing the server.</li>
* <li>{@code true} — force the structure on in this world even if BentoBox's global
* {@code world.disabled-structures} list disables it.</li>
* </ul>
* A structure that is absent from this map inherits the global
* {@code world.disabled-structures} setting. The default is an empty map, so a game mode
* that does not override this method inherits BentoBox's global configuration entirely.
*
* @return a map of structure key to whether it should generate, never {@code null}
* @since 3.19.1
*/
default Map<String, Boolean> getStructureSettings() {
return Collections.emptyMap();
}
}
42 changes: 40 additions & 2 deletions src/main/java/world/bentobox/bentobox/api/user/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
import com.google.common.cache.CacheBuilder;

import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import net.kyori.adventure.title.Title;
import world.bentobox.bentobox.BentoBox;
Expand Down Expand Up @@ -804,13 +806,49 @@ public void sendRawMessage(String message) {
/**
* Parses a message string into an Adventure Component, handling inline commands and
* auto-detecting legacy or MiniMessage format.
* <p>
* Inline [run_command]/[hover]-style commands are applied to the parsed Component
* programmatically, not by wrapping the text in MiniMessage click/hover tags: the
* text routinely contains a {@code §r} from the legacy round-trip (any closed color
* or decoration emits one), and the {@code <reset>} it maps to would close the
* wrapper tags, killing the events and leaking literal {@code </click></hover>}
* text into chat.
*
* @param text the message text
* @return the parsed Component
*/
private Component parseToComponent(String text) {
String mmMessage = Util.convertInlineCommandsToMiniMessage(text);
return Util.parseMiniMessageOrLegacy(mmMessage);
Util.InlineCommands inline = Util.extractInlineCommands(text);
Component component = Util.parseMiniMessageOrLegacy(inline.cleanText());
ClickEvent clickEvent = toClickEvent(inline.clickAction(), inline.clickValue());
if (clickEvent != null) {
component = component.clickEvent(clickEvent);
}
String hoverText = inline.hoverText();
if (hoverText != null) {
component = component.hoverEvent(HoverEvent.showText(Util.parseMiniMessageOrLegacy(hoverText)));
}
return component;
}

/**
* Maps an inline command action to an Adventure {@link ClickEvent}.
*
* @param action the inline command action name, may be null
* @param value the action's value, may be null
* @return the click event, or null if the action is unknown or absent
*/
private static ClickEvent toClickEvent(String action, String value) {
if (action == null || value == null) {
return null;
}
return switch (action) {
case "run_command" -> ClickEvent.runCommand(value);
case "suggest_command" -> ClickEvent.suggestCommand(value);
case "copy_to_clipboard" -> ClickEvent.copyToClipboard(value);
case "open_url" -> ClickEvent.openUrl(value);
default -> null;
};
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,14 @@ public void registerMapHooks() {
}

/**
* Registers hooks for plugins that load after BentoBox (Slimefun, ItemsAdder, Oraxen, CraftEngine).
* Registers hooks for plugins that load after BentoBox (Slimefun, ItemsAdder, Oraxen, CraftEngine, Nexo).
*/
public void registerLateHooks() {
hooksManager.registerHook(new SlimefunHook());
hooksManager.registerHook(new ItemsAdderHook(plugin));
hooksManager.registerHook(new OraxenHook(plugin));
hooksManager.registerHook(new CraftEngineHook());
hooksManager.registerHook(new NexoHook());
}

private boolean hasClass(String className) {
Expand Down
93 changes: 93 additions & 0 deletions src/main/java/world/bentobox/bentobox/hooks/NexoHook.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package world.bentobox.bentobox.hooks;

import java.util.Optional;

import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.inventory.ItemStack;

import com.nexomc.nexo.api.NexoBlocks;
import com.nexomc.nexo.api.NexoItems;
import com.nexomc.nexo.items.ItemBuilder;
import com.nexomc.nexo.mechanics.custom_block.CustomBlockMechanic;

import world.bentobox.bentobox.api.hooks.Hook;

/**
* Provides Nexo API access without requiring addons to depend on Nexo directly.
*/
public class NexoHook extends Hook {

public NexoHook() {
super("Nexo", Material.NOTE_BLOCK);
}

@Override
public boolean hook() {
// See if Nexo is around
return Bukkit.getPluginManager().getPlugin("Nexo") != null;
}

@Override
public String getFailureCause() {
return "Nexo is not installed";
}

/**
* Returns {@code true} if a Nexo custom block with the given ID exists in the registry.
*
* @param id the Nexo custom block ID
* @return {@code true} if the block ID is registered
*/
public static boolean exists(String id) {
return NexoBlocks.isCustomBlock(id);
}

/**
* Places a Nexo custom block at the given location.
*
* @param location the target location
* @param blockId the Nexo custom block ID
* @return {@code true} if the ID is a known Nexo block and placement was attempted (this does
* not guarantee that the placement itself succeeded, only that it was requested)
*/
public static boolean placeBlock(Location location, String blockId) {
if (!NexoBlocks.isCustomBlock(blockId)) {
return false;
}
NexoBlocks.place(blockId, location);
return true;
}

/**
* Returns the ID of the Nexo custom block represented by the given block, or {@code null}
* if it is not a Nexo custom block.
*
* @param block the block to check
* @return the Nexo custom block ID or {@code null}
*/
public static String getBlockId(Block block) {
CustomBlockMechanic mechanic = NexoBlocks.customBlockMechanic(block);
return mechanic == null ? null : mechanic.getItemID();
}

/**
* Returns an {@link ItemStack} for the Nexo item with the given ID.
* Useful for rendering the correct icon in panels and GUIs.
*
* @param id the Nexo item ID
* @return an Optional containing the registered item's ItemStack, or empty if not registered
*/
public static Optional<ItemStack> getItemStack(String id) {
if (id == null) {
return Optional.empty();
}
ItemBuilder builder = NexoItems.itemFromId(id);
if (builder == null) {
return Optional.empty();
}
return Optional.ofNullable(builder.build());
}
}
Loading
Loading