From 58690f203bc912154d3a50204f1d6df7479b257b Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 19 Oct 2024 10:48:45 -0700 Subject: [PATCH 1/5] WIP --- .../bentobox/api/panels/TemplatedPanel.java | 3 +- .../panels/customizable/AbstractPanel.java | 140 +++++++++++ .../customizable/IslandCreationPanel.java | 117 ++------- .../panels/customizable/LanguagePanel.java | 238 ++++++------------ .../panels/customizable/SettingsPanel.java | 59 +++++ 5 files changed, 298 insertions(+), 259 deletions(-) create mode 100644 src/main/java/world/bentobox/bentobox/panels/customizable/AbstractPanel.java create mode 100644 src/main/java/world/bentobox/bentobox/panels/customizable/SettingsPanel.java diff --git a/src/main/java/world/bentobox/bentobox/api/panels/TemplatedPanel.java b/src/main/java/world/bentobox/bentobox/api/panels/TemplatedPanel.java index 6310fa144..8afc57802 100644 --- a/src/main/java/world/bentobox/bentobox/api/panels/TemplatedPanel.java +++ b/src/main/java/world/bentobox/bentobox/api/panels/TemplatedPanel.java @@ -362,9 +362,8 @@ public class TemplatedPanel extends Panel { * this button is present. * * @return Map that links button type to amount in the gui. - * @deprecated Use {@link #amount(String)} instead. + * Use {@link #amount(String)} instead. */ - @Deprecated public Map amountMap() { return this.parentPanel.typeSlotMap; } diff --git a/src/main/java/world/bentobox/bentobox/panels/customizable/AbstractPanel.java b/src/main/java/world/bentobox/bentobox/panels/customizable/AbstractPanel.java new file mode 100644 index 000000000..fb266a784 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/panels/customizable/AbstractPanel.java @@ -0,0 +1,140 @@ +package world.bentobox.bentobox.panels.customizable; + +import java.io.File; + +import org.bukkit.event.inventory.ClickType; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.addons.GameModeAddon; +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.TemplatedPanel; +import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord; +import world.bentobox.bentobox.api.user.User; + +/** + * @author tastybento + */ +public abstract class AbstractPanel { + + // --------------------------------------------------------------------- + // Section: Constants + // --------------------------------------------------------------------- + + /** + * This constant is used for button to indicate that it is Language type. + */ + public static final String LOCALE = "LOCALE"; + + /** + * This constant is used for button to indicate that it is previous page type. + */ + public static final String PREVIOUS = "PREVIOUS"; + + /** + * This constant is used for button to indicate that it is next page type. + */ + public static final String NEXT = "NEXT"; + + /** + * This constant is used for indicating that pages should contain numbering. + */ + public static final String INDEXING = "indexing"; + + /** + * This constant stores value for SELECT action that is used in panels. + */ + public static final String SELECT_ACTION = "SELECT"; + + /** + * This constant stores value for COMMANDS action that is used in panels. + */ + public static final String COMMANDS_ACTION = "COMMANDS"; + + /** + * This constant stores value for AUTHORS label that is used in panels. + */ + public static final String AUTHORS = "[authors]"; + + /** + * This constant stores value for SELECTED label that is used in panels. + */ + public static final String SELECTED = "[selected]"; + + /** + * This variable allows to access plugin object. + */ + final BentoBox plugin; + + /** + * This variable stores main command that was triggered. + */ + final CompositeCommand command; + + /** + * This variable holds user who opens panel. Without it panel cannot be opened. + */ + final User user; + + /** + * This variable holds world where panel is opened. Without it panel cannot be opened. + */ + String mainLabel; + + /** + * This variable holds current pageIndex for multi-page island choosing. + */ + int pageIndex; + + public AbstractPanel(CompositeCommand command, User user) { + plugin = command.getPlugin(); + this.command = command; + this.user = user; + this.pageIndex = 0; // Start with the first page by default + } + + // Abstract methods for creating next and previous buttons + protected abstract PanelItem createNextButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot); + + protected abstract PanelItem createPreviousButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot); + + // Abstract build method to allow each panel to define its own layout + protected abstract void build(); + + // Default method for pagination, can be overridden by subclasses if needed + protected boolean hasNextPage(int elementListSize, int itemsPerPage) { + return (pageIndex + 1) * itemsPerPage < elementListSize; + } + + protected boolean hasPreviousPage() { + return pageIndex > 0; + } + + // Method to handle the click event on next/previous buttons + protected boolean handlePageChange(ItemTemplateRecord.ActionRecords action, ClickType clickType, + String actionType) { + if ((clickType == action.clickType() || action.clickType() == ClickType.UNKNOWN) + && actionType.equalsIgnoreCase(action.actionType())) { + if (actionType.equalsIgnoreCase("NEXT")) { + this.pageIndex++; + } else if (actionType.equalsIgnoreCase("PREVIOUS")) { + this.pageIndex--; + } + build(); + return true; + } + return false; + } + + /** + * This method returns if panel with the requested name is located in GameModeAddon folder. + * @param addon GameModeAddon that need to be checked. + * @param name Name of the panel. + * @return {@code true} if panel exists, {@code false} otherwise. + */ + protected boolean doesCustomPanelExists(GameModeAddon addon, String name) { + return addon.getDataFolder().exists() && new File(addon.getDataFolder(), "panels").exists() + && new File(addon.getDataFolder(), "panels" + File.separator + name + ".yml").exists(); + } + +} diff --git a/src/main/java/world/bentobox/bentobox/panels/customizable/IslandCreationPanel.java b/src/main/java/world/bentobox/bentobox/panels/customizable/IslandCreationPanel.java index a15e77571..ce493c310 100644 --- a/src/main/java/world/bentobox/bentobox/panels/customizable/IslandCreationPanel.java +++ b/src/main/java/world/bentobox/bentobox/panels/customizable/IslandCreationPanel.java @@ -22,8 +22,6 @@ import org.bukkit.inventory.ItemStack; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; -import world.bentobox.bentobox.BentoBox; -import world.bentobox.bentobox.api.addons.GameModeAddon; import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.panels.PanelItem; @@ -41,7 +39,7 @@ import world.bentobox.bentobox.util.Util; * If file with such name is located at gamemode panels directory, then that file will be used. * Otherwise, file in BentoBox/panels is used. */ -public class IslandCreationPanel +public class IslandCreationPanel extends AbstractPanel { // --------------------------------------------------------------------- // Section: Constants @@ -51,37 +49,10 @@ public class IslandCreationPanel * This constant is used for button to indicate that it is Blueprint Bundle type. */ private static final String BUNDLES = "BUNDLE"; - - /** - * This constant is used for button to indicate that it is previous page type. - */ - private static final String PREVIOUS = "PREVIOUS"; - - /** - * This constant is used for button to indicate that it is next page type. - */ - private static final String NEXT = "NEXT"; - - /** - * This constant is used for indicating that pages should contain numbering. - */ - private static final String INDEXING = "indexing"; - - /** - * This constant stores value for SELECT action that is used in panels. - */ - private static final String SELECT_ACTION = "SELECT"; - - /** - * This constant stores value for COMMAND action that is used in panels. - */ - private static final String COMMANDS_ACTION = "COMMANDS"; - /** * This constant stores value for ERROR message that will be displayed upon failing to run creation commands. */ private static final String ISLAND_CREATION_COMMANDS = "ISLAND_CREATION_COMMANDS"; - /** * Button reference */ @@ -91,35 +62,11 @@ public class IslandCreationPanel // Section: Variables // --------------------------------------------------------------------- - /** - * This variable allows to access plugin object. - */ - private final BentoBox plugin; - - /** - * This variable stores main command that was triggered. - */ - private final CompositeCommand mainCommand; - - /** - * This variable holds user who opens panel. Without it panel cannot be opened. - */ - private final User user; - - /** - * This variable holds world where panel is opened. Without it panel cannot be opened. - */ - private final String mainLabel; - /** * This variable stores filtered elements. */ private final List elementList; - /** - * This variable holds current pageIndex for multi-page island choosing. - */ - private int pageIndex; /** * The world that this command applies to @@ -147,8 +94,7 @@ public class IslandCreationPanel private IslandCreationPanel(@NonNull CompositeCommand command, @NonNull User user, @NonNull String label, boolean reset) { - this.plugin = BentoBox.getInstance(); - this.user = user; + super(command, user); this.mainLabel = label; this.world = command.getWorld(); this.reset = reset; @@ -159,7 +105,6 @@ public class IslandCreationPanel .hasPermission(command.getPermissionPrefix() + "island.create." + bundle.getUniqueId())) .toList(); - this.mainCommand = command; } @@ -172,7 +117,8 @@ public class IslandCreationPanel * Build method manages current panel opening. It uses BentoBox PanelAPI that is easy to use and users can get nice * panels. */ - private void build() + @Override + protected void build() { // Do not open gui if there is no magic sticks. if (this.elementList.isEmpty()) @@ -187,10 +133,10 @@ public class IslandCreationPanel TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder(); // Set main template. - if (this.doesCustomPanelExists(this.mainCommand.getAddon(), "island_creation_panel")) + if (this.doesCustomPanelExists(this.command.getAddon(), "island_creation_panel")) { // Addon has its own island creation panel. Use it. - panelBuilder.template("island_creation_panel", new File(this.mainCommand.getAddon().getDataFolder(), "panels")); + panelBuilder.template("island_creation_panel", new File(this.command.getAddon().getDataFolder(), "panels")); } else { @@ -212,21 +158,6 @@ public class IslandCreationPanel panelBuilder.build(); } - - /** - * This method returns if panel with the requested name is located in GameModeAddon folder. - * @param addon GameModeAddon that need to be checked. - * @param name Name of the panel. - * @return {@code true} if panel exists, {@code false} otherwise. - */ - private boolean doesCustomPanelExists(GameModeAddon addon, String name) - { - return addon.getDataFolder().exists() && - new File(addon.getDataFolder(), "panels").exists() - && new File(addon.getDataFolder(), "panels" + File.separator + name + ".yml").exists(); - } - - // --------------------------------------------------------------------- // Section: Buttons // --------------------------------------------------------------------- @@ -239,8 +170,9 @@ public class IslandCreationPanel * @param slot the slot * @return the panel item */ + @Override @Nullable - private PanelItem createNextButton(@NonNull ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) + protected PanelItem createNextButton(@NonNull ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { int size = this.elementList.size(); @@ -269,12 +201,12 @@ public class IslandCreationPanel if (template.title() != null) { - builder.name(this.user.getTranslation(this.mainCommand.getWorld(), template.title())); + builder.name(this.user.getTranslation(this.command.getWorld(), template.title())); } if (template.description() != null) { - builder.description(this.user.getTranslation(this.mainCommand.getWorld(), template.description(), + builder.description(this.user.getTranslation(this.command.getWorld(), template.description(), TextVariables.NUMBER, String.valueOf(nextPageIndex))); } @@ -299,7 +231,7 @@ public class IslandCreationPanel // Collect tooltips. List tooltips = template.actions().stream(). filter(action -> action.tooltip() != null) - .map(action -> this.user.getTranslation(this.mainCommand.getWorld(), action.tooltip())) + .map(action -> this.user.getTranslation(this.command.getWorld(), action.tooltip())) .filter(text -> !text.isBlank()) .collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); @@ -323,7 +255,8 @@ public class IslandCreationPanel * @return the panel item */ @Nullable - private PanelItem createPreviousButton(@NonNull ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) + @Override + protected PanelItem createPreviousButton(@NonNull ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { if (this.pageIndex == 0) { @@ -349,12 +282,12 @@ public class IslandCreationPanel if (template.title() != null) { - builder.name(this.user.getTranslation(this.mainCommand.getWorld(), template.title())); + builder.name(this.user.getTranslation(this.command.getWorld(), template.title())); } if (template.description() != null) { - builder.description(this.user.getTranslation(this.mainCommand.getWorld(), template.description(), + builder.description(this.user.getTranslation(this.command.getWorld(), template.description(), TextVariables.NUMBER, String.valueOf(previousPageIndex))); } @@ -379,7 +312,7 @@ public class IslandCreationPanel // Collect tooltips. List tooltips = template.actions().stream(). filter(action -> action.tooltip() != null) - .map(action -> this.user.getTranslation(this.mainCommand.getWorld(), action.tooltip())) + .map(action -> this.user.getTranslation(this.command.getWorld(), action.tooltip())) .filter(text -> !text.isBlank()) .collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); @@ -467,7 +400,7 @@ public class IslandCreationPanel if (template.title() != null) { - builder.name(this.user.getTranslation(this.mainCommand.getWorld(), template.title(), + builder.name(this.user.getTranslation(this.command.getWorld(), template.title(), TextVariables.NAME, bundle.getDisplayName())); } else @@ -478,7 +411,7 @@ public class IslandCreationPanel if (template.description() != null) { - builder.description(this.user.getTranslation(this.mainCommand.getWorld(), template.description(), + builder.description(this.user.getTranslation(this.command.getWorld(), template.description(), TextVariables.DESCRIPTION, String.join("\n", bundle.getDescription()))); } else @@ -524,13 +457,13 @@ public class IslandCreationPanel { if (SELECT_ACTION.equalsIgnoreCase(action.actionType())) { user.closeInventory(); - this.mainCommand.execute(user, this.mainLabel, + this.command.execute(user, this.mainLabel, Collections.singletonList(bundle.getUniqueId())); } else if (COMMANDS_ACTION.equalsIgnoreCase(action.actionType())) { Util.runCommands(user, Arrays.stream(action.content() .replaceAll(Pattern.quote(TextVariables.LABEL), - this.mainCommand.getTopLabel()) + this.command.getTopLabel()) .split("\n")).toList(), ISLAND_CREATION_COMMANDS); } @@ -543,7 +476,7 @@ public class IslandCreationPanel // Collect tooltips. List tooltips = actions.stream().filter(action -> action.tooltip() != null) - .map(action -> this.user.getTranslation(this.mainCommand.getWorld(), action.tooltip())) + .map(action -> this.user.getTranslation(this.command.getWorld(), action.tooltip())) .filter(text -> !text.isBlank()) .collect(Collectors.toCollection(() -> new ArrayList<>(actions.size()))); @@ -557,12 +490,10 @@ public class IslandCreationPanel return builder.build(); } - // --------------------------------------------------------------------- // Section: Static methods // --------------------------------------------------------------------- - /** * This method is used to open Panel outside this class. It will be much easier to open panel with single method * call then initializing new object. @@ -572,11 +503,9 @@ public class IslandCreationPanel * @param user User who opens panel * @param reset true if this is an island reset */ - public static void openPanel(@NonNull CompositeCommand command, - @NonNull User user, @NonNull String label, boolean reset) - { + public static void openPanel(@NonNull CompositeCommand command, @NonNull User user, @NonNull String label, + boolean reset) { new IslandCreationPanel(command, user, label, reset).build(); } - } diff --git a/src/main/java/world/bentobox/bentobox/panels/customizable/LanguagePanel.java b/src/main/java/world/bentobox/bentobox/panels/customizable/LanguagePanel.java index ba84e4c60..32b879b49 100644 --- a/src/main/java/world/bentobox/bentobox/panels/customizable/LanguagePanel.java +++ b/src/main/java/world/bentobox/bentobox/panels/customizable/LanguagePanel.java @@ -1,6 +1,7 @@ // // Created by BONNe // Copyright - 2022 +// Updated by tastybento // @@ -23,8 +24,6 @@ import org.bukkit.inventory.ItemStack; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; -import world.bentobox.bentobox.BentoBox; -import world.bentobox.bentobox.api.addons.GameModeAddon; import world.bentobox.bentobox.api.commands.CompositeCommand; import world.bentobox.bentobox.api.localization.BentoBoxLocale; import world.bentobox.bentobox.api.localization.TextVariables; @@ -42,8 +41,17 @@ import world.bentobox.bentobox.util.Util; * If file with such name is located at gamemode panels directory, then that file will be used. * Otherwise, file in BentoBox/panels is used. */ -public class LanguagePanel +public class LanguagePanel extends AbstractPanel { + // --------------------------------------------------------------------- + // Section: Variables + // --------------------------------------------------------------------- + + /** + * This variable stores filtered elements. + */ + private final List elementList; + // --------------------------------------------------------------------- // Section: Constructor // --------------------------------------------------------------------- @@ -55,13 +63,24 @@ public class LanguagePanel * @param command The main addon command. * @param user User who opens panel */ - private LanguagePanel(@NonNull CompositeCommand command, @NonNull User user) - { - this.plugin = BentoBox.getInstance(); - this.mainCommand = command; - this.user = user; + public LanguagePanel(CompositeCommand command, User user) { + super(command, user); + this.elementList = plugin.getLocalesManager().getAvailableLocales(true); + } - this.elementList = BentoBox.getInstance().getLocalesManager().getAvailableLocales(true); + // --------------------------------------------------------------------- + // Section: Static methods + // --------------------------------------------------------------------- + + /** + * This method is used to open Panel outside this class. It will be much easier to open panel with single method + * call then initializing new object. + * + * @param command The main addon command. + * @param user User who opens panel + */ + public static void openPanel(@NonNull CompositeCommand command, @NonNull User user) { + new LanguagePanel(command, user).build(); } @@ -74,14 +93,15 @@ public class LanguagePanel * Build method manages current panel opening. It uses BentoBox PanelAPI that is easy to use and users can get nice * panels. */ - private void build() + @Override + protected void build() { // Do not open gui if there is no magic sticks. if (this.elementList.isEmpty()) { this.plugin.logError("There are no available locales for selection!"); this.user.sendMessage("no-locales", - TextVariables.GAMEMODE, this.plugin.getDescription().getName()); + TextVariables.GAMEMODE, this.plugin.getDescription().getName()); return; } @@ -89,10 +109,10 @@ public class LanguagePanel TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder(); // Set main template. - if (this.doesCustomPanelExists(this.mainCommand.getAddon(), "language_panel")) + if (this.doesCustomPanelExists(this.command.getAddon(), "language_panel")) { // Addon has its own island creation panel. Use it. - panelBuilder.template("language_panel", new File(this.mainCommand.getAddon().getDataFolder(), "panels")); + panelBuilder.template("language_panel", new File(this.command.getAddon().getDataFolder(), "panels")); } else { @@ -115,20 +135,6 @@ public class LanguagePanel } - /** - * This method returns if panel with the requested name is located in GameModeAddon folder. - * @param addon GameModeAddon that need to be checked. - * @param name Name of the panel. - * @return {@code true} if panel exists, {@code false} otherwise. - */ - private boolean doesCustomPanelExists(GameModeAddon addon, String name) - { - return addon.getDataFolder().exists() && - new File(addon.getDataFolder(), "panels").exists() && - new File(addon.getDataFolder(), "panels" + File.separator + name + ".yml").exists(); - } - - // --------------------------------------------------------------------- // Section: Buttons // --------------------------------------------------------------------- @@ -141,13 +147,14 @@ public class LanguagePanel * @param slot the slot * @return the panel item */ + @Override @Nullable - private PanelItem createNextButton(@NonNull ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) + protected PanelItem createNextButton(@NonNull ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { int size = this.elementList.size(); if (size <= slot.amountMap().getOrDefault(LOCALE, 1) || - 1.0 * size / slot.amountMap().getOrDefault(LOCALE, 1) <= this.pageIndex + 1) + 1.0 * size / slot.amountMap().getOrDefault(LOCALE, 1) <= this.pageIndex + 1) { // There are no next elements return null; @@ -177,7 +184,7 @@ public class LanguagePanel if (template.description() != null) { builder.description(this.user.getTranslation(template.description(), - TextVariables.NUMBER, String.valueOf(nextPageIndex))); + TextVariables.NUMBER, String.valueOf(nextPageIndex))); } // Add ClickHandler @@ -185,7 +192,7 @@ public class LanguagePanel { template.actions().forEach(action -> { if ((clickType == action.clickType() || - action.clickType() == ClickType.UNKNOWN) && NEXT.equalsIgnoreCase(action.actionType())) + action.clickType() == ClickType.UNKNOWN) && NEXT.equalsIgnoreCase(action.actionType())) { // Next button ignores click type currently. this.pageIndex++; @@ -200,10 +207,9 @@ public class LanguagePanel // Collect tooltips. List tooltips = template.actions().stream(). - filter(action -> action.tooltip() != null). - map(action -> this.user.getTranslation( action.tooltip())). - filter(text -> !text.isBlank()). - collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + filter(action -> action.tooltip() != null).map(action -> this.user.getTranslation(action.tooltip())) + .filter(text -> !text.isBlank()) + .collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); // Add tooltips. if (!tooltips.isEmpty()) @@ -224,8 +230,9 @@ public class LanguagePanel * @param slot the slot * @return the panel item */ + @Override @Nullable - private PanelItem createPreviousButton(@NonNull ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) + protected PanelItem createPreviousButton(@NonNull ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) { if (this.pageIndex == 0) { @@ -251,13 +258,13 @@ public class LanguagePanel if (template.title() != null) { - builder.name(this.user.getTranslation(this.mainCommand.getWorld(), template.title())); + builder.name(this.user.getTranslation(this.command.getWorld(), template.title())); } if (template.description() != null) { - builder.description(this.user.getTranslation(this.mainCommand.getWorld(), template.description(), - TextVariables.NUMBER, String.valueOf(previousPageIndex))); + builder.description(this.user.getTranslation(this.command.getWorld(), template.description(), + TextVariables.NUMBER, String.valueOf(previousPageIndex))); } // Add ClickHandler @@ -266,7 +273,7 @@ public class LanguagePanel { template.actions().forEach(action -> { if ((clickType == action.clickType() || - action.clickType() == ClickType.UNKNOWN) && PREVIOUS.equalsIgnoreCase(action.actionType())) + action.clickType() == ClickType.UNKNOWN) && PREVIOUS.equalsIgnoreCase(action.actionType())) { // Next button ignores click type currently. this.pageIndex--; @@ -281,10 +288,10 @@ public class LanguagePanel // Collect tooltips. List tooltips = template.actions().stream(). - filter(action -> action.tooltip() != null). - map(action -> this.user.getTranslation(this.mainCommand.getWorld(), action.tooltip())). - filter(text -> !text.isBlank()). - collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + filter(action -> action.tooltip() != null) + .map(action -> this.user.getTranslation(this.command.getWorld(), action.tooltip())) + .filter(text -> !text.isBlank()) + .collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); // Add tooltips. if (!tooltips.isEmpty()) @@ -330,9 +337,8 @@ public class LanguagePanel { // Try to find locale with requested ID. if not found, use already collected locale. locale = this.elementList.stream(). - filter(localeID -> localeID.toLanguageTag().equals(template.dataMap().get("lang_id"))). - findFirst(). - orElse(locale); + filter(localeID -> localeID.toLanguageTag().equals(template.dataMap().get("lang_id"))).findFirst() + .orElse(locale); } return this.createLocaleButton(template, locale); @@ -371,18 +377,18 @@ public class LanguagePanel else { builder.icon(Objects.requireNonNullElseGet(language.getBanner(), - () -> new ItemStack(Material.WHITE_BANNER, 1))); + () -> new ItemStack(Material.WHITE_BANNER, 1))); } if (template.title() != null) { - builder.name(this.user.getTranslation(this.mainCommand.getWorld(), template.title(), - TextVariables.NAME, WordUtils.capitalize(locale.getDisplayName(this.user.getLocale())))); + builder.name(this.user.getTranslation(this.command.getWorld(), template.title(), + TextVariables.NAME, WordUtils.capitalize(locale.getDisplayName(this.user.getLocale())))); } else { builder.name(this.user.getTranslation(reference + "name", - TextVariables.NAME, WordUtils.capitalize(locale.getDisplayName(this.user.getLocale())))); + TextVariables.NAME, WordUtils.capitalize(locale.getDisplayName(this.user.getLocale())))); } final StringBuilder authors = new StringBuilder(); @@ -405,28 +411,25 @@ public class LanguagePanel if (template.description() != null) { descriptionText = this.user.getTranslationOrNothing(template.description(), - AUTHORS, authors.toString(), - SELECTED, selected.toString()); + AUTHORS, authors.toString(), SELECTED, selected.toString()); } else { descriptionText = this.user.getTranslationOrNothing(reference + "description", - AUTHORS, authors.toString(), - SELECTED, selected.toString()); + AUTHORS, authors.toString(), SELECTED, selected.toString()); } descriptionText = descriptionText.replaceAll("(?m)^[ \\t]*\\r?\\n", ""). - replaceAll("(? actions = template.actions().stream(). - filter(action -> !this.user.getLocale().equals(locale) && - (SELECT_ACTION.equalsIgnoreCase(action.actionType()) || - COMMANDS_ACTION.equalsIgnoreCase(action.actionType()))). - toList(); + filter(action -> !this.user.getLocale().equals(locale) + && (SELECT_ACTION.equalsIgnoreCase(action.actionType()) + || COMMANDS_ACTION.equalsIgnoreCase(action.actionType()))) + .toList(); // Add ClickHandler builder.clickHandler((panel, user, clickType, i) -> @@ -438,7 +441,7 @@ public class LanguagePanel { this.plugin.getPlayers().setLocale(this.user.getUniqueId(), locale.toLanguageTag()); this.user.sendMessage("language.edited", "[lang]", - WordUtils.capitalize(locale.getDisplayName(this.user.getLocale()))); + WordUtils.capitalize(locale.getDisplayName(this.user.getLocale()))); // Rebuild panel this.build(); @@ -446,11 +449,11 @@ public class LanguagePanel else if (COMMANDS_ACTION.equalsIgnoreCase(action.actionType())) { Util.runCommands(user, - Arrays.stream(action.content(). - replaceAll(Pattern.quote(TextVariables.LABEL), this.mainCommand.getTopLabel()). - split("\n")). + Arrays.stream(action.content() + .replaceAll(Pattern.quote(TextVariables.LABEL), this.command.getTopLabel()) + .split("\n")). toList(), - "CHANGE_LOCALE_COMMANDS"); + "CHANGE_LOCALE_COMMANDS"); } } }); @@ -461,10 +464,10 @@ public class LanguagePanel // Collect tooltips. List tooltips = actions.stream(). - filter(action -> action.tooltip() != null). - map(action -> this.user.getTranslation(this.mainCommand.getWorld(), action.tooltip())). - filter(text -> !text.isBlank()). - collect(Collectors.toCollection(() -> new ArrayList<>(actions.size()))); + filter(action -> action.tooltip() != null) + .map(action -> this.user.getTranslation(this.command.getWorld(), action.tooltip())) + .filter(text -> !text.isBlank()) + .collect(Collectors.toCollection(() -> new ArrayList<>(actions.size()))); // Add tooltips. if (!tooltips.isEmpty()) @@ -478,97 +481,6 @@ public class LanguagePanel } - // --------------------------------------------------------------------- - // Section: Static methods - // --------------------------------------------------------------------- - - - /** - * This method is used to open Panel outside this class. It will be much easier to open panel with single method - * call then initializing new object. - * - * @param command The main addon command. - * @param user User who opens panel - */ - public static void openPanel(@NonNull CompositeCommand command, @NonNull User user) - { - new LanguagePanel(command, user).build(); - } - - -// --------------------------------------------------------------------- -// Section: Constants -// --------------------------------------------------------------------- - - - /** - * This constant is used for button to indicate that it is Language type. - */ - private static final String LOCALE = "LOCALE"; - - /** - * This constant is used for button to indicate that it is previous page type. - */ - private static final String PREVIOUS = "PREVIOUS"; - - /** - * This constant is used for button to indicate that it is next page type. - */ - private static final String NEXT = "NEXT"; - - /** - * This constant is used for indicating that pages should contain numbering. - */ - private static final String INDEXING = "indexing"; - - /** - * This constant stores value for SELECT action that is used in panels. - */ - private static final String SELECT_ACTION = "SELECT"; - - /** - * This constant stores value for COMMANDS action that is used in panels. - */ - private static final String COMMANDS_ACTION = "COMMANDS"; - - /** - * This constant stores value for AUTHORS label that is used in panels. - */ - public static final String AUTHORS = "[authors]"; - - /** - * This constant stores value for SELECTED label that is used in panels. - */ - public static final String SELECTED = "[selected]"; - - -// --------------------------------------------------------------------- -// Section: Variables -// --------------------------------------------------------------------- - - - /** - * This variable allows to access plugin object. - */ - private final BentoBox plugin; - - /** - * This variable stores the main command object. - */ - private final CompositeCommand mainCommand; - - /** - * This variable holds user who opens panel. Without it panel cannot be opened. - */ - private final User user; - - /** - * This variable stores filtered elements. - */ - private final List elementList; - - /** - * This variable holds current pageIndex for multi-page island choosing. - */ - private int pageIndex; } + + diff --git a/src/main/java/world/bentobox/bentobox/panels/customizable/SettingsPanel.java b/src/main/java/world/bentobox/bentobox/panels/customizable/SettingsPanel.java new file mode 100644 index 000000000..32c4ba91b --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/panels/customizable/SettingsPanel.java @@ -0,0 +1,59 @@ +/** + * + */ +package world.bentobox.bentobox.panels.customizable; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.bukkit.World; + +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.flags.Flag; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.TabbedPanel; +import world.bentobox.bentobox.api.panels.TemplatedPanel.ItemSlot; +import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; + +/** + * + */ +public class SettingsPanel extends AbstractPanel { + + protected static final String PROTECTION_PANEL = "protection.panel."; + private static final String CLICK_TO_SWITCH = PROTECTION_PANEL + "mode.click-to-switch"; + protected Flag.Type type; + protected World world; + protected Island island; + protected TabbedPanel parent; + + private Map currentMode = new HashMap<>(); + + public SettingsPanel(CompositeCommand command, User user) { + super(command, user); + // TODO Auto-generated constructor stub + } + + @Override + protected void build() { + // TODO Auto-generated method stub + + } + + @Override + protected PanelItem createNextButton(ItemTemplateRecord arg0, ItemSlot arg1) { + // TODO Auto-generated method stub + return null; + } + + @Override + protected PanelItem createPreviousButton(ItemTemplateRecord arg0, ItemSlot arg1) { + // TODO Auto-generated method stub + return null; + } + +} From e0a3f48aed9a1ed16d63a6e1ef4eadd94cd9bbd8 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 20 Oct 2024 16:29:32 -0700 Subject: [PATCH 2/5] Adds an island home panel. #2531 Also enhances the customizable panel API by making templates load dynamically when requested. --- .../world/bentobox/bentobox/BentoBox.java | 12 - .../api/commands/CompositeCommand.java | 2 +- .../api/commands/island/IslandGoCommand.java | 11 +- .../commands/island/IslandHomesCommand.java | 30 +- .../api/panels/reader/TemplateReader.java | 24 +- .../panels/customizable/IslandHomesPanel.java | 419 ++++++++++++++++++ src/main/resources/locales/en-US.yml | 6 + .../resources/panels/island_homes_panel.yml | 69 +++ 8 files changed, 526 insertions(+), 47 deletions(-) create mode 100644 src/main/java/world/bentobox/bentobox/panels/customizable/IslandHomesPanel.java create mode 100644 src/main/resources/panels/island_homes_panel.yml diff --git a/src/main/java/world/bentobox/bentobox/BentoBox.java b/src/main/java/world/bentobox/bentobox/BentoBox.java index 650d5e4a5..b0ad73046 100644 --- a/src/main/java/world/bentobox/bentobox/BentoBox.java +++ b/src/main/java/world/bentobox/bentobox/BentoBox.java @@ -1,7 +1,5 @@ package world.bentobox.bentobox; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.List; import java.util.Optional; @@ -466,16 +464,6 @@ public class BentoBox extends JavaPlugin implements Listener { return false; } - log("Saving default panels..."); - if (!Files.exists(Path.of(this.getDataFolder().getPath(), "panels", "island_creation_panel.yml"))) { - log("Saving default island_creation_panel..."); - this.saveResource("panels/island_creation_panel.yml", false); - } - - if (!Files.exists(Path.of(this.getDataFolder().getPath(), "panels", "language_panel.yml"))) { - log("Saving default language_panel..."); - this.saveResource("panels/language_panel.yml", false); - } return true; } diff --git a/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java index 4d0a8146e..c75d67c80 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/CompositeCommand.java @@ -346,7 +346,7 @@ public abstract class CompositeCommand extends Command implements PluginIdentifi * * @return IslandsManager */ - protected IslandsManager getIslands() { + public IslandsManager getIslands() { return plugin.getIslands(); } diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandGoCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandGoCommand.java index cfe3f6c8e..0eeccbcbe 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandGoCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandGoCommand.java @@ -61,9 +61,9 @@ public class IslandGoCommand extends DelayedTeleportCommand { @Override public boolean execute(User user, String label, List args) { + Map names = getNameIslandMap(user); // Check if the home is known if (!args.isEmpty()) { - Map names = getNameIslandMap(user); final String name = String.join(" ", args); if (!names.containsKey(name)) { // Failed home name check @@ -113,7 +113,11 @@ public class IslandGoCommand extends DelayedTeleportCommand { } - private record IslandInfo(Island island, boolean islandName) {} + /** + * Record of islands and the name to type + */ + private record IslandInfo(Island island, boolean islandName) { + } private Map getNameIslandMap(User user) { Map islandMap = new HashMap<>(); @@ -129,7 +133,8 @@ public class IslandGoCommand extends DelayedTeleportCommand { islandMap.put(text, new IslandInfo(island, true)); } // Add homes. Homes do not need an island specified - island.getHomes().keySet().forEach(n -> islandMap.put(n, new IslandInfo(island, false))); + island.getHomes().keySet().stream().filter(n -> !n.isBlank()) + .forEach(n -> islandMap.put(n, new IslandInfo(island, false))); } return islandMap; diff --git a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandHomesCommand.java b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandHomesCommand.java index 33b077c5f..b9298664a 100644 --- a/src/main/java/world/bentobox/bentobox/api/commands/island/IslandHomesCommand.java +++ b/src/main/java/world/bentobox/bentobox/api/commands/island/IslandHomesCommand.java @@ -1,19 +1,12 @@ package world.bentobox.bentobox.api.commands.island; -import java.util.ArrayList; import java.util.List; -import java.util.Optional; import world.bentobox.bentobox.api.commands.CompositeCommand; -import world.bentobox.bentobox.api.commands.ConfirmableCommand; -import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.database.objects.Island; -import world.bentobox.bentobox.util.Util; +import world.bentobox.bentobox.panels.customizable.IslandHomesPanel; -public class IslandHomesCommand extends ConfirmableCommand { - - private List islands; +public class IslandHomesCommand extends CompositeCommand { public IslandHomesCommand(CompositeCommand islandCommand) { super(islandCommand, "homes"); @@ -28,9 +21,8 @@ public class IslandHomesCommand extends ConfirmableCommand { @Override public boolean canExecute(User user, String label, List args) { - islands = getIslands().getIslands(getWorld(), user); // Check island - if (islands.isEmpty()) { + if (getIslands().getIslands(getWorld(), user).isEmpty()) { user.sendMessage("general.errors.no-island"); return false; } @@ -39,22 +31,8 @@ public class IslandHomesCommand extends ConfirmableCommand { @Override public boolean execute(User user, String label, List args) { - user.sendMessage("commands.island.sethome.homes-are"); - islands.forEach(island -> - island.getHomes().keySet().stream().filter(s -> !s.isEmpty()) - .forEach(s -> user.sendMessage("commands.island.sethome.home-list-syntax", TextVariables.NAME, s))); + IslandHomesPanel.openPanel(this, user); return true; } - @Override - public Optional> tabComplete(User user, String alias, List args) { - String lastArg = !args.isEmpty() ? args.get(args.size()-1) : ""; - List result = new ArrayList<>(); - for (Island island : getIslands().getIslands(getWorld(), user.getUniqueId())) { - result.addAll(island.getHomes().keySet()); - } - return Optional.of(Util.tabLimit(result, lastArg)); - - } - } diff --git a/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java b/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java index 524f0f260..ce837602d 100644 --- a/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java +++ b/src/main/java/world/bentobox/bentobox/api/panels/reader/TemplateReader.java @@ -90,15 +90,29 @@ public class TemplateReader } File file = new File(panelLocation, templateName.endsWith(YML) ? templateName : templateName + YML); - + String absolutePath = file.getAbsolutePath(); if (!file.exists()) { - BentoBox.getInstance().logError(file.getAbsolutePath() + " does not exist for panel template"); - // Return as file does not exist. - return null; + // Try to get it from the JAR + + String keyword = "panels/"; + + // Find the index of the keyword "panels/" + int index = absolutePath.indexOf(keyword); + + // If the keyword is found, extract the substring starting from that index + if (index != -1) { + BentoBox.getInstance().saveResource(absolutePath.substring(index), false); + file = new File(panelLocation, templateName.endsWith(YML) ? templateName : templateName + YML); + } else { + BentoBox.getInstance().logError(file.getAbsolutePath() + " does not exist for panel template"); + // Return as file does not exist. + return null; + } + } - final String panelKey = file.getAbsolutePath() + ":" + panelName; + final String panelKey = absolutePath + ":" + panelName; // Check if panel is already crafted. if (TemplateReader.loadedPanels.containsKey(panelKey)) diff --git a/src/main/java/world/bentobox/bentobox/panels/customizable/IslandHomesPanel.java b/src/main/java/world/bentobox/bentobox/panels/customizable/IslandHomesPanel.java new file mode 100644 index 000000000..c760a21d8 --- /dev/null +++ b/src/main/java/world/bentobox/bentobox/panels/customizable/IslandHomesPanel.java @@ -0,0 +1,419 @@ +package world.bentobox.bentobox.panels.customizable; + + +import java.io.File; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.bukkit.Material; +import org.bukkit.World; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + +import world.bentobox.bentobox.api.commands.CompositeCommand; +import world.bentobox.bentobox.api.commands.island.IslandGoCommand; +import world.bentobox.bentobox.api.localization.TextVariables; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.TemplatedPanel; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.panels.builders.TemplatedPanelBuilder; +import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.bentobox.database.objects.Island; + + +/** + * Panel for island homes command + */ +public class IslandHomesPanel extends AbstractPanel +{ + + private static final String ISLAND = "ISLAND"; + + + /** + * This variable stores filtered elements. + */ + private final Map islandMap; + private final Map order = new HashMap<>(); + + + /** + * The world that this command applies to + */ + private final World world; + + private final IslandGoCommand goCommand; + + // --------------------------------------------------------------------- + // Section: Constructor + // --------------------------------------------------------------------- + + + /** + * This is internal constructor. It is used internally in current class to avoid creating objects everywhere. + * + * @param command CompositeCommand + * @param user User who opens panel + * @param islandMap map of island names and IslandInfo + */ + private IslandHomesPanel(@NonNull CompositeCommand command, @NonNull User user) + { + super(command, user); + this.world = command.getWorld(); + this.islandMap = this.getNameIslandMap(user); + int index = 0; + for (String name : islandMap.keySet()) { + order.put(index++, name); + } + goCommand = (IslandGoCommand) command.getParent().getSubCommand("go").orElse(null); + } + + + // --------------------------------------------------------------------- + // Section: Methods + // --------------------------------------------------------------------- + + + /** + * Build method manages current panel opening. It uses BentoBox PanelAPI that is easy to use and users can get nice + * panels. + */ + @Override + protected void build() + { + // Do not open gui if there are no islands + if (this.islandMap.isEmpty()) + { + user.sendMessage("general.errors.no-island"); + return; + } + + // Start building panel. + TemplatedPanelBuilder panelBuilder = new TemplatedPanelBuilder(); + + // Set main template. + if (this.doesCustomPanelExists(this.command.getAddon(), "island_homes_panel")) + { + // Addon has its own island homes panel. Use it. + panelBuilder.template("island_homes_panel", new File(this.command.getAddon().getDataFolder(), "panels")); + } + else + { + // Use default island creation panel. + panelBuilder.template("island_homes_panel", new File(this.plugin.getDataFolder(), "panels")); + } + + panelBuilder.user(this.user); + panelBuilder.world(world); + + // Register button builders + panelBuilder.registerTypeBuilder(ISLAND, this::createIslandButton); + + // Register next and previous builders + panelBuilder.registerTypeBuilder(NEXT, this::createNextButton); + panelBuilder.registerTypeBuilder(PREVIOUS, this::createPreviousButton); + + // Register unknown type builder. + panelBuilder.build(); + } + + // --------------------------------------------------------------------- + // Section: Buttons + // --------------------------------------------------------------------- + + + /** + * Create next button panel item. + * + * @param template the template + * @param slot the slot + * @return the panel item + */ + @Override + @Nullable + protected PanelItem createNextButton(@NonNull ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) + { + int size = this.islandMap.size(); + + if (size <= slot.amountMap().getOrDefault(ISLAND, 1) + || 1.0 * size / slot.amountMap().getOrDefault(ISLAND, 1) <= this.pageIndex + 1) + { + // There are no next elements + return null; + } + + int nextPageIndex = this.pageIndex + 2; + + PanelItemBuilder builder = new PanelItemBuilder(); + + if (template.icon() != null) + { + ItemStack clone = template.icon().clone(); + + if ((boolean) template.dataMap().getOrDefault(INDEXING, false)) + { + clone.setAmount(nextPageIndex); + } + + builder.icon(clone); + } + + if (template.title() != null) + { + builder.name(this.user.getTranslation(this.command.getWorld(), template.title())); + } + + if (template.description() != null) + { + builder.description(this.user.getTranslation(this.command.getWorld(), template.description(), + TextVariables.NUMBER, String.valueOf(nextPageIndex))); + } + + // Add ClickHandler + builder.clickHandler((panel, user, clickType, i) -> + { + template.actions().forEach(action -> { + if ((clickType == action.clickType() || + action.clickType() == ClickType.UNKNOWN) && NEXT.equalsIgnoreCase(action.actionType())) + { + // Next button ignores click type currently. + this.pageIndex++; + this.build(); + } + + }); + + // Always return true. + return true; + }); + + // Collect tooltips. + List tooltips = template.actions().stream(). + filter(action -> action.tooltip() != null) + .map(action -> this.user.getTranslation(this.command.getWorld(), action.tooltip())) + .filter(text -> !text.isBlank()) + .collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + + // Add tooltips. + if (!tooltips.isEmpty()) + { + // Empty line and tooltips. + builder.description(""); + builder.description(tooltips); + } + + return builder.build(); + } + + + /** + * Create previous button panel item. + * + * @param template the template + * @param slot the slot + * @return the panel item + */ + @Nullable + @Override + protected PanelItem createPreviousButton(@NonNull ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) + { + if (this.pageIndex == 0) + { + // There are no next elements + return null; + } + + int previousPageIndex = this.pageIndex; + + PanelItemBuilder builder = new PanelItemBuilder(); + + if (template.icon() != null) + { + ItemStack clone = template.icon().clone(); + + if ((boolean) template.dataMap().getOrDefault(INDEXING, false)) + { + clone.setAmount(previousPageIndex); + } + + builder.icon(clone); + } + + if (template.title() != null) + { + builder.name(this.user.getTranslation(this.command.getWorld(), template.title())); + } + + if (template.description() != null) + { + builder.description(this.user.getTranslation(this.command.getWorld(), template.description(), + TextVariables.NUMBER, String.valueOf(previousPageIndex))); + } + + // Add ClickHandler + builder.clickHandler((panel, user, clickType, i) -> + { + template.actions().forEach(action -> { + if ((clickType == action.clickType() || + action.clickType() == ClickType.UNKNOWN) && PREVIOUS.equalsIgnoreCase(action.actionType())) + { + // Next button ignores click type currently. + this.pageIndex--; + this.build(); + } + + }); + + // Always return true. + return true; + }); + + // Collect tooltips. + List tooltips = template.actions().stream(). + filter(action -> action.tooltip() != null) + .map(action -> this.user.getTranslation(this.command.getWorld(), action.tooltip())) + .filter(text -> !text.isBlank()) + .collect(Collectors.toCollection(() -> new ArrayList<>(template.actions().size()))); + + // Add tooltips. + if (!tooltips.isEmpty()) + { + // Empty line and tooltips. + builder.description(""); + builder.description(tooltips); + } + + return builder.build(); + } + + + /** + * This method creates and returns island button. + * + * @return PanelItem that represents island button. + */ + @Nullable + private PanelItem createIslandButton(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) + { + if (this.islandMap.isEmpty()) + { + // Does not contain any islands. + return null; + } + + int index = this.pageIndex * slot.amountMap().getOrDefault(ISLAND, 1) + slot.slot(); + if (index >= this.islandMap.size()) + { + // Out of index. + return null; + } + return this.createIslandButtonDetail(template, slot); + } + + + /** + * This method creates bundle button. + * + * @return PanelItem that allows to select bundle button + */ + private PanelItem createIslandButtonDetail(ItemTemplateRecord template, TemplatedPanel.ItemSlot slot) + { + // Get settings for island. + PanelItemBuilder builder = new PanelItemBuilder(); + + if (template.icon() != null) + { + builder.icon(template.icon().clone()); + } + else + { + builder.icon(Material.GRASS_BLOCK); + } + + if (template.title() != null) + { + builder.name(this.user.getTranslation(this.command.getWorld(), template.title(), + TextVariables.NAME, order.get(slot.slot()))); + } + else + { + builder.name(this.user.getTranslation("panels.island_homes.buttons.name", TextVariables.NAME, + order.get(slot.slot()))); + } + + // Add ClickHandler + builder.clickHandler((panel, user, clickType, i) -> { + template.actions().forEach(action -> { + if (goCommand != null) { + String name = order.get(slot.slot()); + user.closeInventory(); + if (goCommand.canExecute(user, "", List.of(name))) { + goCommand.execute(user, "", List.of(name)); + } + } + }); + + // Always return true. + return true; + }); + + return builder.build(); + } + + /** + * Record of islands and the name to type + */ + private record IslandInfo(Island island, boolean islandName) { + } + + /** + * This is duplicate code from the Go command. + * @param user user + * @return name and island info + */ + private Map getNameIslandMap(User user) { + Map islandMap = new HashMap<>(); + int index = 0; + for (Island island : command.getIslands().getIslands(command.getWorld(), user.getUniqueId())) { + index++; + if (island.getName() != null && !island.getName().isBlank()) { + // Name has been set + islandMap.put(island.getName(), new IslandInfo(island, true)); + } else { + // Name has not been set + String text = user.getTranslation("protection.flags.ENTER_EXIT_MESSAGES.island", TextVariables.NAME, + user.getName(), TextVariables.DISPLAY_NAME, user.getDisplayName()) + " " + index; + islandMap.put(text, new IslandInfo(island, true)); + } + // Add homes. Homes do not need an island specified + island.getHomes().keySet().stream().filter(n -> !n.isBlank()) + .forEach(n -> islandMap.put(n, new IslandInfo(island, false))); + } + + return islandMap; + + } + + // --------------------------------------------------------------------- + // Section: Static methods + // --------------------------------------------------------------------- + + /** + * This method is used to open Panel outside this class. It will be much easier to open panel with single method + * call then initializing new object. + * + * @param command CompositeCommand object + * @param user User who opens panel + */ + public static void openPanel(@NonNull CompositeCommand command, @NonNull User user) { + new IslandHomesPanel(command, user).build(); + } + + +} diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 97134b403..e89e21a49 100644 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -1906,6 +1906,12 @@ panel: # This section contains values for BentoBox panels. panels: + # The section of translations used in Island Homes Panel + island_homes: + title: "&2&l Your island homes" + buttons: + # This button is used for displaying islands to teleport to + name: "&l [name]" # The section of translations used in Island Creation Panel island_creation: title: "&2&l Pick an island" diff --git a/src/main/resources/panels/island_homes_panel.yml b/src/main/resources/panels/island_homes_panel.yml new file mode 100644 index 000000000..1b0d6327b --- /dev/null +++ b/src/main/resources/panels/island_homes_panel.yml @@ -0,0 +1,69 @@ +# This is default island homes panel. It is used in all situations when gamemode addon does not have specified their +# of panel. +island_homes_panel: + title: panels.island_go.title # The title of panel or link to the localization location. + type: INVENTORY # The type of inventory: INVENTORY, DROPPER, HOPPER + background: # The item that will be displayed in empty spots. This section can be removed. + icon: BLACK_STAINED_GLASS_PANE # The icon of background item + title: "&b&r" # Empty text # The text of background item + border: # The item that will be displayed around the inventory. This section can be removed. + icon: BLACK_STAINED_GLASS_PANE # The icon of background item + title: "&b&r" # Empty text # The text of background item + force-shown: [] # Allow to specify (1-6, 1-3, 1) which rows must be showed regardless of empty elements. + content: # Allow to define buttons in your panel. + 2: + 2: island_button # String values are expected to be `reusables` that are defined at the end of this file. + 3: island_button + 4: island_button + 5: island_button + 6: island_button + 7: island_button + 8: island_button + 3: + 1: + icon: tipped_arrow{CustomPotionColor:11546150} # The icon for button + title: panels.buttons.previous.name # The name of button, or link to the localization. + description: panels.buttons.previous.description # The description of button, or link to the localization. + data: + type: PREVIOUS # Indicates what button is doing. Available values depends on panel + indexing: true # Parameter for button. + actions: # List of actions that button can do. Available values depends on button + previous: + click-type: UNKNOWN # UNKNOWN means that any click type is respected. + tooltip: panels.tips.click-to-previous # Tooltips are always generated an empty line bellow description/title. Not required. + 2: island_button + 3: island_button + 4: island_button + 5: island_button + 6: island_button + 7: island_button + 8: island_button + 9: + icon: tipped_arrow{CustomPotionColor:8439583} + title: panels.buttons.next.name + description: panels.buttons.next.description + data: + type: NEXT + indexing: true + actions: + next: + click-type: UNKNOWN + tooltip: panels.tips.click-to-next + 4: + 2: island_button + 3: island_button + 4: island_button + 5: island_button + 6: island_button + 7: island_button + 8: island_button + reusable: # List of reoccurring buttons in the panels. + island_button: # The ID of the button + # icon: GRASS_BLOCK + title: panels.island_homes.buttons.name + data: + type: ISLAND + actions: + select: + click-type: UNKNOWN + tooltip: panels.tips.click-to-choose \ No newline at end of file From 6766c0f9063def97a933c9afa81460f6af449cda Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 20 Oct 2024 17:10:10 -0700 Subject: [PATCH 3/5] Store the island sizes in the grid to avoid database loadings #2534 --- .../bentobox/managers/IslandsManager.java | 4 + .../bentobox/managers/island/IslandCache.java | 17 ++++ .../bentobox/managers/island/IslandGrid.java | 77 +++++++++++++------ .../panels/customizable/SettingsPanel.java | 59 -------------- 4 files changed, 74 insertions(+), 83 deletions(-) delete mode 100644 src/main/java/world/bentobox/bentobox/panels/customizable/SettingsPanel.java diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java index f3cfaf0fc..464ad9602 100644 --- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java @@ -442,6 +442,10 @@ public class IslandsManager { : Optional.empty(); } + public boolean isIslandAd(@NonNull Location location) { + return plugin.getIWM().inWorld(location) ? islandCache.isIslandAt(location) : false; + } + /** * Returns an unmodifiable collection of all existing islands * (even those who may be unowned). diff --git a/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java b/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java index b5f3f5413..a1c0c6859 100644 --- a/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java +++ b/src/main/java/world/bentobox/bentobox/managers/island/IslandCache.java @@ -257,6 +257,23 @@ public class IslandCache { } } + /** + * Returns if there is island at the location. This includes + * the full island space, not just the protected area. + * Does not cause a database load of the island. + * + * @param location the location + * @return true if there is an island there + */ + @Nullable + public boolean isIslandAt(@NonNull Location location) { + World w = Util.getWorld(location.getWorld()); + if (w == null || !grids.containsKey(w)) { + return false; + } + return grids.get(w).isIslandAt(location.getBlockX(), location.getBlockZ()); + } + /** * Returns the island at the location or null if there is none. This includes * the full island space, not just the protected area diff --git a/src/main/java/world/bentobox/bentobox/managers/island/IslandGrid.java b/src/main/java/world/bentobox/bentobox/managers/island/IslandGrid.java index 11a76c367..82120a986 100644 --- a/src/main/java/world/bentobox/bentobox/managers/island/IslandGrid.java +++ b/src/main/java/world/bentobox/bentobox/managers/island/IslandGrid.java @@ -3,6 +3,8 @@ package world.bentobox.bentobox.managers.island; import java.util.Map.Entry; import java.util.TreeMap; +import org.eclipse.jdt.annotation.Nullable; + import world.bentobox.bentobox.database.objects.Island; /** @@ -11,7 +13,11 @@ import world.bentobox.bentobox.database.objects.Island; * */ class IslandGrid { - private final TreeMap> grid = new TreeMap<>(); + + private record IslandData(String id, int minX, int minZ, int range) { + } + + private final TreeMap> grid = new TreeMap<>(); private final IslandCache im; /** @@ -29,10 +35,13 @@ class IslandGrid { */ public boolean addToGrid(Island island) { // Check if we know about this island already - if (grid.containsKey(island.getMinX())) { - TreeMap zEntry = grid.get(island.getMinX()); - if (zEntry.containsKey(island.getMinZ())) { - if (island.getUniqueId().equals(zEntry.get(island.getMinZ()))) { + int minX = island.getMinX(); + int minZ = island.getMinZ(); + IslandData islandData = new IslandData(island.getUniqueId(), minZ, minZ, island.getRange()); + if (grid.containsKey(minX)) { + TreeMap zEntry = grid.get(minX); + if (zEntry.containsKey(minZ)) { + if (island.getUniqueId().equals(zEntry.get(minZ).id())) { // If it is the same island then it's okay return true; } @@ -40,14 +49,14 @@ class IslandGrid { return false; } else { // Add island - zEntry.put(island.getMinZ(), island.getUniqueId()); - grid.put(island.getMinX(), zEntry); + zEntry.put(minZ, islandData); + grid.put(minX, zEntry); } } else { // Add island - TreeMap zEntry = new TreeMap<>(); - zEntry.put(island.getMinZ(), island.getUniqueId()); - grid.put(island.getMinX(), zEntry); + TreeMap zEntry = new TreeMap<>(); + zEntry.put(minZ, islandData); + grid.put(minX, zEntry); } return true; } @@ -60,7 +69,7 @@ class IslandGrid { public boolean removeFromGrid(Island island) { String id = island.getUniqueId(); boolean removed = grid.values().stream() - .anyMatch(innerMap -> innerMap.values().removeIf(innerValue -> innerValue.equals(id))); + .anyMatch(innerMap -> innerMap.values().removeIf(innerValue -> innerValue.id().equals(id))); grid.values().removeIf(TreeMap::isEmpty); @@ -70,35 +79,55 @@ class IslandGrid { /** * Retrieves the island located at the specified x and z coordinates, covering both the protected area * and the full island space. Returns null if no island exists at the given location. + * This will load the island from the database if it is not in the cache. * * @param x the x coordinate of the location * @param z the z coordinate of the location * @return the Island at the specified location, or null if no island is found */ public Island getIslandAt(int x, int z) { + String id = getIslandStringAt(x, z); + if (id == null) { + return null; + } + + // Retrieve the island using the id found - loading from database if required + return im.getIslandById(id); + } + + /** + * Checks if an island is at this coordinate or not + * @param x coord + * @param z coord + * @return true if there is an island registered in the grid + */ + public boolean isIslandAt(int x, int z) { + return getIslandStringAt(x, z) != null; + } + + /** + * Get the island ID string for an island at this coordinates, or null if none. + * @param x coord + * @param z coord + * @return Unique Island ID string, or null if there is no island here. + */ + public @Nullable String getIslandStringAt(int x, int z) { // Attempt to find the closest x-coordinate entry that does not exceed 'x' - Entry> xEntry = grid.floorEntry(x); + Entry> xEntry = grid.floorEntry(x); if (xEntry == null) { return null; // No x-coordinate entry found, return null } // Attempt to find the closest z-coordinate entry that does not exceed 'z' within the found x-coordinate - Entry zEntry = xEntry.getValue().floorEntry(z); + Entry zEntry = xEntry.getValue().floorEntry(z); if (zEntry == null) { return null; // No z-coordinate entry found, return null } - - // Retrieve the island using the id found in the z-coordinate entry - Island island = im.getIslandById(zEntry.getValue()); - if (island == null) { - return null; // No island found by the id, return null - } // Check if the specified coordinates are within the island space - if (island.inIslandSpace(x, z)) { - return island; // Coordinates are within island space, return the island + if (x >= zEntry.getValue().minX() && x < zEntry.getValue().minX() + zEntry.getValue().range() * 2 + && z >= zEntry.getValue().minZ() && z < zEntry.getValue().minZ() + zEntry.getValue().range() * 2) { + return zEntry.getValue().id(); } - - // Coordinates are outside the island space, return null return null; } @@ -107,7 +136,7 @@ class IslandGrid { */ public long getSize() { long count = 0; - for (TreeMap innerMap : grid.values()) { + for (TreeMap innerMap : grid.values()) { count += innerMap.size(); } return count; diff --git a/src/main/java/world/bentobox/bentobox/panels/customizable/SettingsPanel.java b/src/main/java/world/bentobox/bentobox/panels/customizable/SettingsPanel.java deleted file mode 100644 index 32c4ba91b..000000000 --- a/src/main/java/world/bentobox/bentobox/panels/customizable/SettingsPanel.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * - */ -package world.bentobox.bentobox.panels.customizable; - -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -import org.bukkit.World; - -import world.bentobox.bentobox.BentoBox; -import world.bentobox.bentobox.api.commands.CompositeCommand; -import world.bentobox.bentobox.api.flags.Flag; -import world.bentobox.bentobox.api.panels.PanelItem; -import world.bentobox.bentobox.api.panels.TabbedPanel; -import world.bentobox.bentobox.api.panels.TemplatedPanel.ItemSlot; -import world.bentobox.bentobox.api.panels.reader.ItemTemplateRecord; -import world.bentobox.bentobox.api.user.User; -import world.bentobox.bentobox.database.objects.Island; - -/** - * - */ -public class SettingsPanel extends AbstractPanel { - - protected static final String PROTECTION_PANEL = "protection.panel."; - private static final String CLICK_TO_SWITCH = PROTECTION_PANEL + "mode.click-to-switch"; - protected Flag.Type type; - protected World world; - protected Island island; - protected TabbedPanel parent; - - private Map currentMode = new HashMap<>(); - - public SettingsPanel(CompositeCommand command, User user) { - super(command, user); - // TODO Auto-generated constructor stub - } - - @Override - protected void build() { - // TODO Auto-generated method stub - - } - - @Override - protected PanelItem createNextButton(ItemTemplateRecord arg0, ItemSlot arg1) { - // TODO Auto-generated method stub - return null; - } - - @Override - protected PanelItem createPreviousButton(ItemTemplateRecord arg0, ItemSlot arg1) { - // TODO Auto-generated method stub - return null; - } - -} From f9c34e5df676f897434d1c5d8895efdce8729968 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 20 Oct 2024 17:57:59 -0700 Subject: [PATCH 4/5] Test fixes --- .../api/commands/island/IslandHomesCommandTest.java | 6 ------ .../panels/customizable/IslandCreationPanelTest.java | 1 + .../bentobox/panels/customizable/LanguagePanelTest.java | 3 +-- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/test/java/world/bentobox/bentobox/api/commands/island/IslandHomesCommandTest.java b/src/test/java/world/bentobox/bentobox/api/commands/island/IslandHomesCommandTest.java index a5702fcb9..9d32760f9 100644 --- a/src/test/java/world/bentobox/bentobox/api/commands/island/IslandHomesCommandTest.java +++ b/src/test/java/world/bentobox/bentobox/api/commands/island/IslandHomesCommandTest.java @@ -8,7 +8,6 @@ import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -39,7 +38,6 @@ import org.powermock.reflect.Whitebox; import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.Settings; import world.bentobox.bentobox.api.commands.CompositeCommand; -import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; import world.bentobox.bentobox.managers.CommandsManager; @@ -200,12 +198,8 @@ public class IslandHomesCommandTest { */ @Test public void testExecuteUserStringListOfString() { - when(im.getIslands(world, user)).thenReturn(List.of(island)); IslandHomesCommand isc = new IslandHomesCommand(ic); - assertTrue(isc.canExecute(user, "island", Collections.emptyList())); assertTrue(isc.execute(user, "island", Collections.emptyList())); - verify(user).sendMessage("commands.island.sethome.homes-are"); - verify(user, times(4)).sendMessage(eq("commands.island.sethome.home-list-syntax"), eq(TextVariables.NAME), anyString()); } } diff --git a/src/test/java/world/bentobox/bentobox/panels/customizable/IslandCreationPanelTest.java b/src/test/java/world/bentobox/bentobox/panels/customizable/IslandCreationPanelTest.java index 9931a7642..0f8bb9433 100644 --- a/src/test/java/world/bentobox/bentobox/panels/customizable/IslandCreationPanelTest.java +++ b/src/test/java/world/bentobox/bentobox/panels/customizable/IslandCreationPanelTest.java @@ -139,6 +139,7 @@ public class IslandCreationPanelTest { when(ic.getAddon()).thenReturn(addon); World world = mock(World.class); when(ic.getWorld()).thenReturn(world); + when(ic.getPlugin()).thenReturn(plugin); // No island for player to begin with (set it later in the tests) when(im.hasIsland(any(), eq(uuid))).thenReturn(false); diff --git a/src/test/java/world/bentobox/bentobox/panels/customizable/LanguagePanelTest.java b/src/test/java/world/bentobox/bentobox/panels/customizable/LanguagePanelTest.java index 94adc7e89..2a2ad5526 100644 --- a/src/test/java/world/bentobox/bentobox/panels/customizable/LanguagePanelTest.java +++ b/src/test/java/world/bentobox/bentobox/panels/customizable/LanguagePanelTest.java @@ -108,6 +108,7 @@ public class LanguagePanelTest { GameModeAddon addon = mock(GameModeAddon.class); when(command.getAddon()).thenReturn(addon); + when(command.getPlugin()).thenReturn(plugin); when(addon.getDataFolder()).thenReturn(resourcePath.toFile()); World world = mock(World.class); @@ -139,8 +140,6 @@ public class LanguagePanelTest { } - /** - */ @After public void tearDown() { User.clearUsers(); From 3d696e10d567be3917ae1ba9d63a419bc599a22a Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 20 Oct 2024 20:32:49 -0700 Subject: [PATCH 5/5] Avoid loading islands unless necessary --- pom.xml | 2 +- .../world/bentobox/bentobox/managers/IslandsManager.java | 2 +- .../island/DefaultNewIslandLocationStrategy.java | 4 +++- .../bentobox/bentobox/managers/island/IslandGrid.java | 8 ++++---- .../bentobox/managers/island/IslandCacheTest.java | 9 ++++----- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/pom.xml b/pom.xml index e49d241f5..88aac5660 100644 --- a/pom.xml +++ b/pom.xml @@ -88,7 +88,7 @@ -LOCAL - 2.6.0 + 2.7.0 bentobox-world https://sonarcloud.io ${project.basedir}/lib diff --git a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java index 464ad9602..b85781153 100644 --- a/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java +++ b/src/main/java/world/bentobox/bentobox/managers/IslandsManager.java @@ -442,7 +442,7 @@ public class IslandsManager { : Optional.empty(); } - public boolean isIslandAd(@NonNull Location location) { + public boolean isIslandAt(@NonNull Location location) { return plugin.getIWM().inWorld(location) ? islandCache.isIslandAt(location) : false; } diff --git a/src/main/java/world/bentobox/bentobox/managers/island/DefaultNewIslandLocationStrategy.java b/src/main/java/world/bentobox/bentobox/managers/island/DefaultNewIslandLocationStrategy.java index 46994fac7..fc5ecab3b 100644 --- a/src/main/java/world/bentobox/bentobox/managers/island/DefaultNewIslandLocationStrategy.java +++ b/src/main/java/world/bentobox/bentobox/managers/island/DefaultNewIslandLocationStrategy.java @@ -74,7 +74,9 @@ public class DefaultNewIslandLocationStrategy implements NewIslandLocationStrate */ protected Result isIsland(Location location) { // Quick check - if (plugin.getIslands().getIslandAt(location).isPresent()) return Result.ISLAND_FOUND; + if (plugin.getIslands().isIslandAt(location)) { + return Result.ISLAND_FOUND; + } World world = location.getWorld(); diff --git a/src/main/java/world/bentobox/bentobox/managers/island/IslandGrid.java b/src/main/java/world/bentobox/bentobox/managers/island/IslandGrid.java index 82120a986..8afed14a2 100644 --- a/src/main/java/world/bentobox/bentobox/managers/island/IslandGrid.java +++ b/src/main/java/world/bentobox/bentobox/managers/island/IslandGrid.java @@ -37,7 +37,7 @@ class IslandGrid { // Check if we know about this island already int minX = island.getMinX(); int minZ = island.getMinZ(); - IslandData islandData = new IslandData(island.getUniqueId(), minZ, minZ, island.getRange()); + IslandData islandData = new IslandData(island.getUniqueId(), minX, minZ, island.getRange()); if (grid.containsKey(minX)) { TreeMap zEntry = grid.get(minX); if (zEntry.containsKey(minZ)) { @@ -99,7 +99,7 @@ class IslandGrid { * Checks if an island is at this coordinate or not * @param x coord * @param z coord - * @return true if there is an island registered in the grid + * @return true if there is an island registered here in the grid */ public boolean isIslandAt(int x, int z) { return getIslandStringAt(x, z) != null; @@ -124,8 +124,8 @@ class IslandGrid { return null; // No z-coordinate entry found, return null } // Check if the specified coordinates are within the island space - if (x >= zEntry.getValue().minX() && x < zEntry.getValue().minX() + zEntry.getValue().range() * 2 - && z >= zEntry.getValue().minZ() && z < zEntry.getValue().minZ() + zEntry.getValue().range() * 2) { + if (x >= zEntry.getValue().minX() && x < (zEntry.getValue().minX() + zEntry.getValue().range() * 2) + && z >= zEntry.getValue().minZ() && z < (zEntry.getValue().minZ() + zEntry.getValue().range() * 2)) { return zEntry.getValue().id(); } return null; diff --git a/src/test/java/world/bentobox/bentobox/managers/island/IslandCacheTest.java b/src/test/java/world/bentobox/bentobox/managers/island/IslandCacheTest.java index 787459983..b9a1c283f 100644 --- a/src/test/java/world/bentobox/bentobox/managers/island/IslandCacheTest.java +++ b/src/test/java/world/bentobox/bentobox/managers/island/IslandCacheTest.java @@ -137,6 +137,7 @@ public class IslandCacheTest extends AbstractCommonSetup { when(island.getMemberSet()).thenReturn(members.build()); when(island.getMinX()).thenReturn(-200); when(island.getMinZ()).thenReturn(-200); + when(island.getRange()).thenReturn(400); // database must be mocked here db = mock(Database.class); @@ -234,12 +235,10 @@ public class IslandCacheTest extends AbstractCommonSetup { Location location2 = mock(Location.class); when(location2.getWorld()).thenReturn(world); - when(location2.getBlockX()).thenReturn(10); - when(location2.getBlockY()).thenReturn(10); - when(location2.getBlockZ()).thenReturn(10); + when(location2.getBlockX()).thenReturn(10000); + when(location2.getBlockY()).thenReturn(100); + when(location2.getBlockZ()).thenReturn(10000); - assertEquals(island, ic.getIslandAt(location2)); - when(island.inIslandSpace(any(Integer.class), any(Integer.class))).thenReturn(false); assertNull(ic.getIslandAt(location2)); }