Skip to content
18 changes: 18 additions & 0 deletions src/main/java/net/blueva/menu/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,11 @@ public void onDisable() {
if (menuSyncService != null) {
menuSyncService.shutdown();
}
// Cancel running animations and drop per-player menu state
if (javaMenuManager != null) {
javaMenuManager.shutdown();
}
net.blueva.menu.managers.java.PlayerManager.clearAll();
// Disconnect web editor
if (webEditorManager != null) {
webEditorManager.disconnect();
Expand All @@ -167,6 +172,19 @@ public void onDisable() {
Bukkit.getConsoleSender().sendMessage(ChatColor.RED + "V. " + pluginversion + " | Plugin disabled successfully | blueva.net");
}

/**
* Full config + menu reload, shared by {@code /bm reload} and the web editor so the two
* can never drift apart. Must run on the main server thread.
* <p>Note: {@code webeditor.*} and {@code metrics} still need a full restart.
*/
public void reloadAll() {
configManager.reloadSettings();
configManager.reloadLang();
menuSyncService.reload();
javaMenuManager.loadJavaMenus();
bedrockMenuManager.loadBedrockMenus();
}

public void registerCommands() {
CommandHandler handler = new CommandHandler(this);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,9 @@

import net.blueva.menu.Main;
import net.blueva.menu.commands.CommandInterface;
import net.blueva.menu.managers.java.AnimationManager;
import net.blueva.menu.utils.MessagesUtil;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;

public class ReloadSubCommand implements CommandInterface
Expand All @@ -24,13 +20,7 @@ public ReloadSubCommand(Main main) {
public boolean onCommand(@NotNull CommandSender sender, Command cmd,
String commandLabel, String @NotNull [] args) {
if(sender.hasPermission("bluemenu.reload")) {
main.getConfigManager().reloadSettings();
main.configManager.reloadLang();
main.getMenuSyncService().reload();
main.javaMenuManager.loadJavaMenus();
if(Main.isUsingFloodgate) {
main.bedrockMenuManager.loadBedrockMenus();
}
main.reloadAll();
MessagesUtil.sendMessage(sender, main.configManager.getLang().getString("commands.bluemenu.reload.success"));
} else {
MessagesUtil.sendMessage(sender, main.configManager.getLang().getString("commands.bluemenu.reload.insufficient_permissions"));
Expand Down
25 changes: 25 additions & 0 deletions src/main/java/net/blueva/menu/common/dto/MenuMetadataDTO.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public class MenuMetadataDTO {
private String type; // "CHEST", "SIMPLE", "MODAL", "CUSTOM"
private String openCommand; // Command to open the menu
private int itemCount; // Number of items/buttons (optional)
private boolean registered = true; // Whether the menu is registered in java_menus/bedrock_menus and actually loaded
private String source = "disk"; // Where the menu content lives: "disk" or "mysql"

// Default constructor for JSON serialization
public MenuMetadataDTO() {
Expand All @@ -24,6 +26,13 @@ public MenuMetadataDTO(String fileName, String menuName, String platform, String
this.itemCount = itemCount;
}

public MenuMetadataDTO(String fileName, String menuName, String platform, String type, String openCommand,
int itemCount, boolean registered, String source) {
this(fileName, menuName, platform, type, openCommand, itemCount);
this.registered = registered;
this.source = source;
}

// Getters and setters
public String getFileName() {
return fileName;
Expand Down Expand Up @@ -72,4 +81,20 @@ public int getItemCount() {
public void setItemCount(int itemCount) {
this.itemCount = itemCount;
}

public boolean isRegistered() {
return registered;
}

public void setRegistered(boolean registered) {
this.registered = registered;
}

public String getSource() {
return source;
}

public void setSource(String source) {
this.source = source;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,18 @@ public void generateFolders() {

public void generateFile(String file, String folder) throws IOException {
File cfgFile = new File(main.getDataFolder() + folder + "/", file + ".yml");
// Example menus are meant to be copied and edited freely - never auto-update them
// against the bundled resource, or a version bump would reorder/strip the user's keys.
if (cfgFile.exists()) {
return;
}
YamlDocument.create(
cfgFile,
Objects.requireNonNull(main.getResource("net/blueva/menu/configuration/files"+folder+"/" + file + ".yml")),
GeneralSettings.DEFAULT,
LoaderSettings.builder().setAutoUpdate(true).build(),
LoaderSettings.DEFAULT,
DumperSettings.DEFAULT,
UpdaterSettings.builder().setVersioning(new BasicVersioning("file_version")).build()
UpdaterSettings.DEFAULT
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,34 @@
package net.blueva.menu.listeners;

import fr.mrmicky.fastinv.FastInv;
import net.blueva.menu.Main;
import net.blueva.menu.managers.java.PlayerManager;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryInteractEvent;

/**
* Safety net: cancel any interaction (click, drag, shift-move) while a BlueMenu Java menu
* is open, so items in the GUI can never be taken or duplicated even if FastInv misses a case.
*/
public class InventoryInteractListener implements Listener {
@EventHandler
public void IIL(InventoryInteractEvent e) {
Player player = (Player) e.getView().getPlayer();
public void onInteract(InventoryInteractEvent e) {
HumanEntity viewer = e.getView().getPlayer();
if (!(viewer instanceof Player player)) {
return;
}

if (PlayerManager.isPlayerInMenu(player)) {
e.setCancelled(true);
return;
}

FastInv menu = Main.getPlugin().javaMenuManager.getActiveMenu(player);
if (menu != null && e.getInventory().equals(menu.getInventory())) {
e.setCancelled(true);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,12 @@

import dev.dejvokep.boostedyaml.YamlDocument;
import net.blueva.menu.Main;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.floodgate.api.player.FloodgatePlayer;

import java.io.File;
import java.util.Map;

public class PlayerCommandPreprocessListener implements Listener {
Expand All @@ -38,32 +34,49 @@ public void PCPL(PlayerCommandPreprocessEvent e) {
}

private static void checkJavaMenu(Main main, PlayerCommandPreprocessEvent e) {
String message = e.getMessage().trim();
String label = firstToken(e.getMessage());
for (Map.Entry<String, YamlDocument> entry : main.javaMenuManager.menuConfigs.entrySet()) {
String openCommand = entry.getValue().getString("openCommand");
if (openCommand == null || openCommand.trim().isEmpty()) {
if (!matchesOpenCommand(entry.getValue(), label)) {
continue;
}
if (message.equalsIgnoreCase(openCommand.trim())) {
e.setCancelled(true);
main.javaMenuManager.openMenu(e.getPlayer(), entry.getKey());
return; // first match wins, avoid opening several menus on a duplicate command
String permission = entry.getValue().getString("openPermission", "");
if (!permission.isBlank() && !e.getPlayer().hasPermission(permission)) {
return; // command matched a menu, but the player may not open it
}
e.setCancelled(true);
main.javaMenuManager.openMenu(e.getPlayer(), entry.getKey());
return; // first match wins, avoid opening several menus on a duplicate command
}
}

private static void checkBedrockMenu(Main main, PlayerCommandPreprocessEvent e) {
String message = e.getMessage().trim();
String label = firstToken(e.getMessage());
for (Map.Entry<String, YamlDocument> entry : main.bedrockMenuManager.menuConfigs.entrySet()) {
String openCommand = entry.getValue().getString("openCommand");
if (openCommand == null || openCommand.trim().isEmpty()) {
if (!matchesOpenCommand(entry.getValue(), label)) {
continue;
}
if (message.equalsIgnoreCase(openCommand.trim())) {
e.setCancelled(true);
main.bedrockMenuManager.openMenu(e.getPlayer(), entry.getKey());
return; // first match wins
String permission = entry.getValue().getString("openPermission", "");
if (!permission.isBlank() && !e.getPlayer().hasPermission(permission)) {
return;
}
e.setCancelled(true);
main.bedrockMenuManager.openMenu(e.getPlayer(), entry.getKey());
return; // first match wins
}
}

/** The command word without any trailing arguments, e.g. "/shop buy 3" -> "/shop". */
private static String firstToken(String message) {
String trimmed = message.trim();
int space = trimmed.indexOf(' ');
return space > 0 ? trimmed.substring(0, space) : trimmed;
}

private static boolean matchesOpenCommand(YamlDocument menuConfig, String label) {
String openCommand = menuConfig.getString("openCommand");
if (openCommand == null || openCommand.trim().isEmpty()) {
return false;
}
return label.equalsIgnoreCase(openCommand.trim());
}
}
18 changes: 10 additions & 8 deletions src/main/java/net/blueva/menu/listeners/PlayerJoinListener.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
package net.blueva.menu.listeners;

import net.blueva.menu.managers.java.PlayerManager;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;

/**
* Cleans up per-player menu state when a player leaves so nothing is kept in memory.
* <p>
* No join handler is needed any more: {@link PlayerManager} creates entries lazily and
* unconditionally the first time a menu is opened, which also fixes players that were
* already online when the plugin (re)loaded.
*/
public class PlayerJoinListener implements Listener {
@EventHandler
public void PJL(PlayerJoinEvent e) {
if(!PlayerManager.playerInMenu.containsKey(e.getPlayer())) {
PlayerManager.playerInMenu.put(e.getPlayer(), false);
PlayerManager.playerMenuTitle.put(e.getPlayer(), "None");
PlayerManager.playerMenuName.put(e.getPlayer(), null);
}
public void onQuit(PlayerQuitEvent e) {
PlayerManager.forget(e.getPlayer());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,12 @@
public class CustomManager {
public static void openMenu(Player player, YamlDocument menuConfig) {
FloodgatePlayer playerB = FloodgateApi.getInstance().getPlayer(player.getUniqueId());
if (playerB == null) {
return;
}

CustomForm.Builder formBuilder = CustomForm.builder()
.title(MessagesUtil.format(player, Objects.requireNonNull(menuConfig.getString("menuName"))));
.title(MessagesUtil.format(player, menuConfig.getString("menuName", "")));

// Storage to preserve component order and their actions
List<ComponentData> componentsOrder = new ArrayList<>();
Expand All @@ -37,6 +40,9 @@ public static void openMenu(Player player, YamlDocument menuConfig) {
}
}

formBuilder.closedOrInvalidResultHandler(() ->
ActionManager.executeActions(player, menuConfig.getStringList("close_actions")));

CustomForm form = formBuilder.validResultHandler(response -> {
handleResponse(player, response, componentsOrder);
}).build();
Expand Down Expand Up @@ -70,7 +76,7 @@ private static void addComponent(CustomForm.Builder formBuilder, Section compone
return;
}

String text = MessagesUtil.format(player, Objects.requireNonNull(component.getString("text", "")));
String text = MessagesUtil.format(player, component.getString("text", ""));

switch (type.toUpperCase()) {
case "DROPDOWN":
Expand Down
Loading