From a65b1ecbc5cd2e18197e9b2a91aa40aa9ba0d320 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sun, 2 Feb 2025 08:45:30 -0800 Subject: [PATCH 01/34] Version 1.5.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 3da436b1..5308ae8f 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ 1.21.3-R0.1-SNAPSHOT 1.2.3-SNAPSHOT - 2.7.1-SNAPSHOT + 3.0.0-SNAPSHOT 2.6.3 1.7 1.2.0 From a969008450320c5ac3caa8670102822aba6325ec Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 3 Feb 2025 18:40:10 -0800 Subject: [PATCH 02/34] Added support for Block Groups, AKA Material Tags --- .../requirements/IslandRequirements.java | 437 +-- .../object/requirements/Requirements.java | 4 +- .../managers/ChallengesManager.java | 3 +- .../challenges/panel/CommonPanel.java | 25 + .../panel/admin/EditChallengePanel.java | 2955 +++++++++-------- .../panel/admin/ManageBlocksPanel.java | 2 +- .../panel/admin/ManageTagsPanel.java | 320 ++ .../panel/util/MultiBlockSelector.java | 2 +- .../panel/util/MultiMaterialTagsSelector.java | 326 ++ .../challenges/tasks/TryToComplete.java | 824 +++-- .../bentobox/challenges/utils/Utils.java | 20 + src/main/resources/locales/en-US.yml | 25 + 12 files changed, 2855 insertions(+), 2088 deletions(-) create mode 100644 src/main/java/world/bentobox/challenges/panel/admin/ManageTagsPanel.java create mode 100644 src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java diff --git a/src/main/java/world/bentobox/challenges/database/object/requirements/IslandRequirements.java b/src/main/java/world/bentobox/challenges/database/object/requirements/IslandRequirements.java index 407c28b1..2cff268d 100644 --- a/src/main/java/world/bentobox/challenges/database/object/requirements/IslandRequirements.java +++ b/src/main/java/world/bentobox/challenges/database/object/requirements/IslandRequirements.java @@ -7,9 +7,15 @@ package world.bentobox.challenges.database.object.requirements; -import java.util.*; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import org.bukkit.Fluid; import org.bukkit.Material; +import org.bukkit.Tag; import org.bukkit.entity.EntityType; import com.google.gson.annotations.Expose; @@ -23,202 +29,235 @@ */ public class IslandRequirements extends Requirements { - /** - * Constructor Requirements creates a new Requirements instance. - */ - public IslandRequirements() - { - // Empty constructor for data loader - } - -// --------------------------------------------------------------------- -// Section: Getters and Setters -// --------------------------------------------------------------------- - - - /** - * Method IslandRequirements#getRequiredBlocks returns the requiredBlocks of this object. - * - * @return the requiredBlocks (type {@code Map}) of this object. - */ - public Map getRequiredBlocks() - { - return requiredBlocks; - } - - - /** - * Method IslandRequirements#setRequiredBlocks sets new value for the requiredBlocks of this object. - * @param requiredBlocks new value for this object. - * - */ - public void setRequiredBlocks(Map requiredBlocks) - { - this.requiredBlocks = requiredBlocks; - } - - - /** - * Method IslandRequirements#isRemoveBlocks returns the removeBlocks of this object. - * - * @return the removeBlocks (type boolean) of this object. - */ - public boolean isRemoveBlocks() - { - return removeBlocks; - } - - - /** - * Method IslandRequirements#setRemoveBlocks sets new value for the removeBlocks of this object. - * @param removeBlocks new value for this object. - * - */ - public void setRemoveBlocks(boolean removeBlocks) - { - this.removeBlocks = removeBlocks; - } - - - /** - * Method IslandRequirements#getRequiredEntities returns the requiredEntities of this object. - * - * @return the requiredEntities (type {@code Map}) of this object. - */ - public Map getRequiredEntities() - { - return requiredEntities; - } - - - /** - * Method IslandRequirements#setRequiredEntities sets new value for the requiredEntities of this object. - * @param requiredEntities new value for this object. - * - */ - public void setRequiredEntities(Map requiredEntities) - { - this.requiredEntities = requiredEntities; - } - - - /** - * Method IslandRequirements#isRemoveEntities returns the removeEntities of this object. - * - * @return the removeEntities (type boolean) of this object. - */ - public boolean isRemoveEntities() - { - return removeEntities; - } - - - /** - * Method IslandRequirements#setRemoveEntities sets new value for the removeEntities of this object. - * @param removeEntities new value for this object. - * - */ - public void setRemoveEntities(boolean removeEntities) - { - this.removeEntities = removeEntities; - } - - - /** - * Method IslandRequirements#getSearchRadius returns the searchRadius of this object. - * - * @return the searchRadius (type int) of this object. - */ - public int getSearchRadius() - { - return searchRadius; - } - - - /** - * Method IslandRequirements#setSearchRadius sets new value for the searchRadius of this object. - * @param searchRadius new value for this object. - * - */ - public void setSearchRadius(int searchRadius) - { - this.searchRadius = searchRadius; - } - -// --------------------------------------------------------------------- -// Section: Other methods -// --------------------------------------------------------------------- - - - /** - * Method isValid returns if given requirement data is valid or not. - * - * @return {@code true} if data is valid, {@code false} otherwise. - */ - @Override - public boolean isValid() - { - return super.isValid() && - this.requiredBlocks != null && this.requiredBlocks.keySet().stream().noneMatch(Objects::isNull) && - this.requiredEntities != null && this.requiredEntities.keySet().stream().noneMatch(Objects::isNull); - } - - - /** - * Method Requirements#copy allows copies Requirements object, to avoid changing content when it is necessary - * to use it. - * @return IslandRequirements copy - */ - @Override - public Requirements copy() - { - IslandRequirements clone = new IslandRequirements(); - clone.setRequiredPermissions(new HashSet<>(this.getRequiredPermissions())); - - clone.setRequiredBlocks(new HashMap<>(this.requiredBlocks)); - clone.setRemoveBlocks(this.removeBlocks); - clone.setRequiredEntities(new HashMap<>(this.requiredEntities)); - clone.setRemoveEntities(this.removeEntities); - - clone.setSearchRadius(this.searchRadius); - - return clone; - } - - -// --------------------------------------------------------------------- -// Section: Variables -// --------------------------------------------------------------------- - - - /** - * Map that contains which materials and how many is necessary around player to complete challenge. - */ - @Expose - private Map requiredBlocks = new EnumMap<>(Material.class); - - /** - * Boolean that indicate if blocks should be removed from world after completion. - */ - @Expose - private boolean removeBlocks; - - /** - * Map that contains which entities and how many is necessary around player to complete challenge. - */ - @Expose - @JsonAdapter(EntityCompatibilityAdapter.class) - private Map requiredEntities = new EnumMap<>(EntityType.class); - - /** - * Boolean that indicate if entities should be removed from world after completion. - */ - @Expose - private boolean removeEntities; - - /** - * Radius for searching distance for blocks and entities. - */ - @Expose - private int searchRadius = 10; + /** + * Constructor Requirements creates a new Requirements instance. + */ + public IslandRequirements() { + // Empty constructor for data loader + } + + // --------------------------------------------------------------------- + // Section: Variables + // --------------------------------------------------------------------- + + + /** + * Map that contains which materials and how many is necessary around player to complete challenge. + */ + @Expose + private Map requiredBlocks = new EnumMap<>(Material.class); + + @Expose + private Map, Integer> requiredMaterialTags = new HashMap<>(); + @Expose + private Map, Integer> requiredFluidTags = new HashMap<>(); + @Expose + private Map, Integer> requiredEntityTypeTags = new HashMap<>(); + + /** + * Boolean that indicate if blocks should be removed from world after completion. + */ + @Expose + private boolean removeBlocks; + + /** + * Map that contains which entities and how many is necessary around player to complete challenge. + */ + @Expose + @JsonAdapter(EntityCompatibilityAdapter.class) + private Map requiredEntities = new EnumMap<>(EntityType.class); + + /** + * Boolean that indicate if entities should be removed from world after completion. + */ + @Expose + private boolean removeEntities; + + /** + * Radius for searching distance for blocks and entities. + */ + @Expose + private int searchRadius = 10; + + // --------------------------------------------------------------------- + // Section: Getters and Setters + // --------------------------------------------------------------------- + + + /** + * Method IslandRequirements#getRequiredBlocks returns the requiredBlocks of this object. + * + * @return the requiredBlocks (type {@code Map}) of this object. + */ + public Map getRequiredBlocks() { + return requiredBlocks; + } + + + /** + * Method IslandRequirements#setRequiredBlocks sets new value for the requiredBlocks of this object. + * @param requiredBlocks new value for this object. + * + */ + public void setRequiredBlocks(Map requiredBlocks) { + this.requiredBlocks = requiredBlocks; + } + + + /** + * Method IslandRequirements#isRemoveBlocks returns the removeBlocks of this object. + * + * @return the removeBlocks (type boolean) of this object. + */ + public boolean isRemoveBlocks() { + return removeBlocks; + } + + + /** + * Method IslandRequirements#setRemoveBlocks sets new value for the removeBlocks of this object. + * @param removeBlocks new value for this object. + * + */ + public void setRemoveBlocks(boolean removeBlocks) { + this.removeBlocks = removeBlocks; + } + + + /** + * Method IslandRequirements#getRequiredEntities returns the requiredEntities of this object. + * + * @return the requiredEntities (type {@code Map}) of this object. + */ + public Map getRequiredEntities() { + return requiredEntities; + } + + + /** + * Method IslandRequirements#setRequiredEntities sets new value for the requiredEntities of this object. + * @param requiredEntities new value for this object. + * + */ + public void setRequiredEntities(Map requiredEntities) { + this.requiredEntities = requiredEntities; + } + + /** + * Method IslandRequirements#isRemoveEntities returns the removeEntities of this object. + * + * @return the removeEntities (type boolean) of this object. + */ + public boolean isRemoveEntities() { + return removeEntities; + } + + + /** + * Method IslandRequirements#setRemoveEntities sets new value for the removeEntities of this object. + * @param removeEntities new value for this object. + * + */ + public void setRemoveEntities(boolean removeEntities) { + this.removeEntities = removeEntities; + } + + + /** + * Method IslandRequirements#getSearchRadius returns the searchRadius of this object. + * + * @return the searchRadius (type int) of this object. + */ + public int getSearchRadius() { + return searchRadius; + } + + + /** + * Method IslandRequirements#setSearchRadius sets new value for the searchRadius of this object. + * @param searchRadius new value for this object. + * + */ + public void setSearchRadius(int searchRadius) { + this.searchRadius = searchRadius; + } + + /** + * Method isValid returns if given requirement data is valid or not. + * + * @return {@code true} if data is valid, {@code false} otherwise. + */ + @Override + public boolean isValid() { + return super.isValid() && this.requiredBlocks != null + && this.requiredBlocks.keySet().stream().noneMatch(Objects::isNull) && this.requiredEntities != null + && this.requiredEntities.keySet().stream().noneMatch(Objects::isNull); + } + + + /** + * Method Requirements#copy allows copies Requirements object, to avoid changing content when it is necessary + * to use it. + * @return IslandRequirements copy + */ + @Override + public Requirements copy() { + IslandRequirements clone = new IslandRequirements(); + clone.setRequiredPermissions(new HashSet<>(this.getRequiredPermissions())); + clone.setRequiredMaterialTags(new HashMap<>(this.requiredMaterialTags)); + clone.setRequiredFluidTags(new HashMap<>(this.requiredFluidTags)); + clone.setRequiredEntityTypeTags(new HashMap<>(this.requiredEntityTypeTags)); + clone.setRequiredBlocks(new HashMap<>(this.requiredBlocks)); + clone.setRemoveBlocks(this.removeBlocks); + clone.setRequiredEntities(new HashMap<>(this.requiredEntities)); + clone.setRemoveEntities(this.removeEntities); + + clone.setSearchRadius(this.searchRadius); + + return clone; + } + + + /** + * @return the requiredMaterialTags + */ + public Map, Integer> getRequiredMaterialTags() { + return requiredMaterialTags; + } + + /** + * @param requiredMaterialTags the requiredMaterialTags to set + */ + public void setRequiredMaterialTags(Map, Integer> requiredMaterialTags) { + this.requiredMaterialTags = requiredMaterialTags; + } + + /** + * @return the requiredFluidTags + */ + public Map, Integer> getRequiredFluidTags() { + return requiredFluidTags; + } + + /** + * @param requiredFluidTags the requiredFluidTags to set + */ + public void setRequiredFluidTags(Map, Integer> requiredFluidTags) { + this.requiredFluidTags = requiredFluidTags; + } + + /** + * @return the requiredEntityTypeTags + */ + public Map, Integer> getRequiredEntityTypeTags() { + return requiredEntityTypeTags; + } + + /** + * @param requiredEntityTypeTags the requiredEntityTypeTags to set + */ + public void setRequiredEntityTypeTags(Map, Integer> requiredEntityTypeTags) { + this.requiredEntityTypeTags = requiredEntityTypeTags; + } + } diff --git a/src/main/java/world/bentobox/challenges/database/object/requirements/Requirements.java b/src/main/java/world/bentobox/challenges/database/object/requirements/Requirements.java index ce8279a4..4dae7f03 100644 --- a/src/main/java/world/bentobox/challenges/database/object/requirements/Requirements.java +++ b/src/main/java/world/bentobox/challenges/database/object/requirements/Requirements.java @@ -83,8 +83,8 @@ public boolean isValid() /** - * This set contains all permission strings that ir required for player to complete challenge. - */ + * This set contains all permission strings that are required for player to complete challenge. + */ @Expose private Set requiredPermissions = new HashSet<>(); } diff --git a/src/main/java/world/bentobox/challenges/managers/ChallengesManager.java b/src/main/java/world/bentobox/challenges/managers/ChallengesManager.java index a61e175c..4a655f7b 100644 --- a/src/main/java/world/bentobox/challenges/managers/ChallengesManager.java +++ b/src/main/java/world/bentobox/challenges/managers/ChallengesManager.java @@ -1572,7 +1572,8 @@ public void resetAllChallenges(@NonNull UUID userID, World world, @Nullable UUID this.islandWorldManager.getAddon(world).ifPresent(gameMode -> { this.resetAllChallenges(storageID, gameMode.getDescription().getName()); - this.addLogEntry(storageID, new LogEntry.Builder("RESET_ALL"). + this.addLogEntry(storageID, new LogEntry.Builder("RESET_ALL") + . data(USER_ID, userID.toString()). data(ADMIN_ID, adminID == null ? "ISLAND_RESET" : adminID.toString()). build()); diff --git a/src/main/java/world/bentobox/challenges/panel/CommonPanel.java b/src/main/java/world/bentobox/challenges/panel/CommonPanel.java index 6709d617..e3162398 100644 --- a/src/main/java/world/bentobox/challenges/panel/CommonPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/CommonPanel.java @@ -284,6 +284,30 @@ private String generateRequirements(Challenge challenge, @Nullable User target) private String generateIslandChallenge(IslandRequirements requirement) { final String reference = Constants.DESCRIPTIONS + "challenge.requirements.island."; + // Required Tags - Tags cover both blocks and entities + String tags; + if (!requirement.getRequiredMaterialTags().isEmpty()) { + StringBuilder builder = new StringBuilder(); + builder.append(this.user.getTranslationOrNothing(reference + "tags-title")); + requirement.getRequiredMaterialTags().entrySet().stream().forEach(entry -> { + builder.append("\n"); + + if (entry.getValue() > 1) { + builder.append(this.user.getTranslationOrNothing(reference + "blocks-value", + Constants.PARAMETER_NUMBER, String.valueOf(entry.getValue()), Constants.PARAMETER_MATERIAL, + Utils.prettifyObject(entry.getKey(), this.user))); + } else { + builder.append(this.user.getTranslationOrNothing(reference + "block-value", + Constants.PARAMETER_MATERIAL, Utils.prettifyObject(entry.getKey(), this.user))); + } + }); + + tags = builder.toString(); + } else { + tags = ""; + } + + // Required Blocks String blocks; if (!requirement.getRequiredBlocks().isEmpty()) { @@ -341,6 +365,7 @@ private String generateIslandChallenge(IslandRequirements requirement) { : ""; return this.user.getTranslationOrNothing(reference + "lore", "[blocks]", blocks, "[entities]", entities, + "[tags]", tags, "[warning-block]", warningBlocks, "[warning-entity]", warningEntities, "[search-radius]", searchRadius); } diff --git a/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java index 147f8f7b..2382f493 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java @@ -11,7 +11,9 @@ import java.util.function.Consumer; import java.util.stream.Collectors; +import org.bukkit.Fluid; import org.bukkit.Material; +import org.bukkit.Tag; import org.bukkit.World; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; @@ -60,10 +62,10 @@ public class EditChallengePanel extends CommonPanel { * @param challenge - challenge that needs editing */ private EditChallengePanel(ChallengesAddon addon, User user, World world, String topLabel, String permissionPrefix, - Challenge challenge) { - super(addon, user, world, topLabel, permissionPrefix); - this.challenge = challenge; - this.currentMenuType = MenuType.PROPERTIES; + Challenge challenge) { + super(addon, user, world, topLabel, permissionPrefix); + this.challenge = challenge; + this.currentMenuType = MenuType.PROPERTIES; } /** @@ -71,10 +73,10 @@ private EditChallengePanel(ChallengesAddon addon, User user, World world, String * @param challenge challenge that needs editing. */ private EditChallengePanel(CommonPanel panel, Challenge challenge) { - super(panel); - this.challenge = challenge; - // Default panel should be Properties. - this.currentMenuType = MenuType.PROPERTIES; + super(panel); + this.challenge = challenge; + // Default panel should be Properties. + this.currentMenuType = MenuType.PROPERTIES; } /** @@ -88,8 +90,8 @@ private EditChallengePanel(CommonPanel panel, Challenge challenge) { * @param challenge - challenge that needs editing */ public static void open(ChallengesAddon addon, User user, World world, String topLabel, String permissionPrefix, - Challenge challenge) { - new EditChallengePanel(addon, user, world, topLabel, permissionPrefix, challenge).build(); + Challenge challenge) { + new EditChallengePanel(addon, user, world, topLabel, permissionPrefix, challenge).build(); } /** @@ -99,7 +101,7 @@ public static void open(ChallengesAddon addon, User user, World world, String to * @param challenge - challenge that needs editing */ public static void open(CommonPanel panel, Challenge challenge) { - new EditChallengePanel(panel, challenge).build(); + new EditChallengePanel(panel, challenge).build(); } // --------------------------------------------------------------------- @@ -111,37 +113,37 @@ public static void open(CommonPanel panel, Challenge challenge) { */ @Override protected void build() { - PanelBuilder panelBuilder = new PanelBuilder().user(this.user).name(this.user - .getTranslation(Constants.TITLE + "edit-challenge", "[challenge]", this.challenge.getFriendlyName())); - - PanelUtils.fillBorder(panelBuilder); - - panelBuilder.item(2, this.createMenuButton(MenuType.PROPERTIES)); - panelBuilder.item(4, this.createMenuButton(MenuType.REQUIREMENTS)); - panelBuilder.item(6, this.createMenuButton(MenuType.REWARDS)); - - if (this.currentMenuType.equals(MenuType.PROPERTIES)) { - this.buildMainPropertiesPanel(panelBuilder); - } else if (this.currentMenuType.equals(MenuType.REQUIREMENTS)) { - switch (this.challenge.getChallengeType()) { - case INVENTORY_TYPE -> this.buildInventoryRequirementsPanel(panelBuilder); - case ISLAND_TYPE -> this.buildIslandRequirementsPanel(panelBuilder); - case OTHER_TYPE -> this.buildOtherRequirementsPanel(panelBuilder); - case STATISTIC_TYPE -> this.buildStatisticRequirementsPanel(panelBuilder); - } - } else if (this.currentMenuType.equals(MenuType.REWARDS)) { - this.buildRewardsPanel(panelBuilder); - } - - panelBuilder.item(44, this.returnButton); - - // Every time when this GUI is build, save challenge - // This will ensure that all main things will be always stored - this.addon.getChallengesManager().saveChallenge(this.challenge); - // If for some reason challenge is not loaded, do it. - this.addon.getChallengesManager().loadChallenge(this.challenge, this.world, false, null, true); - - panelBuilder.build(); + PanelBuilder panelBuilder = new PanelBuilder().user(this.user).name(this.user + .getTranslation(Constants.TITLE + "edit-challenge", "[challenge]", this.challenge.getFriendlyName())); + + PanelUtils.fillBorder(panelBuilder); + + panelBuilder.item(2, this.createMenuButton(MenuType.PROPERTIES)); + panelBuilder.item(4, this.createMenuButton(MenuType.REQUIREMENTS)); + panelBuilder.item(6, this.createMenuButton(MenuType.REWARDS)); + + if (this.currentMenuType.equals(MenuType.PROPERTIES)) { + this.buildMainPropertiesPanel(panelBuilder); + } else if (this.currentMenuType.equals(MenuType.REQUIREMENTS)) { + switch (this.challenge.getChallengeType()) { + case INVENTORY_TYPE -> this.buildInventoryRequirementsPanel(panelBuilder); + case ISLAND_TYPE -> this.buildIslandRequirementsPanel(panelBuilder); + case OTHER_TYPE -> this.buildOtherRequirementsPanel(panelBuilder); + case STATISTIC_TYPE -> this.buildStatisticRequirementsPanel(panelBuilder); + } + } else if (this.currentMenuType.equals(MenuType.REWARDS)) { + this.buildRewardsPanel(panelBuilder); + } + + panelBuilder.item(44, this.returnButton); + + // Every time when this GUI is build, save challenge + // This will ensure that all main things will be always stored + this.addon.getChallengesManager().saveChallenge(this.challenge); + // If for some reason challenge is not loaded, do it. + this.addon.getChallengesManager().loadChallenge(this.challenge, this.world, false, null, true); + + panelBuilder.build(); } /** @@ -150,17 +152,17 @@ protected void build() { * @param panelBuilder PanelBuilder where icons must be added. */ private void buildMainPropertiesPanel(PanelBuilder panelBuilder) { - panelBuilder.listener(new IconChanger()); + panelBuilder.listener(new IconChanger()); - panelBuilder.item(10, this.createButton(Button.NAME)); - panelBuilder.item(16, this.createButton(Button.DEPLOYED)); + panelBuilder.item(10, this.createButton(Button.NAME)); + panelBuilder.item(16, this.createButton(Button.DEPLOYED)); - panelBuilder.item(19, this.createButton(Button.ICON)); - panelBuilder.item(22, this.createButton(Button.DESCRIPTION)); - panelBuilder.item(25, this.createButton(Button.ORDER)); + panelBuilder.item(19, this.createButton(Button.ICON)); + panelBuilder.item(22, this.createButton(Button.DESCRIPTION)); + panelBuilder.item(25, this.createButton(Button.ORDER)); - panelBuilder.item(28, this.createButton(Button.ENVIRONMENT)); - panelBuilder.item(31, this.createButton(Button.REMOVE_ON_COMPLETE)); + panelBuilder.item(28, this.createButton(Button.ENVIRONMENT)); + panelBuilder.item(31, this.createButton(Button.REMOVE_ON_COMPLETE)); } /** @@ -170,14 +172,16 @@ private void buildMainPropertiesPanel(PanelBuilder panelBuilder) { * @param panelBuilder PanelBuilder where icons must be added. */ private void buildIslandRequirementsPanel(PanelBuilder panelBuilder) { - panelBuilder.item(19, this.createRequirementButton(RequirementButton.REQUIRED_ENTITIES)); - panelBuilder.item(28, this.createRequirementButton(RequirementButton.REMOVE_ENTITIES)); + panelBuilder.item(19, this.createRequirementButton(RequirementButton.REQUIRED_ENTITIES)); + panelBuilder.item(28, this.createRequirementButton(RequirementButton.REMOVE_ENTITIES)); - panelBuilder.item(21, this.createRequirementButton(RequirementButton.REQUIRED_BLOCKS)); - panelBuilder.item(30, this.createRequirementButton(RequirementButton.REMOVE_BLOCKS)); + panelBuilder.item(21, this.createRequirementButton(RequirementButton.REQUIRED_BLOCKS)); + panelBuilder.item(22, this.createRequirementButton(RequirementButton.REQUIRED_MATERIALTAGS)); + panelBuilder.item(30, this.createRequirementButton(RequirementButton.REMOVE_BLOCKS)); - panelBuilder.item(23, this.createRequirementButton(RequirementButton.SEARCH_RADIUS)); - panelBuilder.item(25, this.createRequirementButton(RequirementButton.REQUIRED_PERMISSIONS)); + + panelBuilder.item(23, this.createRequirementButton(RequirementButton.SEARCH_RADIUS)); + panelBuilder.item(25, this.createRequirementButton(RequirementButton.REQUIRED_PERMISSIONS)); } /** @@ -187,18 +191,18 @@ private void buildIslandRequirementsPanel(PanelBuilder panelBuilder) { * @param panelBuilder PanelBuilder where icons must be added. */ private void buildInventoryRequirementsPanel(PanelBuilder panelBuilder) { - panelBuilder.item(10, this.createRequirementButton(RequirementButton.REQUIRED_ITEMS)); - panelBuilder.item(19, this.createRequirementButton(RequirementButton.REMOVE_ITEMS)); + panelBuilder.item(10, this.createRequirementButton(RequirementButton.REQUIRED_ITEMS)); + panelBuilder.item(19, this.createRequirementButton(RequirementButton.REMOVE_ITEMS)); - if (!this.challenge.getRequirements().getRequiredItems().isEmpty()) { - panelBuilder.item(12, this.createRequirementButton(RequirementButton.ADD_IGNORED_META)); + if (!this.challenge.getRequirements().getRequiredItems().isEmpty()) { + panelBuilder.item(12, this.createRequirementButton(RequirementButton.ADD_IGNORED_META)); - if (!this.challenge.getRequirements().getIgnoreMetaData().isEmpty()) { - panelBuilder.item(21, this.createRequirementButton(RequirementButton.REMOVE_IGNORED_META)); - } - } + if (!this.challenge.getRequirements().getIgnoreMetaData().isEmpty()) { + panelBuilder.item(21, this.createRequirementButton(RequirementButton.REMOVE_IGNORED_META)); + } + } - panelBuilder.item(25, this.createRequirementButton(RequirementButton.REQUIRED_PERMISSIONS)); + panelBuilder.item(25, this.createRequirementButton(RequirementButton.REQUIRED_PERMISSIONS)); } /** @@ -208,15 +212,15 @@ private void buildInventoryRequirementsPanel(PanelBuilder panelBuilder) { * @param panelBuilder PanelBuilder where icons must be added. */ private void buildOtherRequirementsPanel(PanelBuilder panelBuilder) { - panelBuilder.item(10, this.createRequirementButton(RequirementButton.REQUIRED_EXPERIENCE)); - panelBuilder.item(19, this.createRequirementButton(RequirementButton.REMOVE_EXPERIENCE)); + panelBuilder.item(10, this.createRequirementButton(RequirementButton.REQUIRED_EXPERIENCE)); + panelBuilder.item(19, this.createRequirementButton(RequirementButton.REMOVE_EXPERIENCE)); - panelBuilder.item(12, this.createRequirementButton(RequirementButton.REQUIRED_MONEY)); - panelBuilder.item(21, this.createRequirementButton(RequirementButton.REMOVE_MONEY)); + panelBuilder.item(12, this.createRequirementButton(RequirementButton.REQUIRED_MONEY)); + panelBuilder.item(21, this.createRequirementButton(RequirementButton.REMOVE_MONEY)); - panelBuilder.item(23, this.createRequirementButton(RequirementButton.REQUIRED_LEVEL)); + panelBuilder.item(23, this.createRequirementButton(RequirementButton.REQUIRED_LEVEL)); - panelBuilder.item(25, this.createRequirementButton(RequirementButton.REQUIRED_PERMISSIONS)); + panelBuilder.item(25, this.createRequirementButton(RequirementButton.REQUIRED_PERMISSIONS)); } /** @@ -226,24 +230,24 @@ private void buildOtherRequirementsPanel(PanelBuilder panelBuilder) { * @param panelBuilder PanelBuilder where icons must be added. */ private void buildStatisticRequirementsPanel(PanelBuilder panelBuilder) { - panelBuilder.item(10, this.createRequirementButton(RequirementButton.STATISTIC)); - panelBuilder.item(19, this.createRequirementButton(RequirementButton.REMOVE_STATISTIC)); + panelBuilder.item(10, this.createRequirementButton(RequirementButton.STATISTIC)); + panelBuilder.item(19, this.createRequirementButton(RequirementButton.REMOVE_STATISTIC)); - panelBuilder.item(11, this.createRequirementButton(RequirementButton.STATISTIC_AMOUNT)); + panelBuilder.item(11, this.createRequirementButton(RequirementButton.STATISTIC_AMOUNT)); - StatisticRequirements requirements = this.challenge.getRequirements(); + StatisticRequirements requirements = this.challenge.getRequirements(); - if (requirements.getStatistic() != null) { - switch (requirements.getStatistic().getType()) { - case ITEM -> panelBuilder.item(13, this.createRequirementButton(RequirementButton.STATISTIC_ITEMS)); - case BLOCK -> panelBuilder.item(13, this.createRequirementButton(RequirementButton.STATISTIC_BLOCKS)); - case ENTITY -> panelBuilder.item(13, this.createRequirementButton(RequirementButton.STATISTIC_ENTITIES)); - default -> { - } - } - } + if (requirements.getStatistic() != null) { + switch (requirements.getStatistic().getType()) { + case ITEM -> panelBuilder.item(13, this.createRequirementButton(RequirementButton.STATISTIC_ITEMS)); + case BLOCK -> panelBuilder.item(13, this.createRequirementButton(RequirementButton.STATISTIC_BLOCKS)); + case ENTITY -> panelBuilder.item(13, this.createRequirementButton(RequirementButton.STATISTIC_ENTITIES)); + default -> { + } + } + } - panelBuilder.item(25, this.createRequirementButton(RequirementButton.REQUIRED_PERMISSIONS)); + panelBuilder.item(25, this.createRequirementButton(RequirementButton.REQUIRED_PERMISSIONS)); } /** @@ -252,34 +256,34 @@ private void buildStatisticRequirementsPanel(PanelBuilder panelBuilder) { * @param panelBuilder PanelBuilder where icons must be added. */ private void buildRewardsPanel(PanelBuilder panelBuilder) { - panelBuilder.item(10, this.createRewardButton(RewardButton.REWARD_TEXT)); - panelBuilder.item(19, this.createRewardButton(RewardButton.REWARD_COMMANDS)); + panelBuilder.item(10, this.createRewardButton(RewardButton.REWARD_TEXT)); + panelBuilder.item(19, this.createRewardButton(RewardButton.REWARD_COMMANDS)); - panelBuilder.item(11, this.createRewardButton(RewardButton.REWARD_ITEMS)); - panelBuilder.item(20, this.createRewardButton(RewardButton.REWARD_EXPERIENCE)); - panelBuilder.item(29, this.createRewardButton(RewardButton.REWARD_MONEY)); + panelBuilder.item(11, this.createRewardButton(RewardButton.REWARD_ITEMS)); + panelBuilder.item(20, this.createRewardButton(RewardButton.REWARD_EXPERIENCE)); + panelBuilder.item(29, this.createRewardButton(RewardButton.REWARD_MONEY)); - panelBuilder.item(22, this.createRewardButton(RewardButton.REPEATABLE)); + panelBuilder.item(22, this.createRewardButton(RewardButton.REPEATABLE)); - if (!this.challenge.getRewardItems().isEmpty() || !this.challenge.getRepeatItemReward().isEmpty()) { - panelBuilder.item(31, this.createRewardButton(RewardButton.ADD_IGNORED_META)); - } + if (!this.challenge.getRewardItems().isEmpty() || !this.challenge.getRepeatItemReward().isEmpty()) { + panelBuilder.item(31, this.createRewardButton(RewardButton.ADD_IGNORED_META)); + } - if (!this.challenge.getIgnoreRewardMetaData().isEmpty()) { - panelBuilder.item(32, this.createRewardButton(RewardButton.REMOVE_IGNORED_META)); - } + if (!this.challenge.getIgnoreRewardMetaData().isEmpty()) { + panelBuilder.item(32, this.createRewardButton(RewardButton.REMOVE_IGNORED_META)); + } - if (this.challenge.isRepeatable()) { - panelBuilder.item(13, this.createRewardButton(RewardButton.COOL_DOWN)); - panelBuilder.item(23, this.createRewardButton(RewardButton.REPEAT_COUNT)); + if (this.challenge.isRepeatable()) { + panelBuilder.item(13, this.createRewardButton(RewardButton.COOL_DOWN)); + panelBuilder.item(23, this.createRewardButton(RewardButton.REPEAT_COUNT)); - panelBuilder.item(15, this.createRewardButton(RewardButton.REPEAT_REWARD_TEXT)); - panelBuilder.item(24, this.createRewardButton(RewardButton.REPEAT_REWARD_COMMANDS)); + panelBuilder.item(15, this.createRewardButton(RewardButton.REPEAT_REWARD_TEXT)); + panelBuilder.item(24, this.createRewardButton(RewardButton.REPEAT_REWARD_COMMANDS)); - panelBuilder.item(16, this.createRewardButton(RewardButton.REPEAT_REWARD_ITEMS)); - panelBuilder.item(25, this.createRewardButton(RewardButton.REPEAT_REWARD_EXPERIENCE)); - panelBuilder.item(34, this.createRewardButton(RewardButton.REPEAT_REWARD_MONEY)); - } + panelBuilder.item(16, this.createRewardButton(RewardButton.REPEAT_REWARD_ITEMS)); + panelBuilder.item(25, this.createRewardButton(RewardButton.REPEAT_REWARD_EXPERIENCE)); + panelBuilder.item(34, this.createRewardButton(RewardButton.REPEAT_REWARD_MONEY)); + } } // --------------------------------------------------------------------- @@ -293,58 +297,58 @@ private void buildRewardsPanel(PanelBuilder panelBuilder) { * @return PanelItem that represents given menu type. */ private PanelItem createMenuButton(MenuType menuType) { - final String reference = Constants.BUTTON + menuType.name().toLowerCase() + "."; - - final String name = this.user.getTranslation(reference + "name"); - final List description = new ArrayList<>(3); - description.add(this.user.getTranslation(reference + "description")); - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-select")); - - ItemStack icon; - boolean glow; - PanelItem.ClickHandler clickHandler; - - switch (menuType) { - case PROPERTIES -> { - icon = new ItemStack(Material.CRAFTING_TABLE); - clickHandler = (panel, user, clickType, slot) -> { - this.currentMenuType = MenuType.PROPERTIES; - this.build(); - - return true; - }; - glow = this.currentMenuType.equals(MenuType.PROPERTIES); - } - case REQUIREMENTS -> { - icon = new ItemStack(Material.HOPPER); - clickHandler = (panel, user, clickType, slot) -> { - this.currentMenuType = MenuType.REQUIREMENTS; - this.build(); - - return true; - }; - glow = this.currentMenuType.equals(MenuType.REQUIREMENTS); - } - case REWARDS -> { - icon = new ItemStack(Material.DROPPER); - clickHandler = (panel, user, clickType, slot) -> { - this.currentMenuType = MenuType.REWARDS; - this.build(); - - return true; - }; - glow = this.currentMenuType.equals(MenuType.REWARDS); - } - default -> { - icon = new ItemStack(Material.PAPER); - clickHandler = null; - glow = false; - } - } - - return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) - .clickHandler(clickHandler).build(); + final String reference = Constants.BUTTON + menuType.name().toLowerCase() + "."; + + final String name = this.user.getTranslation(reference + "name"); + final List description = new ArrayList<>(3); + description.add(this.user.getTranslation(reference + "description")); + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-select")); + + ItemStack icon; + boolean glow; + PanelItem.ClickHandler clickHandler; + + switch (menuType) { + case PROPERTIES -> { + icon = new ItemStack(Material.CRAFTING_TABLE); + clickHandler = (panel, user, clickType, slot) -> { + this.currentMenuType = MenuType.PROPERTIES; + this.build(); + + return true; + }; + glow = this.currentMenuType.equals(MenuType.PROPERTIES); + } + case REQUIREMENTS -> { + icon = new ItemStack(Material.HOPPER); + clickHandler = (panel, user, clickType, slot) -> { + this.currentMenuType = MenuType.REQUIREMENTS; + this.build(); + + return true; + }; + glow = this.currentMenuType.equals(MenuType.REQUIREMENTS); + } + case REWARDS -> { + icon = new ItemStack(Material.DROPPER); + clickHandler = (panel, user, clickType, slot) -> { + this.currentMenuType = MenuType.REWARDS; + this.build(); + + return true; + }; + glow = this.currentMenuType.equals(MenuType.REWARDS); + } + default -> { + icon = new ItemStack(Material.PAPER); + clickHandler = null; + glow = false; + } + } + + return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) + .clickHandler(clickHandler).build(); } /** @@ -354,204 +358,204 @@ private PanelItem createMenuButton(MenuType menuType) { * @return PanelItem that represents given button. */ private PanelItem createButton(Button button) { - final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; - - final String name = this.user.getTranslation(reference + "name"); - final List description = new ArrayList<>(3); - description.add(this.user.getTranslation(reference + "description")); - - ItemStack icon; - boolean glow; - PanelItem.ClickHandler clickHandler; - - switch (button) { - case NAME -> { - description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NAME, - this.challenge.getFriendlyName())); - - icon = new ItemStack(Material.NAME_TAG); - - clickHandler = (panel, user, clickType, i) -> { - // Create consumer that process description change - Consumer consumer = value -> { - if (value != null) { - this.challenge.setFriendlyName(value); - } - - this.build(); - }; - - // start conversation - ConversationUtils.createStringInput(consumer, user, - user.getTranslation(Constants.CONVERSATIONS + "write-name"), - user.getTranslation(Constants.CONVERSATIONS + "name-changed")); - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case DEPLOYED -> { - description - .add(this.user.getTranslation(reference + (this.challenge.isDeployed() ? "enabled" : "disabled"))); - - icon = new ItemStack(Material.LEVER); - clickHandler = (panel, user, clickType, slot) -> { - if (this.challenge.isValid()) { - this.challenge.setDeployed(!this.challenge.isDeployed()); - } else { - Utils.sendMessage(this.user, this.world, Constants.CONVERSATIONS + "invalid-challenge", - Constants.PARAMETER_CHALLENGE, this.challenge.getFriendlyName()); - this.challenge.setDeployed(false); - } - - this.build(); - return true; - }; - glow = this.challenge.isDeployed(); - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); - } - case ICON -> { - icon = this.challenge.getIcon(); - clickHandler = (panel, user, clickType, i) -> { - this.selectedButton = button; - this.build(); - return true; - }; - glow = this.selectedButton == button; - - if (this.selectedButton != button) { - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } else { - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-on-item")); - } - } - case DESCRIPTION -> { - icon = new ItemStack(Material.WRITTEN_BOOK); - - description.add(this.user.getTranslation(reference + "value")); - this.challenge.getDescription().forEach(line -> description.add(Util.translateColorCodes(line))); - - clickHandler = (panel, user, clickType, i) -> { - // Create consumer that process description change - Consumer> consumer = value -> { - if (value != null) { - this.challenge.setDescription(value); - } - - this.build(); - }; - - if (!this.challenge.getDescription().isEmpty() && clickType.isShiftClick()) { - // Reset to the empty value - consumer.accept(Collections.emptyList()); - } else { - // start conversation - ConversationUtils.createStringListInput(consumer, user, - user.getTranslation(Constants.CONVERSATIONS + "write-description"), - user.getTranslation(Constants.CONVERSATIONS + "description-changed")); - } - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - - if (!this.challenge.getDescription().isEmpty()) { - description.add(this.user.getTranslation(Constants.TIPS + "shift-click-to-reset")); - } - } - case ORDER -> { - description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, - String.valueOf(this.challenge.getOrder()))); - - icon = new ItemStack(Material.HOPPER, Math.max(1, this.challenge.getOrder())); - clickHandler = (panel, user, clickType, i) -> { - Consumer numberConsumer = number -> { - if (number != null) { - this.challenge.setOrder(number.intValue()); - } - - // reopen panel - this.build(); - }; - - ConversationUtils.createNumericInput(numberConsumer, this.user, - this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 0, 2000); - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case ENVIRONMENT -> { - description.add(this.user.getTranslation( - this.challenge.getEnvironment().contains(World.Environment.NORMAL) ? reference + "enabled" - : reference + "disabled") - + Utils.prettifyObject(World.Environment.NORMAL, this.user)); - description.add(this.user.getTranslation( - this.challenge.getEnvironment().contains(World.Environment.NETHER) ? reference + "enabled" - : reference + "disabled") - + Utils.prettifyObject(World.Environment.NETHER, this.user)); - description.add(this.user.getTranslation( - this.challenge.getEnvironment().contains(World.Environment.THE_END) ? reference + "enabled" - : reference + "disabled") - + Utils.prettifyObject(World.Environment.THE_END, this.user)); - - icon = new ItemStack(Material.DROPPER); - clickHandler = (panel, user, clickType, slot) -> { - EnvironmentSelector.open(this.user, this.challenge.getEnvironment(), (status, value) -> { - if (status) { - this.challenge.setEnvironment(value); - } - - this.build(); - }); - - return true; - }; - glow = false; - } - case REMOVE_ON_COMPLETE -> { - description.add(this.user - .getTranslation(reference + (this.challenge.isRemoveWhenCompleted() ? "enabled" : "disabled"))); - - if (this.challenge.isRemoveWhenCompleted()) { - icon = new ItemStack(Material.LAVA_BUCKET); - } else { - icon = new ItemStack(Material.BUCKET); - } - - clickHandler = (panel, user, clickType, slot) -> { - this.challenge.setRemoveWhenCompleted(!this.challenge.isRemoveWhenCompleted()); - this.build(); - - return true; - }; - glow = this.challenge.isRemoveWhenCompleted(); - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); - } - default -> { - icon = new ItemStack(Material.PAPER); - clickHandler = null; - glow = false; - } - } - - return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) - .clickHandler(clickHandler).build(); + final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; + + final String name = this.user.getTranslation(reference + "name"); + final List description = new ArrayList<>(3); + description.add(this.user.getTranslation(reference + "description")); + + ItemStack icon; + boolean glow; + PanelItem.ClickHandler clickHandler; + + switch (button) { + case NAME -> { + description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NAME, + this.challenge.getFriendlyName())); + + icon = new ItemStack(Material.NAME_TAG); + + clickHandler = (panel, user, clickType, i) -> { + // Create consumer that process description change + Consumer consumer = value -> { + if (value != null) { + this.challenge.setFriendlyName(value); + } + + this.build(); + }; + + // start conversation + ConversationUtils.createStringInput(consumer, user, + user.getTranslation(Constants.CONVERSATIONS + "write-name"), + user.getTranslation(Constants.CONVERSATIONS + "name-changed")); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case DEPLOYED -> { + description + .add(this.user.getTranslation(reference + (this.challenge.isDeployed() ? "enabled" : "disabled"))); + + icon = new ItemStack(Material.LEVER); + clickHandler = (panel, user, clickType, slot) -> { + if (this.challenge.isValid()) { + this.challenge.setDeployed(!this.challenge.isDeployed()); + } else { + Utils.sendMessage(this.user, this.world, Constants.CONVERSATIONS + "invalid-challenge", + Constants.PARAMETER_CHALLENGE, this.challenge.getFriendlyName()); + this.challenge.setDeployed(false); + } + + this.build(); + return true; + }; + glow = this.challenge.isDeployed(); + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); + } + case ICON -> { + icon = this.challenge.getIcon(); + clickHandler = (panel, user, clickType, i) -> { + this.selectedButton = button; + this.build(); + return true; + }; + glow = this.selectedButton == button; + + if (this.selectedButton != button) { + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } else { + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-on-item")); + } + } + case DESCRIPTION -> { + icon = new ItemStack(Material.WRITTEN_BOOK); + + description.add(this.user.getTranslation(reference + "value")); + this.challenge.getDescription().forEach(line -> description.add(Util.translateColorCodes(line))); + + clickHandler = (panel, user, clickType, i) -> { + // Create consumer that process description change + Consumer> consumer = value -> { + if (value != null) { + this.challenge.setDescription(value); + } + + this.build(); + }; + + if (!this.challenge.getDescription().isEmpty() && clickType.isShiftClick()) { + // Reset to the empty value + consumer.accept(Collections.emptyList()); + } else { + // start conversation + ConversationUtils.createStringListInput(consumer, user, + user.getTranslation(Constants.CONVERSATIONS + "write-description"), + user.getTranslation(Constants.CONVERSATIONS + "description-changed")); + } + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + + if (!this.challenge.getDescription().isEmpty()) { + description.add(this.user.getTranslation(Constants.TIPS + "shift-click-to-reset")); + } + } + case ORDER -> { + description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, + String.valueOf(this.challenge.getOrder()))); + + icon = new ItemStack(Material.HOPPER, Math.max(1, this.challenge.getOrder())); + clickHandler = (panel, user, clickType, i) -> { + Consumer numberConsumer = number -> { + if (number != null) { + this.challenge.setOrder(number.intValue()); + } + + // reopen panel + this.build(); + }; + + ConversationUtils.createNumericInput(numberConsumer, this.user, + this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 0, 2000); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case ENVIRONMENT -> { + description.add(this.user.getTranslation( + this.challenge.getEnvironment().contains(World.Environment.NORMAL) ? reference + "enabled" + : reference + "disabled") + + Utils.prettifyObject(World.Environment.NORMAL, this.user)); + description.add(this.user.getTranslation( + this.challenge.getEnvironment().contains(World.Environment.NETHER) ? reference + "enabled" + : reference + "disabled") + + Utils.prettifyObject(World.Environment.NETHER, this.user)); + description.add(this.user.getTranslation( + this.challenge.getEnvironment().contains(World.Environment.THE_END) ? reference + "enabled" + : reference + "disabled") + + Utils.prettifyObject(World.Environment.THE_END, this.user)); + + icon = new ItemStack(Material.DROPPER); + clickHandler = (panel, user, clickType, slot) -> { + EnvironmentSelector.open(this.user, this.challenge.getEnvironment(), (status, value) -> { + if (status) { + this.challenge.setEnvironment(value); + } + + this.build(); + }); + + return true; + }; + glow = false; + } + case REMOVE_ON_COMPLETE -> { + description.add(this.user + .getTranslation(reference + (this.challenge.isRemoveWhenCompleted() ? "enabled" : "disabled"))); + + if (this.challenge.isRemoveWhenCompleted()) { + icon = new ItemStack(Material.LAVA_BUCKET); + } else { + icon = new ItemStack(Material.BUCKET); + } + + clickHandler = (panel, user, clickType, slot) -> { + this.challenge.setRemoveWhenCompleted(!this.challenge.isRemoveWhenCompleted()); + this.build(); + + return true; + }; + glow = this.challenge.isRemoveWhenCompleted(); + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); + } + default -> { + icon = new ItemStack(Material.PAPER); + clickHandler = null; + glow = false; + } + } + + return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) + .clickHandler(clickHandler).build(); } /** @@ -561,79 +565,80 @@ private PanelItem createButton(Button button) { * @return PanelItem that represents given button. */ private PanelItem createRequirementButton(RequirementButton button) { - switch (button) { - case REQUIRED_PERMISSIONS -> { - String reference = Constants.BUTTON + button.name().toLowerCase() + "."; - - String name = this.user.getTranslation(reference + "name"); - final List description = new ArrayList<>(3); - description.add(this.user.getTranslation(reference + "description")); - - if (this.challenge.getRequirements().getRequiredPermissions().isEmpty()) { - description.add(this.user.getTranslation(reference + "none")); - } else { - description.add(this.user.getTranslation(reference + "title")); - - this.challenge.getRequirements().getRequiredPermissions().forEach(permission -> description - .add(this.user.getTranslation(reference + "permission", "[permission]", permission))); - } - - ItemStack icon = new ItemStack(Material.REDSTONE_LAMP); - - PanelItem.ClickHandler clickHandler = (panel, user, clickType, i) -> { - // Create consumer that process description change - Consumer> consumer = value -> { - if (value != null) { - this.challenge.getRequirements().setRequiredPermissions(new HashSet<>(value)); - } - - this.build(); - }; - - if (!this.challenge.getRequirements().getRequiredPermissions().isEmpty() && clickType.isShiftClick()) { - // Reset to the empty value - consumer.accept(Collections.emptyList()); - } else { - // start conversation - ConversationUtils.createStringListInput(consumer, user, - user.getTranslation(Constants.CONVERSATIONS + "write-permissions"), - user.getTranslation(Constants.CONVERSATIONS + "permissions-changed")); - } - - return true; - }; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - - if (!this.challenge.getRequirements().getRequiredPermissions().isEmpty()) { - description.add(this.user.getTranslation(Constants.TIPS + "shift-click-to-reset")); - } - - return new PanelItemBuilder().icon(icon).name(name).description(description).clickHandler(clickHandler) - .build(); - } - // Buttons for Island Requirements - case REQUIRED_ENTITIES, REMOVE_ENTITIES, REQUIRED_BLOCKS, REMOVE_BLOCKS, SEARCH_RADIUS -> { - return this.createIslandRequirementButton(button); - } - // Buttons for Inventory Requirements - case REQUIRED_ITEMS, REMOVE_ITEMS, ADD_IGNORED_META, REMOVE_IGNORED_META -> { - return this.createInventoryRequirementButton(button); - } - // Buttons for Other Requirements - case REQUIRED_EXPERIENCE, REMOVE_EXPERIENCE, REQUIRED_LEVEL, REQUIRED_MONEY, REMOVE_MONEY -> { - return this.createOtherRequirementButton(button); - } - // Buttons for Statistic Requirements - case STATISTIC, STATISTIC_BLOCKS, STATISTIC_ITEMS, STATISTIC_ENTITIES, STATISTIC_AMOUNT, REMOVE_STATISTIC -> { - return this.createStatisticRequirementButton(button); - } - // Default behaviour. - default -> { - return PanelItem.empty(); - } - } + switch (button) { + case REQUIRED_PERMISSIONS -> { + String reference = Constants.BUTTON + button.name().toLowerCase() + "."; + + String name = this.user.getTranslation(reference + "name"); + final List description = new ArrayList<>(3); + description.add(this.user.getTranslation(reference + "description")); + + if (this.challenge.getRequirements().getRequiredPermissions().isEmpty()) { + description.add(this.user.getTranslation(reference + "none")); + } else { + description.add(this.user.getTranslation(reference + "title")); + + this.challenge.getRequirements().getRequiredPermissions().forEach(permission -> description + .add(this.user.getTranslation(reference + "permission", "[permission]", permission))); + } + + ItemStack icon = new ItemStack(Material.REDSTONE_LAMP); + + PanelItem.ClickHandler clickHandler = (panel, user, clickType, i) -> { + // Create consumer that process description change + Consumer> consumer = value -> { + if (value != null) { + this.challenge.getRequirements().setRequiredPermissions(new HashSet<>(value)); + } + + this.build(); + }; + + if (!this.challenge.getRequirements().getRequiredPermissions().isEmpty() && clickType.isShiftClick()) { + // Reset to the empty value + consumer.accept(Collections.emptyList()); + } else { + // start conversation + ConversationUtils.createStringListInput(consumer, user, + user.getTranslation(Constants.CONVERSATIONS + "write-permissions"), + user.getTranslation(Constants.CONVERSATIONS + "permissions-changed")); + } + + return true; + }; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + + if (!this.challenge.getRequirements().getRequiredPermissions().isEmpty()) { + description.add(this.user.getTranslation(Constants.TIPS + "shift-click-to-reset")); + } + + return new PanelItemBuilder().icon(icon).name(name).description(description).clickHandler(clickHandler) + .build(); + } + // Buttons for Island Requirements + case REQUIRED_ENTITIES, REMOVE_ENTITIES, REQUIRED_BLOCKS, REMOVE_BLOCKS, SEARCH_RADIUS, + REQUIRED_MATERIALTAGS -> { + return this.createIslandRequirementButton(button); + } + // Buttons for Inventory Requirements + case REQUIRED_ITEMS, REMOVE_ITEMS, ADD_IGNORED_META, REMOVE_IGNORED_META -> { + return this.createInventoryRequirementButton(button); + } + // Buttons for Other Requirements + case REQUIRED_EXPERIENCE, REMOVE_EXPERIENCE, REQUIRED_LEVEL, REQUIRED_MONEY, REMOVE_MONEY -> { + return this.createOtherRequirementButton(button); + } + // Buttons for Statistic Requirements + case STATISTIC, STATISTIC_BLOCKS, STATISTIC_ITEMS, STATISTIC_ENTITIES, STATISTIC_AMOUNT, REMOVE_STATISTIC -> { + return this.createStatisticRequirementButton(button); + } + // Default behaviour. + default -> { + return PanelItem.empty(); + } + } } /** @@ -643,127 +648,148 @@ private PanelItem createRequirementButton(RequirementButton button) { * @return PanelItem that represents given button. */ private PanelItem createIslandRequirementButton(RequirementButton button) { - final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; - - final String name = this.user.getTranslation(reference + "name"); - final List description = new ArrayList<>(3); - description.add(this.user.getTranslation(reference + "description")); - - ItemStack icon; - boolean glow; - PanelItem.ClickHandler clickHandler; - - final IslandRequirements requirements = this.challenge.getRequirements(); - - switch (button) { - case REQUIRED_ENTITIES -> { - if (requirements.getRequiredEntities().isEmpty()) { - description.add(this.user.getTranslation(reference + "none")); - } else { - description.add(this.user.getTranslation(reference + "title")); - - requirements.getRequiredEntities().forEach( - (entity, count) -> description.add(this.user.getTranslation(reference + "list", "[entity]", - Utils.prettifyObject(entity, this.user), "[number]", String.valueOf(count)))); - } - - icon = new ItemStack(Material.CREEPER_HEAD); - clickHandler = (panel, user, clickType, slot) -> { - ManageEntitiesPanel.open(this, requirements.getRequiredEntities()); - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case REMOVE_ENTITIES -> { - description.add( - this.user.getTranslation(reference + (requirements.isRemoveEntities() ? "enabled" : "disabled"))); - - icon = new ItemStack(Material.LEVER); - clickHandler = (panel, user, clickType, slot) -> { - requirements.setRemoveEntities(!requirements.isRemoveEntities()); - this.build(); - return true; - }; - glow = requirements.isRemoveEntities(); - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); - } - case REQUIRED_BLOCKS -> { - if (requirements.getRequiredBlocks().isEmpty()) { - description.add(this.user.getTranslation(reference + "none")); - } else { - description.add(this.user.getTranslation(reference + "title")); - - requirements.getRequiredBlocks() - .forEach((block, count) -> description.add(this.user.getTranslation(reference + "list", - "[block]", Utils.prettifyObject(block, this.user), "[number]", String.valueOf(count)))); - } - - icon = new ItemStack(Material.STONE); - clickHandler = (panel, user, clickType, slot) -> { - ManageBlocksPanel.open(this, requirements.getRequiredBlocks()); - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case REMOVE_BLOCKS -> { - description.add( - this.user.getTranslation(reference + (requirements.isRemoveBlocks() ? "enabled" : "disabled"))); - - icon = new ItemStack(Material.LEVER); - clickHandler = (panel, user, clickType, slot) -> { - requirements.setRemoveBlocks(!requirements.isRemoveBlocks()); - this.build(); - return true; - }; - glow = requirements.isRemoveBlocks(); - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); - } - case SEARCH_RADIUS -> { - description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, - String.valueOf(requirements.getSearchRadius()))); - icon = new ItemStack(Material.COBBLESTONE_WALL); - clickHandler = (panel, user, clickType, i) -> { - Consumer numberConsumer = number -> { - if (number != null) { - requirements.setSearchRadius(number.intValue()); - } - - // reopen panel - this.build(); - }; - - int maxSearchDistance = this.addon.getPlugin().getIWM().getAddon(this.world) - .map(gameModeAddon -> gameModeAddon.getWorldSettings().getIslandDistance()).orElse(100); - - ConversationUtils.createNumericInput(numberConsumer, this.user, - this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 1, maxSearchDistance); - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - default -> { - icon = new ItemStack(Material.PAPER); - clickHandler = null; - glow = false; - } - } - - return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) - .clickHandler(clickHandler).build(); + final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; + + final String name = this.user.getTranslation(reference + "name"); + final List description = new ArrayList<>(3); + description.add(this.user.getTranslation(reference + "description")); + + ItemStack icon; + boolean glow; + PanelItem.ClickHandler clickHandler; + + final IslandRequirements requirements = this.challenge.getRequirements(); + + switch (button) { + case REQUIRED_ENTITIES -> { + if (requirements.getRequiredEntities().isEmpty()) { + description.add(this.user.getTranslation(reference + "none")); + } else { + description.add(this.user.getTranslation(reference + "title")); + + requirements.getRequiredEntities().forEach( + (entity, count) -> description.add(this.user.getTranslation(reference + "list", "[entity]", + Utils.prettifyObject(entity, this.user), "[number]", String.valueOf(count)))); + } + + icon = new ItemStack(Material.CREEPER_HEAD); + clickHandler = (panel, user, clickType, slot) -> { + ManageEntitiesPanel.open(this, requirements.getRequiredEntities()); + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case REMOVE_ENTITIES -> { + description.add( + this.user.getTranslation(reference + (requirements.isRemoveEntities() ? "enabled" : "disabled"))); + + icon = new ItemStack(Material.LEVER); + clickHandler = (panel, user, clickType, slot) -> { + requirements.setRemoveEntities(!requirements.isRemoveEntities()); + this.build(); + return true; + }; + glow = requirements.isRemoveEntities(); + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); + } + case REQUIRED_MATERIALTAGS -> { + if (requirements.getRequiredMaterialTags().isEmpty()) { + description.add(this.user.getTranslation(reference + "none")); + } else { + description.add(this.user.getTranslation(reference + "title")); + // Add Material Tags only + requirements.getRequiredMaterialTags() + .forEach((block, count) -> description.add(this.user.getTranslation(reference + "list", + "[block]", Utils.prettifyObject(block, this.user), "[number]", String.valueOf(count)))); + } + + icon = new ItemStack(Material.STONE_BRICKS); + clickHandler = (panel, user, clickType, slot) -> { + ManageTagsPanel.open(this, requirements.getRequiredMaterialTags()); + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + + case REQUIRED_BLOCKS -> { + if (requirements.getRequiredBlocks().isEmpty()) { + description.add(this.user.getTranslation(reference + "none")); + } else { + description.add(this.user.getTranslation(reference + "title")); + requirements.getRequiredBlocks() + .forEach((block, count) -> description.add(this.user.getTranslation(reference + "list", + "[block]", Utils.prettifyObject(block, this.user), "[number]", String.valueOf(count)))); + } + + icon = new ItemStack(Material.STONE); + clickHandler = (panel, user, clickType, slot) -> { + ManageBlocksPanel.open(this, requirements.getRequiredBlocks()); + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case REMOVE_BLOCKS -> { + description.add( + this.user.getTranslation(reference + (requirements.isRemoveBlocks() ? "enabled" : "disabled"))); + + icon = new ItemStack(Material.LEVER); + clickHandler = (panel, user, clickType, slot) -> { + requirements.setRemoveBlocks(!requirements.isRemoveBlocks()); + this.build(); + return true; + }; + glow = requirements.isRemoveBlocks(); + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); + } + case SEARCH_RADIUS -> { + description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, + String.valueOf(requirements.getSearchRadius()))); + icon = new ItemStack(Material.COBBLESTONE_WALL); + clickHandler = (panel, user, clickType, i) -> { + Consumer numberConsumer = number -> { + if (number != null) { + requirements.setSearchRadius(number.intValue()); + } + + // reopen panel + this.build(); + }; + + int maxSearchDistance = this.addon.getPlugin().getIWM().getAddon(this.world) + .map(gameModeAddon -> gameModeAddon.getWorldSettings().getIslandDistance()).orElse(100); + + ConversationUtils.createNumericInput(numberConsumer, this.user, + this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 1, maxSearchDistance); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + default -> { + icon = new ItemStack(Material.PAPER); + clickHandler = null; + glow = false; + } + } + + return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) + .clickHandler(clickHandler).build(); } /** @@ -773,143 +799,143 @@ private PanelItem createIslandRequirementButton(RequirementButton button) { * @return PanelItem that represents given button. */ private PanelItem createInventoryRequirementButton(RequirementButton button) { - final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; - - final String name = this.user.getTranslation(reference + "name"); - final List description = new ArrayList<>(3); - description.add(this.user.getTranslation(reference + "description")); - - ItemStack icon; - boolean glow; - PanelItem.ClickHandler clickHandler; - - final InventoryRequirements requirements = this.challenge.getRequirements(); - - switch (button) { - case REQUIRED_ITEMS -> { - if (requirements.getRequiredItems().isEmpty()) { - description.add(this.user.getTranslation(reference + "none")); - } else { - description.add(this.user.getTranslation(reference + "title")); - - Utils.groupEqualItems(requirements.getRequiredItems(), requirements.getIgnoreMetaData()).stream() - .sorted(Comparator.comparing(ItemStack::getType)) - .forEach(itemStack -> description.add(this.user.getTranslationOrNothing(reference + "list", - "[number]", String.valueOf(itemStack.getAmount()), "[item]", - Utils.prettifyObject(itemStack, this.user)))); - } - - icon = new ItemStack(Material.CHEST); - clickHandler = (panel, user, clickType, slot) -> { - ItemSelector.open(this.user, requirements.getRequiredItems(), (status, value) -> { - if (status) { - requirements.setRequiredItems(value); - } - - this.build(); - }); - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case REMOVE_ITEMS -> { - description - .add(this.user.getTranslation(reference + (requirements.isTakeItems() ? "enabled" : "disabled"))); - - icon = new ItemStack(Material.LEVER); - clickHandler = (panel, user, clickType, slot) -> { - requirements.setTakeItems(!requirements.isTakeItems()); - this.build(); - return true; - }; - glow = requirements.isTakeItems(); - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); - } - case ADD_IGNORED_META -> { - if (requirements.getIgnoreMetaData().isEmpty()) { - description.add(this.user.getTranslation(reference + "none")); - } else { - description.add(this.user.getTranslation(reference + "title")); - - requirements.getIgnoreMetaData().stream().sorted(Comparator.comparing(Material::name)) - .forEach(itemStack -> description.add(this.user.getTranslationOrNothing(reference + "list", - "[item]", Utils.prettifyObject(itemStack, this.user)))); - } - - icon = new ItemStack(Material.GREEN_SHULKER_BOX); - - clickHandler = (panel, user, clickType, slot) -> { - if (requirements.getRequiredItems().isEmpty()) { - // Do nothing if no requirements are set. - return true; - } - - // Allow choosing only from inventory items. - Set collection = Arrays.stream(Material.values()).collect(Collectors.toSet()); - requirements.getRequiredItems().stream().map(ItemStack::getType).forEach(collection::remove); - collection.addAll(requirements.getIgnoreMetaData()); - - if (Material.values().length == collection.size()) { - // If there are no items anymore, then do not allow opening gui. - return true; - } - - MultiBlockSelector.open(this.user, MultiBlockSelector.Mode.ANY, collection, (status, materials) -> { - if (status) { - materials.addAll(requirements.getIgnoreMetaData()); - requirements.setIgnoreMetaData(new HashSet<>(materials)); - } - - this.build(); - }); - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-add")); - } - case REMOVE_IGNORED_META -> { - icon = new ItemStack(Material.RED_SHULKER_BOX); - - clickHandler = (panel, user, clickType, slot) -> { - if (requirements.getIgnoreMetaData().isEmpty()) { - // Do nothing if no requirements are set. - return true; - } - - // Allow choosing only from inventory items. - Set collection = Arrays.stream(Material.values()).collect(Collectors.toSet()); - collection.removeAll(requirements.getIgnoreMetaData()); - - MultiBlockSelector.open(this.user, MultiBlockSelector.Mode.ANY, collection, (status, materials) -> { - if (status) { - requirements.getIgnoreMetaData().removeAll(materials); - } - - this.build(); - }); - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-remove")); - } - default -> { - icon = new ItemStack(Material.PAPER); - clickHandler = null; - glow = false; - } - } - - return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) - .clickHandler(clickHandler).build(); + final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; + + final String name = this.user.getTranslation(reference + "name"); + final List description = new ArrayList<>(3); + description.add(this.user.getTranslation(reference + "description")); + + ItemStack icon; + boolean glow; + PanelItem.ClickHandler clickHandler; + + final InventoryRequirements requirements = this.challenge.getRequirements(); + + switch (button) { + case REQUIRED_ITEMS -> { + if (requirements.getRequiredItems().isEmpty()) { + description.add(this.user.getTranslation(reference + "none")); + } else { + description.add(this.user.getTranslation(reference + "title")); + + Utils.groupEqualItems(requirements.getRequiredItems(), requirements.getIgnoreMetaData()).stream() + .sorted(Comparator.comparing(ItemStack::getType)) + .forEach(itemStack -> description.add(this.user.getTranslationOrNothing(reference + "list", + "[number]", String.valueOf(itemStack.getAmount()), "[item]", + Utils.prettifyObject(itemStack, this.user)))); + } + + icon = new ItemStack(Material.CHEST); + clickHandler = (panel, user, clickType, slot) -> { + ItemSelector.open(this.user, requirements.getRequiredItems(), (status, value) -> { + if (status) { + requirements.setRequiredItems(value); + } + + this.build(); + }); + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case REMOVE_ITEMS -> { + description + .add(this.user.getTranslation(reference + (requirements.isTakeItems() ? "enabled" : "disabled"))); + + icon = new ItemStack(Material.LEVER); + clickHandler = (panel, user, clickType, slot) -> { + requirements.setTakeItems(!requirements.isTakeItems()); + this.build(); + return true; + }; + glow = requirements.isTakeItems(); + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); + } + case ADD_IGNORED_META -> { + if (requirements.getIgnoreMetaData().isEmpty()) { + description.add(this.user.getTranslation(reference + "none")); + } else { + description.add(this.user.getTranslation(reference + "title")); + + requirements.getIgnoreMetaData().stream().sorted(Comparator.comparing(Material::name)) + .forEach(itemStack -> description.add(this.user.getTranslationOrNothing(reference + "list", + "[item]", Utils.prettifyObject(itemStack, this.user)))); + } + + icon = new ItemStack(Material.GREEN_SHULKER_BOX); + + clickHandler = (panel, user, clickType, slot) -> { + if (requirements.getRequiredItems().isEmpty()) { + // Do nothing if no requirements are set. + return true; + } + + // Allow choosing only from inventory items. + Set collection = Arrays.stream(Material.values()).collect(Collectors.toSet()); + requirements.getRequiredItems().stream().map(ItemStack::getType).forEach(collection::remove); + collection.addAll(requirements.getIgnoreMetaData()); + + if (Material.values().length == collection.size()) { + // If there are no items anymore, then do not allow opening gui. + return true; + } + + MultiBlockSelector.open(this.user, MultiBlockSelector.Mode.ANY, collection, (status, materials) -> { + if (status) { + materials.addAll(requirements.getIgnoreMetaData()); + requirements.setIgnoreMetaData(new HashSet<>(materials)); + } + + this.build(); + }); + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-add")); + } + case REMOVE_IGNORED_META -> { + icon = new ItemStack(Material.RED_SHULKER_BOX); + + clickHandler = (panel, user, clickType, slot) -> { + if (requirements.getIgnoreMetaData().isEmpty()) { + // Do nothing if no requirements are set. + return true; + } + + // Allow choosing only from inventory items. + Set collection = Arrays.stream(Material.values()).collect(Collectors.toSet()); + collection.removeAll(requirements.getIgnoreMetaData()); + + MultiBlockSelector.open(this.user, MultiBlockSelector.Mode.ANY, collection, (status, materials) -> { + if (status) { + requirements.getIgnoreMetaData().removeAll(materials); + } + + this.build(); + }); + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-remove")); + } + default -> { + icon = new ItemStack(Material.PAPER); + clickHandler = null; + glow = false; + } + } + + return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) + .clickHandler(clickHandler).build(); } /** @@ -919,127 +945,127 @@ private PanelItem createInventoryRequirementButton(RequirementButton button) { * @return PanelItem that represents given button. */ private PanelItem createOtherRequirementButton(RequirementButton button) { - final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; - - final String name = this.user.getTranslation(reference + "name"); - final List description = new ArrayList<>(3); - description.add(this.user.getTranslation(reference + "description")); - - ItemStack icon; - boolean glow; - PanelItem.ClickHandler clickHandler; - - final OtherRequirements requirements = this.challenge.getRequirements(); - - switch (button) { - case REQUIRED_EXPERIENCE -> { - description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, - String.valueOf(requirements.getRequiredExperience()))); - icon = new ItemStack(Material.EXPERIENCE_BOTTLE); - clickHandler = (panel, user, clickType, i) -> { - Consumer numberConsumer = number -> { - if (number != null) { - requirements.setRequiredExperience(number.intValue()); - } - - // reopen panel - this.build(); - }; - ConversationUtils.createNumericInput(numberConsumer, this.user, - this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 0, Integer.MAX_VALUE); - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case REMOVE_EXPERIENCE -> { - description.add( - this.user.getTranslation(reference + (requirements.isTakeExperience() ? "enabled" : "disabled"))); - - icon = new ItemStack(Material.LEVER); - clickHandler = (panel, user, clickType, slot) -> { - requirements.setTakeExperience(!requirements.isTakeExperience()); - this.build(); - return true; - }; - glow = requirements.isTakeExperience(); - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); - } - case REQUIRED_LEVEL -> { - description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, - String.valueOf(requirements.getRequiredIslandLevel()))); - icon = new ItemStack(this.addon.isLevelProvided() ? Material.BEACON : Material.BARRIER); - clickHandler = (panel, user, clickType, i) -> { - Consumer numberConsumer = number -> { - if (number != null) { - requirements.setRequiredIslandLevel(number.longValue()); - } - - // reopen panel - this.build(); - }; - ConversationUtils.createNumericInput(numberConsumer, this.user, - this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 0, Integer.MAX_VALUE); - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case REQUIRED_MONEY -> { - description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, - String.valueOf(requirements.getRequiredMoney()))); - icon = new ItemStack(this.addon.isEconomyProvided() ? Material.GOLD_INGOT : Material.BARRIER); - clickHandler = (panel, user, clickType, i) -> { - Consumer numberConsumer = number -> { - if (number != null) { - requirements.setRequiredMoney(number.doubleValue()); - } - - // reopen panel - this.build(); - }; - ConversationUtils.createNumericInput(numberConsumer, this.user, - this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 0, Double.MAX_VALUE); - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case REMOVE_MONEY -> { - description - .add(this.user.getTranslation(reference + (requirements.isTakeMoney() ? "enabled" : "disabled"))); - - icon = new ItemStack(this.addon.isEconomyProvided() ? Material.LEVER : Material.BARRIER); - clickHandler = (panel, user, clickType, slot) -> { - requirements.setTakeMoney(!requirements.isTakeMoney()); - this.build(); - return true; - }; - glow = requirements.isTakeMoney(); - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); - } - default -> { - icon = new ItemStack(Material.PAPER); - clickHandler = null; - glow = false; - } - } - - return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) - .clickHandler(clickHandler).build(); + final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; + + final String name = this.user.getTranslation(reference + "name"); + final List description = new ArrayList<>(3); + description.add(this.user.getTranslation(reference + "description")); + + ItemStack icon; + boolean glow; + PanelItem.ClickHandler clickHandler; + + final OtherRequirements requirements = this.challenge.getRequirements(); + + switch (button) { + case REQUIRED_EXPERIENCE -> { + description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, + String.valueOf(requirements.getRequiredExperience()))); + icon = new ItemStack(Material.EXPERIENCE_BOTTLE); + clickHandler = (panel, user, clickType, i) -> { + Consumer numberConsumer = number -> { + if (number != null) { + requirements.setRequiredExperience(number.intValue()); + } + + // reopen panel + this.build(); + }; + ConversationUtils.createNumericInput(numberConsumer, this.user, + this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 0, Integer.MAX_VALUE); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case REMOVE_EXPERIENCE -> { + description.add( + this.user.getTranslation(reference + (requirements.isTakeExperience() ? "enabled" : "disabled"))); + + icon = new ItemStack(Material.LEVER); + clickHandler = (panel, user, clickType, slot) -> { + requirements.setTakeExperience(!requirements.isTakeExperience()); + this.build(); + return true; + }; + glow = requirements.isTakeExperience(); + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); + } + case REQUIRED_LEVEL -> { + description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, + String.valueOf(requirements.getRequiredIslandLevel()))); + icon = new ItemStack(this.addon.isLevelProvided() ? Material.BEACON : Material.BARRIER); + clickHandler = (panel, user, clickType, i) -> { + Consumer numberConsumer = number -> { + if (number != null) { + requirements.setRequiredIslandLevel(number.longValue()); + } + + // reopen panel + this.build(); + }; + ConversationUtils.createNumericInput(numberConsumer, this.user, + this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 0, Integer.MAX_VALUE); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case REQUIRED_MONEY -> { + description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, + String.valueOf(requirements.getRequiredMoney()))); + icon = new ItemStack(this.addon.isEconomyProvided() ? Material.GOLD_INGOT : Material.BARRIER); + clickHandler = (panel, user, clickType, i) -> { + Consumer numberConsumer = number -> { + if (number != null) { + requirements.setRequiredMoney(number.doubleValue()); + } + + // reopen panel + this.build(); + }; + ConversationUtils.createNumericInput(numberConsumer, this.user, + this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 0, Double.MAX_VALUE); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case REMOVE_MONEY -> { + description + .add(this.user.getTranslation(reference + (requirements.isTakeMoney() ? "enabled" : "disabled"))); + + icon = new ItemStack(this.addon.isEconomyProvided() ? Material.LEVER : Material.BARRIER); + clickHandler = (panel, user, clickType, slot) -> { + requirements.setTakeMoney(!requirements.isTakeMoney()); + this.build(); + return true; + }; + glow = requirements.isTakeMoney(); + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); + } + default -> { + icon = new ItemStack(Material.PAPER); + clickHandler = null; + glow = false; + } + } + + return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) + .clickHandler(clickHandler).build(); } /** @@ -1049,155 +1075,155 @@ private PanelItem createOtherRequirementButton(RequirementButton button) { * @return PanelItem button. */ private PanelItem createStatisticRequirementButton(RequirementButton button) { - final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; - - final String name = this.user.getTranslation(reference + "name"); - final List description = new ArrayList<>(3); - description.add(this.user.getTranslation(reference + "description")); - - ItemStack icon; - boolean glow; - PanelItem.ClickHandler clickHandler; - - final StatisticRequirements requirements = this.challenge.getRequirements(); - - switch (button) { - case STATISTIC -> { - description.add(this.user.getTranslation(reference + "value", "[statistic]", - Utils.prettifyObject(requirements.getStatistic(), this.user))); - - icon = new ItemStack(requirements.getStatistic() == null ? Material.BARRIER : Material.PAPER); - clickHandler = (panel, user, clickType, slot) -> { - StatisticSelector.open(this.user, (status, statistic) -> { - if (status) { - requirements.setStatistic(statistic); - requirements.setMaterial(null); - requirements.setEntity(null); - requirements.setAmount(0); - } - - this.build(); - }); - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case STATISTIC_AMOUNT -> { - description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, - String.valueOf(requirements.getAmount()))); - icon = new ItemStack(Material.CHEST); - clickHandler = (panel, user, clickType, i) -> { - Consumer numberConsumer = number -> { - if (number != null) { - requirements.setAmount(number.intValue()); - } - - // reopen panel - this.build(); - }; - ConversationUtils.createNumericInput(numberConsumer, this.user, - this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 0, Integer.MAX_VALUE); - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case REMOVE_STATISTIC -> { - description.add( - this.user.getTranslation(reference + (requirements.isReduceStatistic() ? "enabled" : "disabled"))); - - icon = new ItemStack(Material.LEVER); - clickHandler = (panel, user, clickType, slot) -> { - requirements.setReduceStatistic(!requirements.isReduceStatistic()); - this.build(); - return true; - }; - glow = requirements.isReduceStatistic(); - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); - } - case STATISTIC_BLOCKS -> { - description.add(this.user.getTranslation(reference + "value", "[block]", - Utils.prettifyObject(requirements.getMaterial(), this.user))); - - icon = requirements.getMaterial() == null ? new ItemStack(Material.BARRIER) - : new ItemStack(requirements.getMaterial()); - clickHandler = (panel, user, clickType, slot) -> { - SingleBlockSelector.open(this.user, SingleBlockSelector.Mode.BLOCKS, (status, block) -> { - if (status) { - requirements.setMaterial(block); - } - - this.build(); - }); - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case STATISTIC_ITEMS -> { - description.add(this.user.getTranslation(reference + "value", "[item]", - Utils.prettifyObject(requirements.getMaterial(), this.user))); - - icon = requirements.getMaterial() == null ? new ItemStack(Material.BARRIER) - : new ItemStack(requirements.getMaterial()); - clickHandler = (panel, user, clickType, slot) -> { - SingleBlockSelector.open(this.user, SingleBlockSelector.Mode.ITEMS, (status, block) -> { - if (status) { - requirements.setMaterial(block); - } - - this.build(); - }); - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case STATISTIC_ENTITIES -> { - description.add(this.user.getTranslation(reference + "value", "[entity]", - Utils.prettifyObject(requirements.getEntity(), this.user))); - - icon = requirements.getEntity() == null ? new ItemStack(Material.BARRIER) - : new ItemStack(PanelUtils.getEntityEgg(requirements.getEntity())); - clickHandler = (panel, user, clickType, slot) -> { - SingleEntitySelector.open(this.user, true, (status, entity) -> { - if (status) { - requirements.setEntity(entity); - } - - this.build(); - }); - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - default -> { - icon = new ItemStack(Material.PAPER); - clickHandler = null; - glow = false; - } - } - - return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) - .clickHandler(clickHandler).build(); + final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; + + final String name = this.user.getTranslation(reference + "name"); + final List description = new ArrayList<>(3); + description.add(this.user.getTranslation(reference + "description")); + + ItemStack icon; + boolean glow; + PanelItem.ClickHandler clickHandler; + + final StatisticRequirements requirements = this.challenge.getRequirements(); + + switch (button) { + case STATISTIC -> { + description.add(this.user.getTranslation(reference + "value", "[statistic]", + Utils.prettifyObject(requirements.getStatistic(), this.user))); + + icon = new ItemStack(requirements.getStatistic() == null ? Material.BARRIER : Material.PAPER); + clickHandler = (panel, user, clickType, slot) -> { + StatisticSelector.open(this.user, (status, statistic) -> { + if (status) { + requirements.setStatistic(statistic); + requirements.setMaterial(null); + requirements.setEntity(null); + requirements.setAmount(0); + } + + this.build(); + }); + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case STATISTIC_AMOUNT -> { + description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, + String.valueOf(requirements.getAmount()))); + icon = new ItemStack(Material.CHEST); + clickHandler = (panel, user, clickType, i) -> { + Consumer numberConsumer = number -> { + if (number != null) { + requirements.setAmount(number.intValue()); + } + + // reopen panel + this.build(); + }; + ConversationUtils.createNumericInput(numberConsumer, this.user, + this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 0, Integer.MAX_VALUE); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case REMOVE_STATISTIC -> { + description.add( + this.user.getTranslation(reference + (requirements.isReduceStatistic() ? "enabled" : "disabled"))); + + icon = new ItemStack(Material.LEVER); + clickHandler = (panel, user, clickType, slot) -> { + requirements.setReduceStatistic(!requirements.isReduceStatistic()); + this.build(); + return true; + }; + glow = requirements.isReduceStatistic(); + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); + } + case STATISTIC_BLOCKS -> { + description.add(this.user.getTranslation(reference + "value", "[block]", + Utils.prettifyObject(requirements.getMaterial(), this.user))); + + icon = requirements.getMaterial() == null ? new ItemStack(Material.BARRIER) + : new ItemStack(requirements.getMaterial()); + clickHandler = (panel, user, clickType, slot) -> { + SingleBlockSelector.open(this.user, SingleBlockSelector.Mode.BLOCKS, (status, block) -> { + if (status) { + requirements.setMaterial(block); + } + + this.build(); + }); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case STATISTIC_ITEMS -> { + description.add(this.user.getTranslation(reference + "value", "[item]", + Utils.prettifyObject(requirements.getMaterial(), this.user))); + + icon = requirements.getMaterial() == null ? new ItemStack(Material.BARRIER) + : new ItemStack(requirements.getMaterial()); + clickHandler = (panel, user, clickType, slot) -> { + SingleBlockSelector.open(this.user, SingleBlockSelector.Mode.ITEMS, (status, block) -> { + if (status) { + requirements.setMaterial(block); + } + + this.build(); + }); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case STATISTIC_ENTITIES -> { + description.add(this.user.getTranslation(reference + "value", "[entity]", + Utils.prettifyObject(requirements.getEntity(), this.user))); + + icon = requirements.getEntity() == null ? new ItemStack(Material.BARRIER) + : new ItemStack(PanelUtils.getEntityEgg(requirements.getEntity())); + clickHandler = (panel, user, clickType, slot) -> { + SingleEntitySelector.open(this.user, true, (status, entity) -> { + if (status) { + requirements.setEntity(entity); + } + + this.build(); + }); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + default -> { + icon = new ItemStack(Material.PAPER); + clickHandler = null; + glow = false; + } + } + + return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) + .clickHandler(clickHandler).build(); } /** @@ -1207,462 +1233,462 @@ private PanelItem createStatisticRequirementButton(RequirementButton button) { * @return PanelItem that represents given button. */ private PanelItem createRewardButton(RewardButton button) { - final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; - - final String name = this.user.getTranslation(reference + "name"); - final List description = new ArrayList<>(3); - description.add(this.user.getTranslation(reference + "description")); - - ItemStack icon; - boolean glow; - PanelItem.ClickHandler clickHandler; - - switch (button) { - case REWARD_TEXT -> { - icon = new ItemStack(Material.WRITTEN_BOOK); - - description.add(this.user.getTranslation(reference + "value")); - description.add(Util.translateColorCodes(this.challenge.getRewardText())); - - clickHandler = (panel, user, clickType, i) -> { - // Create consumer that process description change - Consumer> consumer = value -> { - if (value != null) { - this.challenge.setRewardText(String.join("\n", value)); - } - - this.build(); - }; - - if (!this.challenge.getRewardText().isEmpty() && clickType.isShiftClick()) { - // Reset to the empty value - consumer.accept(Collections.emptyList()); - } else { - // start conversation - ConversationUtils.createStringListInput(consumer, user, - user.getTranslation(Constants.CONVERSATIONS + "write-reward-text"), - user.getTranslation(Constants.CONVERSATIONS + "reward-text-changed")); - } - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - - if (!this.challenge.getRewardText().isEmpty()) { - description.add(this.user.getTranslation(Constants.TIPS + "shift-click-to-reset")); - } - } - case REWARD_ITEMS -> { - - if (this.challenge.getRewardItems().isEmpty()) { - description.add(this.user.getTranslation(reference + "none")); - } else { - description.add(this.user.getTranslation(reference + "title")); - - Utils.groupEqualItems(this.challenge.getRewardItems(), this.challenge.getIgnoreRewardMetaData()) - .stream().sorted(Comparator.comparing(ItemStack::getType)) - .forEach(itemStack -> description.add(this.user.getTranslationOrNothing(reference + "list", - "[number]", String.valueOf(itemStack.getAmount()), "[item]", - Utils.prettifyObject(itemStack, this.user)))); - } - - icon = new ItemStack(Material.CHEST); - clickHandler = (panel, user, clickType, slot) -> { - ItemSelector.open(this.user, this.challenge.getRewardItems(), (status, value) -> { - if (status) { - this.challenge.setRewardItems(value); - } - - this.build(); - }); - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case REWARD_EXPERIENCE -> { - description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, - String.valueOf(this.challenge.getRewardExperience()))); - icon = new ItemStack(Material.EXPERIENCE_BOTTLE); - clickHandler = (panel, user, clickType, i) -> { - Consumer numberConsumer = number -> { - if (number != null) { - this.challenge.setRewardExperience(number.intValue()); - } - - // reopen panel - this.build(); - }; - ConversationUtils.createNumericInput(numberConsumer, this.user, - this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 0, Integer.MAX_VALUE); - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case REWARD_MONEY -> { - description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, - addon.getPlugin().getVault().map(v -> v.format(challenge.getRewardMoney())) - .orElse(String.valueOf(challenge.getRewardMoney())))); - icon = new ItemStack(this.addon.isEconomyProvided() ? Material.GOLD_INGOT : Material.BARRIER); - clickHandler = (panel, user, clickType, i) -> { - Consumer numberConsumer = number -> { - if (number != null) { - this.challenge.setRewardMoney(number.doubleValue()); - } - - // reopen panel - this.build(); - }; - ConversationUtils.createNumericInput(numberConsumer, this.user, - this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 0, Double.MAX_VALUE); - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case REWARD_COMMANDS -> { - icon = new ItemStack(Material.COMMAND_BLOCK); - - description.add(this.user.getTranslation(reference + "value")); - description.addAll(this.challenge.getRewardCommands()); - - clickHandler = (panel, user, clickType, i) -> { - // Create consumer that process description change - Consumer> consumer = value -> { - if (value != null) { - this.challenge.setRewardCommands(value); - } - - this.build(); - }; - - if (!this.challenge.getRewardCommands().isEmpty() && clickType.isShiftClick()) { - // Reset to the empty value - consumer.accept(Collections.emptyList()); - } else { - // start conversation - ConversationUtils.createStringListInput(consumer, user, - user.getTranslation(Constants.CONVERSATIONS + "write-reward-commands"), - user.getTranslation(Constants.CONVERSATIONS + "reward-commands-changed")); - } - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - - if (!this.challenge.getRewardCommands().isEmpty()) { - description.add(this.user.getTranslation(Constants.TIPS + "shift-click-to-reset")); - } - } - case REPEATABLE -> { - description.add( - this.user.getTranslation(reference + (this.challenge.isRepeatable() ? "enabled" : "disabled"))); - - icon = new ItemStack(Material.LEVER); - clickHandler = (panel, user, clickType, slot) -> { - this.challenge.setRepeatable(!this.challenge.isRepeatable()); - this.build(); - return true; - }; - glow = this.challenge.isRepeatable(); - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); - } - case REPEAT_COUNT -> { - description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, - String.valueOf(this.challenge.getMaxTimes()))); - icon = new ItemStack(Material.COBBLESTONE_WALL); - clickHandler = (panel, user, clickType, i) -> { - Consumer numberConsumer = number -> { - if (number != null) { - this.challenge.setMaxTimes(number.intValue()); - } - - // reopen panel - this.build(); - }; - ConversationUtils.createNumericInput(numberConsumer, this.user, - this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 0, Integer.MAX_VALUE); - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case COOL_DOWN -> { - description.add(this.user.getTranslation(reference + "value", "[time]", - Utils.parseDuration(Duration.ofMillis(this.challenge.getTimeout()), this.user))); - icon = new ItemStack(Material.CLOCK); - clickHandler = (panel, user, clickType, i) -> { - Consumer numberConsumer = number -> { - if (number != null) { - this.challenge.setTimeout(number.longValue() * 1000); - } - - // reopen panel - this.build(); - }; - ConversationUtils.createNumericInput(numberConsumer, this.user, - this.user.getTranslation(Constants.CONVERSATIONS + "input-seconds"), 0, Integer.MAX_VALUE); - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case REPEAT_REWARD_TEXT -> { - icon = new ItemStack(Material.WRITTEN_BOOK); - - description.add(this.user.getTranslation(reference + "value")); - description.add(Util.translateColorCodes(this.challenge.getRepeatRewardText())); - - clickHandler = (panel, user, clickType, i) -> { - // Create consumer that process description change - Consumer> consumer = value -> { - if (value != null) { - this.challenge.setRepeatRewardText(String.join("\n", value)); - } - - this.build(); - }; - - if (!this.challenge.getRepeatRewardText().isEmpty() && clickType.isShiftClick()) { - // Reset to the empty value - consumer.accept(Collections.emptyList()); - } else { - // start conversation - ConversationUtils.createStringListInput(consumer, user, - user.getTranslation(Constants.CONVERSATIONS + "write-repeat-reward-text"), - user.getTranslation(Constants.CONVERSATIONS + "repeat-reward-text-changed")); - } - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - - if (!this.challenge.getRepeatRewardText().isEmpty()) { - description.add(this.user.getTranslation(Constants.TIPS + "shift-click-to-reset")); - } - } - case REPEAT_REWARD_ITEMS -> { - - if (this.challenge.getRepeatItemReward().isEmpty()) { - description.add(this.user.getTranslation(reference + "none")); - } else { - description.add(this.user.getTranslation(reference + "title")); - - Utils.groupEqualItems(this.challenge.getRepeatItemReward(), this.challenge.getIgnoreRewardMetaData()) - .stream().sorted(Comparator.comparing(ItemStack::getType)) - .forEach(itemStack -> description.add(this.user.getTranslationOrNothing(reference + "list", - "[number]", String.valueOf(itemStack.getAmount()), "[item]", - Utils.prettifyObject(itemStack, this.user)))); - } - - icon = new ItemStack(Material.CHEST); - clickHandler = (panel, user, clickType, slot) -> { - ItemSelector.open(this.user, this.challenge.getRewardItems(), (status, value) -> { - if (status) { - this.challenge.setRepeatItemReward(value); - } - - this.build(); - }); - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case REPEAT_REWARD_EXPERIENCE -> { - description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, - String.valueOf(this.challenge.getRepeatExperienceReward()))); - icon = new ItemStack(Material.EXPERIENCE_BOTTLE); - clickHandler = (panel, user, clickType, i) -> { - Consumer numberConsumer = number -> { - if (number != null) { - this.challenge.setRepeatExperienceReward(number.intValue()); - } - - // reopen panel - this.build(); - }; - ConversationUtils.createNumericInput(numberConsumer, this.user, - this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 0, Integer.MAX_VALUE); - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case REPEAT_REWARD_MONEY -> { - description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, - String.valueOf(this.challenge.getRepeatMoneyReward()))); - icon = new ItemStack(this.addon.isEconomyProvided() ? Material.GOLD_NUGGET : Material.BARRIER); - clickHandler = (panel, user, clickType, i) -> { - Consumer numberConsumer = number -> { - if (number != null) { - this.challenge.setRepeatMoneyReward(number.doubleValue()); - } - - // reopen panel - this.build(); - }; - ConversationUtils.createNumericInput(numberConsumer, this.user, - this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 0, Double.MAX_VALUE); - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case REPEAT_REWARD_COMMANDS -> { - icon = new ItemStack(Material.COMMAND_BLOCK); - - description.add(this.user.getTranslation(reference + "value")); - description.addAll(this.challenge.getRepeatRewardCommands()); - - clickHandler = (panel, user, clickType, i) -> { - // Create consumer that process description change - Consumer> consumer = value -> { - if (value != null) { - this.challenge.setRepeatRewardCommands(value); - } - - this.build(); - }; - - if (!this.challenge.getRepeatRewardCommands().isEmpty() && clickType.isShiftClick()) { - // Reset to the empty value - consumer.accept(Collections.emptyList()); - } else { - // start conversation - ConversationUtils.createStringListInput(consumer, user, - user.getTranslation(Constants.CONVERSATIONS + "write-repeat-reward-commands"), - user.getTranslation(Constants.CONVERSATIONS + "repeat-reward-commands-changed")); - } - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - - if (!this.challenge.getRepeatRewardCommands().isEmpty()) { - description.add(this.user.getTranslation(Constants.TIPS + "shift-click-to-reset")); - } - } - case ADD_IGNORED_META -> { - if (this.challenge.getIgnoreRewardMetaData().isEmpty()) { - description.add(this.user.getTranslation(reference + "none")); - } else { - description.add(this.user.getTranslation(reference + "title")); - - this.challenge.getIgnoreRewardMetaData().stream().sorted(Comparator.comparing(Material::name)) - .forEach(itemStack -> description.add(this.user.getTranslationOrNothing(reference + "list", - "[item]", Utils.prettifyObject(itemStack, this.user)))); - } - - icon = new ItemStack(Material.GREEN_SHULKER_BOX); - - clickHandler = (panel, user, clickType, slot) -> { - if (this.challenge.getRewardItems().isEmpty() && this.challenge.getRepeatItemReward().isEmpty()) { - // Do nothing if no requirements are set. - return true; - } - - // Allow choosing only from inventory items. - Set collection = Arrays.stream(Material.values()).collect(Collectors.toSet()); - this.challenge.getRewardItems().stream().map(ItemStack::getType).forEach(collection::remove); - this.challenge.getRepeatItemReward().stream().map(ItemStack::getType).forEach(collection::remove); - collection.addAll(this.challenge.getIgnoreRewardMetaData()); - - if (Material.values().length == collection.size()) { - // If there are no items anymore, then do not allow opening gui. - return true; - } - - MultiBlockSelector.open(this.user, MultiBlockSelector.Mode.ANY, collection, (status, materials) -> { - if (status) { - materials.addAll(this.challenge.getIgnoreRewardMetaData()); - this.challenge.setIgnoreRewardMetaData(new HashSet<>(materials)); - } - - this.build(); - }); - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-add")); - } - case REMOVE_IGNORED_META -> { - icon = new ItemStack(Material.RED_SHULKER_BOX); - - clickHandler = (panel, user, clickType, slot) -> { - if (this.challenge.getIgnoreRewardMetaData().isEmpty()) { - // Do nothing if no requirements are set. - return true; - } - - // Allow choosing only from inventory items. - Set collection = Arrays.stream(Material.values()).collect(Collectors.toSet()); - collection.removeAll(this.challenge.getIgnoreRewardMetaData()); - - MultiBlockSelector.open(this.user, MultiBlockSelector.Mode.ANY, collection, (status, materials) -> { - if (status) { - this.challenge.getIgnoreRewardMetaData().removeAll(materials); - } - - this.build(); - }); - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-remove")); - } - default -> { - icon = new ItemStack(Material.PAPER); - clickHandler = null; - glow = false; - } - } - - return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) - .clickHandler(clickHandler).build(); + final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; + + final String name = this.user.getTranslation(reference + "name"); + final List description = new ArrayList<>(3); + description.add(this.user.getTranslation(reference + "description")); + + ItemStack icon; + boolean glow; + PanelItem.ClickHandler clickHandler; + + switch (button) { + case REWARD_TEXT -> { + icon = new ItemStack(Material.WRITTEN_BOOK); + + description.add(this.user.getTranslation(reference + "value")); + description.add(Util.translateColorCodes(this.challenge.getRewardText())); + + clickHandler = (panel, user, clickType, i) -> { + // Create consumer that process description change + Consumer> consumer = value -> { + if (value != null) { + this.challenge.setRewardText(String.join("\n", value)); + } + + this.build(); + }; + + if (!this.challenge.getRewardText().isEmpty() && clickType.isShiftClick()) { + // Reset to the empty value + consumer.accept(Collections.emptyList()); + } else { + // start conversation + ConversationUtils.createStringListInput(consumer, user, + user.getTranslation(Constants.CONVERSATIONS + "write-reward-text"), + user.getTranslation(Constants.CONVERSATIONS + "reward-text-changed")); + } + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + + if (!this.challenge.getRewardText().isEmpty()) { + description.add(this.user.getTranslation(Constants.TIPS + "shift-click-to-reset")); + } + } + case REWARD_ITEMS -> { + + if (this.challenge.getRewardItems().isEmpty()) { + description.add(this.user.getTranslation(reference + "none")); + } else { + description.add(this.user.getTranslation(reference + "title")); + + Utils.groupEqualItems(this.challenge.getRewardItems(), this.challenge.getIgnoreRewardMetaData()) + .stream().sorted(Comparator.comparing(ItemStack::getType)) + .forEach(itemStack -> description.add(this.user.getTranslationOrNothing(reference + "list", + "[number]", String.valueOf(itemStack.getAmount()), "[item]", + Utils.prettifyObject(itemStack, this.user)))); + } + + icon = new ItemStack(Material.CHEST); + clickHandler = (panel, user, clickType, slot) -> { + ItemSelector.open(this.user, this.challenge.getRewardItems(), (status, value) -> { + if (status) { + this.challenge.setRewardItems(value); + } + + this.build(); + }); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case REWARD_EXPERIENCE -> { + description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, + String.valueOf(this.challenge.getRewardExperience()))); + icon = new ItemStack(Material.EXPERIENCE_BOTTLE); + clickHandler = (panel, user, clickType, i) -> { + Consumer numberConsumer = number -> { + if (number != null) { + this.challenge.setRewardExperience(number.intValue()); + } + + // reopen panel + this.build(); + }; + ConversationUtils.createNumericInput(numberConsumer, this.user, + this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 0, Integer.MAX_VALUE); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case REWARD_MONEY -> { + description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, + addon.getPlugin().getVault().map(v -> v.format(challenge.getRewardMoney())) + .orElse(String.valueOf(challenge.getRewardMoney())))); + icon = new ItemStack(this.addon.isEconomyProvided() ? Material.GOLD_INGOT : Material.BARRIER); + clickHandler = (panel, user, clickType, i) -> { + Consumer numberConsumer = number -> { + if (number != null) { + this.challenge.setRewardMoney(number.doubleValue()); + } + + // reopen panel + this.build(); + }; + ConversationUtils.createNumericInput(numberConsumer, this.user, + this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 0, Double.MAX_VALUE); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case REWARD_COMMANDS -> { + icon = new ItemStack(Material.COMMAND_BLOCK); + + description.add(this.user.getTranslation(reference + "value")); + description.addAll(this.challenge.getRewardCommands()); + + clickHandler = (panel, user, clickType, i) -> { + // Create consumer that process description change + Consumer> consumer = value -> { + if (value != null) { + this.challenge.setRewardCommands(value); + } + + this.build(); + }; + + if (!this.challenge.getRewardCommands().isEmpty() && clickType.isShiftClick()) { + // Reset to the empty value + consumer.accept(Collections.emptyList()); + } else { + // start conversation + ConversationUtils.createStringListInput(consumer, user, + user.getTranslation(Constants.CONVERSATIONS + "write-reward-commands"), + user.getTranslation(Constants.CONVERSATIONS + "reward-commands-changed")); + } + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + + if (!this.challenge.getRewardCommands().isEmpty()) { + description.add(this.user.getTranslation(Constants.TIPS + "shift-click-to-reset")); + } + } + case REPEATABLE -> { + description.add( + this.user.getTranslation(reference + (this.challenge.isRepeatable() ? "enabled" : "disabled"))); + + icon = new ItemStack(Material.LEVER); + clickHandler = (panel, user, clickType, slot) -> { + this.challenge.setRepeatable(!this.challenge.isRepeatable()); + this.build(); + return true; + }; + glow = this.challenge.isRepeatable(); + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); + } + case REPEAT_COUNT -> { + description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, + String.valueOf(this.challenge.getMaxTimes()))); + icon = new ItemStack(Material.COBBLESTONE_WALL); + clickHandler = (panel, user, clickType, i) -> { + Consumer numberConsumer = number -> { + if (number != null) { + this.challenge.setMaxTimes(number.intValue()); + } + + // reopen panel + this.build(); + }; + ConversationUtils.createNumericInput(numberConsumer, this.user, + this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 0, Integer.MAX_VALUE); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case COOL_DOWN -> { + description.add(this.user.getTranslation(reference + "value", "[time]", + Utils.parseDuration(Duration.ofMillis(this.challenge.getTimeout()), this.user))); + icon = new ItemStack(Material.CLOCK); + clickHandler = (panel, user, clickType, i) -> { + Consumer numberConsumer = number -> { + if (number != null) { + this.challenge.setTimeout(number.longValue() * 1000); + } + + // reopen panel + this.build(); + }; + ConversationUtils.createNumericInput(numberConsumer, this.user, + this.user.getTranslation(Constants.CONVERSATIONS + "input-seconds"), 0, Integer.MAX_VALUE); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case REPEAT_REWARD_TEXT -> { + icon = new ItemStack(Material.WRITTEN_BOOK); + + description.add(this.user.getTranslation(reference + "value")); + description.add(Util.translateColorCodes(this.challenge.getRepeatRewardText())); + + clickHandler = (panel, user, clickType, i) -> { + // Create consumer that process description change + Consumer> consumer = value -> { + if (value != null) { + this.challenge.setRepeatRewardText(String.join("\n", value)); + } + + this.build(); + }; + + if (!this.challenge.getRepeatRewardText().isEmpty() && clickType.isShiftClick()) { + // Reset to the empty value + consumer.accept(Collections.emptyList()); + } else { + // start conversation + ConversationUtils.createStringListInput(consumer, user, + user.getTranslation(Constants.CONVERSATIONS + "write-repeat-reward-text"), + user.getTranslation(Constants.CONVERSATIONS + "repeat-reward-text-changed")); + } + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + + if (!this.challenge.getRepeatRewardText().isEmpty()) { + description.add(this.user.getTranslation(Constants.TIPS + "shift-click-to-reset")); + } + } + case REPEAT_REWARD_ITEMS -> { + + if (this.challenge.getRepeatItemReward().isEmpty()) { + description.add(this.user.getTranslation(reference + "none")); + } else { + description.add(this.user.getTranslation(reference + "title")); + + Utils.groupEqualItems(this.challenge.getRepeatItemReward(), this.challenge.getIgnoreRewardMetaData()) + .stream().sorted(Comparator.comparing(ItemStack::getType)) + .forEach(itemStack -> description.add(this.user.getTranslationOrNothing(reference + "list", + "[number]", String.valueOf(itemStack.getAmount()), "[item]", + Utils.prettifyObject(itemStack, this.user)))); + } + + icon = new ItemStack(Material.CHEST); + clickHandler = (panel, user, clickType, slot) -> { + ItemSelector.open(this.user, this.challenge.getRewardItems(), (status, value) -> { + if (status) { + this.challenge.setRepeatItemReward(value); + } + + this.build(); + }); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case REPEAT_REWARD_EXPERIENCE -> { + description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, + String.valueOf(this.challenge.getRepeatExperienceReward()))); + icon = new ItemStack(Material.EXPERIENCE_BOTTLE); + clickHandler = (panel, user, clickType, i) -> { + Consumer numberConsumer = number -> { + if (number != null) { + this.challenge.setRepeatExperienceReward(number.intValue()); + } + + // reopen panel + this.build(); + }; + ConversationUtils.createNumericInput(numberConsumer, this.user, + this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 0, Integer.MAX_VALUE); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case REPEAT_REWARD_MONEY -> { + description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, + String.valueOf(this.challenge.getRepeatMoneyReward()))); + icon = new ItemStack(this.addon.isEconomyProvided() ? Material.GOLD_NUGGET : Material.BARRIER); + clickHandler = (panel, user, clickType, i) -> { + Consumer numberConsumer = number -> { + if (number != null) { + this.challenge.setRepeatMoneyReward(number.doubleValue()); + } + + // reopen panel + this.build(); + }; + ConversationUtils.createNumericInput(numberConsumer, this.user, + this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 0, Double.MAX_VALUE); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case REPEAT_REWARD_COMMANDS -> { + icon = new ItemStack(Material.COMMAND_BLOCK); + + description.add(this.user.getTranslation(reference + "value")); + description.addAll(this.challenge.getRepeatRewardCommands()); + + clickHandler = (panel, user, clickType, i) -> { + // Create consumer that process description change + Consumer> consumer = value -> { + if (value != null) { + this.challenge.setRepeatRewardCommands(value); + } + + this.build(); + }; + + if (!this.challenge.getRepeatRewardCommands().isEmpty() && clickType.isShiftClick()) { + // Reset to the empty value + consumer.accept(Collections.emptyList()); + } else { + // start conversation + ConversationUtils.createStringListInput(consumer, user, + user.getTranslation(Constants.CONVERSATIONS + "write-repeat-reward-commands"), + user.getTranslation(Constants.CONVERSATIONS + "repeat-reward-commands-changed")); + } + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + + if (!this.challenge.getRepeatRewardCommands().isEmpty()) { + description.add(this.user.getTranslation(Constants.TIPS + "shift-click-to-reset")); + } + } + case ADD_IGNORED_META -> { + if (this.challenge.getIgnoreRewardMetaData().isEmpty()) { + description.add(this.user.getTranslation(reference + "none")); + } else { + description.add(this.user.getTranslation(reference + "title")); + + this.challenge.getIgnoreRewardMetaData().stream().sorted(Comparator.comparing(Material::name)) + .forEach(itemStack -> description.add(this.user.getTranslationOrNothing(reference + "list", + "[item]", Utils.prettifyObject(itemStack, this.user)))); + } + + icon = new ItemStack(Material.GREEN_SHULKER_BOX); + + clickHandler = (panel, user, clickType, slot) -> { + if (this.challenge.getRewardItems().isEmpty() && this.challenge.getRepeatItemReward().isEmpty()) { + // Do nothing if no requirements are set. + return true; + } + + // Allow choosing only from inventory items. + Set collection = Arrays.stream(Material.values()).collect(Collectors.toSet()); + this.challenge.getRewardItems().stream().map(ItemStack::getType).forEach(collection::remove); + this.challenge.getRepeatItemReward().stream().map(ItemStack::getType).forEach(collection::remove); + collection.addAll(this.challenge.getIgnoreRewardMetaData()); + + if (Material.values().length == collection.size()) { + // If there are no items anymore, then do not allow opening gui. + return true; + } + + MultiBlockSelector.open(this.user, MultiBlockSelector.Mode.ANY, collection, (status, materials) -> { + if (status) { + materials.addAll(this.challenge.getIgnoreRewardMetaData()); + this.challenge.setIgnoreRewardMetaData(new HashSet<>(materials)); + } + + this.build(); + }); + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-add")); + } + case REMOVE_IGNORED_META -> { + icon = new ItemStack(Material.RED_SHULKER_BOX); + + clickHandler = (panel, user, clickType, slot) -> { + if (this.challenge.getIgnoreRewardMetaData().isEmpty()) { + // Do nothing if no requirements are set. + return true; + } + + // Allow choosing only from inventory items. + Set collection = Arrays.stream(Material.values()).collect(Collectors.toSet()); + collection.removeAll(this.challenge.getIgnoreRewardMetaData()); + + MultiBlockSelector.open(this.user, MultiBlockSelector.Mode.ANY, collection, (status, materials) -> { + if (status) { + this.challenge.getIgnoreRewardMetaData().removeAll(materials); + } + + this.build(); + }); + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-remove")); + } + default -> { + icon = new ItemStack(Material.PAPER); + clickHandler = null; + glow = false; + } + } + + return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) + .clickHandler(clickHandler).build(); } // --------------------------------------------------------------------- @@ -1673,47 +1699,47 @@ private PanelItem createRewardButton(RewardButton button) { * This class allows changing icon for Generator Tier */ private class IconChanger implements PanelListener { - /** - * Process inventory click. If generator icon is selected and user clicks on - * item in his inventory, then change icon to the item from inventory. - * - * @param user the user - * @param event the event - */ - @Override - public void onInventoryClick(User user, InventoryClickEvent event) { - // Handle icon changing - if (EditChallengePanel.this.selectedButton != null && event.getCurrentItem() != null - && !event.getCurrentItem().getType().equals(Material.AIR) && event.getRawSlot() > 44) { - // set material and amount only. Other data should be removed. - - if (EditChallengePanel.this.selectedButton == Button.ICON) { - EditChallengePanel.this.challenge.setIcon(event.getCurrentItem().clone()); - // Deselect icon - EditChallengePanel.this.selectedButton = null; - // Rebuild icon - EditChallengePanel.this.build(); - } - } - } - - /** - * On inventory close. - * - * @param event the event - */ - @Override - public void onInventoryClose(InventoryCloseEvent event) { - // Do nothing - } - - /** - * Setup current listener. - */ - @Override - public void setup() { - // Do nothing - } + /** + * Process inventory click. If generator icon is selected and user clicks on + * item in his inventory, then change icon to the item from inventory. + * + * @param user the user + * @param event the event + */ + @Override + public void onInventoryClick(User user, InventoryClickEvent event) { + // Handle icon changing + if (EditChallengePanel.this.selectedButton != null && event.getCurrentItem() != null + && !event.getCurrentItem().getType().equals(Material.AIR) && event.getRawSlot() > 44) { + // set material and amount only. Other data should be removed. + + if (EditChallengePanel.this.selectedButton == Button.ICON) { + EditChallengePanel.this.challenge.setIcon(event.getCurrentItem().clone()); + // Deselect icon + EditChallengePanel.this.selectedButton = null; + // Rebuild icon + EditChallengePanel.this.build(); + } + } + } + + /** + * On inventory close. + * + * @param event the event + */ + @Override + public void onInventoryClose(InventoryCloseEvent event) { + // Do nothing + } + + /** + * Setup current listener. + */ + @Override + public void setup() { + // Do nothing + } } // --------------------------------------------------------------------- @@ -1724,37 +1750,37 @@ public void setup() { * Represents different types of menus */ private enum MenuType { - PROPERTIES, REQUIREMENTS, REWARDS + PROPERTIES, REQUIREMENTS, REWARDS } /** * Represents different buttons that could be in menus. */ private enum Button { - NAME, DEPLOYED, ICON, DESCRIPTION, ORDER, ENVIRONMENT, REMOVE_ON_COMPLETE, + NAME, DEPLOYED, ICON, DESCRIPTION, ORDER, ENVIRONMENT, REMOVE_ON_COMPLETE, } /** * Represents different rewards buttons that are used in menus. */ private enum RewardButton { - REWARD_TEXT, REWARD_ITEMS, REWARD_EXPERIENCE, REWARD_MONEY, REWARD_COMMANDS, + REWARD_TEXT, REWARD_ITEMS, REWARD_EXPERIENCE, REWARD_MONEY, REWARD_COMMANDS, - REPEATABLE, REPEAT_COUNT, COOL_DOWN, + REPEATABLE, REPEAT_COUNT, COOL_DOWN, - REPEAT_REWARD_TEXT, REPEAT_REWARD_ITEMS, REPEAT_REWARD_EXPERIENCE, REPEAT_REWARD_MONEY, REPEAT_REWARD_COMMANDS, + REPEAT_REWARD_TEXT, REPEAT_REWARD_ITEMS, REPEAT_REWARD_EXPERIENCE, REPEAT_REWARD_MONEY, REPEAT_REWARD_COMMANDS, - ADD_IGNORED_META, REMOVE_IGNORED_META, + ADD_IGNORED_META, REMOVE_IGNORED_META, } /** * Represents different requirement buttons that are used in menus. */ private enum RequirementButton { - REQUIRED_ENTITIES, REMOVE_ENTITIES, REQUIRED_BLOCKS, REMOVE_BLOCKS, SEARCH_RADIUS, REQUIRED_PERMISSIONS, - REQUIRED_ITEMS, REMOVE_ITEMS, ADD_IGNORED_META, REMOVE_IGNORED_META, REQUIRED_EXPERIENCE, REMOVE_EXPERIENCE, - REQUIRED_LEVEL, REQUIRED_MONEY, REMOVE_MONEY, STATISTIC, STATISTIC_BLOCKS, STATISTIC_ITEMS, STATISTIC_ENTITIES, - STATISTIC_AMOUNT, REMOVE_STATISTIC, + REQUIRED_ENTITIES, REMOVE_ENTITIES, REQUIRED_BLOCKS, REMOVE_BLOCKS, SEARCH_RADIUS, REQUIRED_PERMISSIONS, + REQUIRED_ITEMS, REMOVE_ITEMS, ADD_IGNORED_META, REMOVE_IGNORED_META, REQUIRED_EXPERIENCE, REMOVE_EXPERIENCE, + REQUIRED_LEVEL, REQUIRED_MONEY, REMOVE_MONEY, STATISTIC, STATISTIC_BLOCKS, STATISTIC_ITEMS, STATISTIC_ENTITIES, + STATISTIC_AMOUNT, REMOVE_STATISTIC, REQUIRED_MATERIALTAGS, REQUIRED_ENTITYTAGS, } // --------------------------------------------------------------------- @@ -1772,4 +1798,5 @@ private enum RequirementButton { * Variable holds current active menu. */ private MenuType currentMenuType; + } diff --git a/src/main/java/world/bentobox/challenges/panel/admin/ManageBlocksPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/ManageBlocksPanel.java index cd43adf7..109c4516 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/ManageBlocksPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/ManageBlocksPanel.java @@ -282,7 +282,7 @@ protected PanelItem createElementButton(Material material) private enum Button { ADD_BLOCK, - REMOVE_BLOCK + REMOVE_BLOCK } diff --git a/src/main/java/world/bentobox/challenges/panel/admin/ManageTagsPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/ManageTagsPanel.java new file mode 100644 index 00000000..38fd2bd2 --- /dev/null +++ b/src/main/java/world/bentobox/challenges/panel/admin/ManageTagsPanel.java @@ -0,0 +1,320 @@ +package world.bentobox.challenges.panel.admin; + + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import org.bukkit.Material; +import org.bukkit.Registry; +import org.bukkit.Tag; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.Nullable; + +import lv.id.bonne.panelutils.PanelUtils; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.builders.PanelBuilder; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.challenges.panel.CommonPagedPanel; +import world.bentobox.challenges.panel.CommonPanel; +import world.bentobox.challenges.panel.ConversationUtils; +import world.bentobox.challenges.panel.util.MultiMaterialTagsSelector; +import world.bentobox.challenges.utils.Constants; +import world.bentobox.challenges.utils.Utils; + + +/** + * This class allows to edit material that are in required material map. + */ +public class ManageTagsPanel extends CommonPagedPanel> +{ + + // --------------------------------------------------------------------- + // Section: Enums + // --------------------------------------------------------------------- + + /** + * Functional buttons in current GUI. + */ + private enum Button { + ADD_BLOCK, REMOVE_BLOCK + } + + // --------------------------------------------------------------------- + // Section: Variables + // --------------------------------------------------------------------- + + /** + * Contains selected materials. + */ + private final Set> selectedTags; + + /** + * List of materials to avoid order issues. + */ + private final List> materialList; + + /** + * List of required materials. + */ + private final Map, Integer> tagMap; + + /** + * Stores filtered items. + */ + private List> filterElements; + + private ManageTagsPanel(CommonPanel parentGUI, Map, Integer> map) + { + super(parentGUI); + this.tagMap = map; + this.materialList = new ArrayList<>(this.tagMap.keySet()); + + // Sort tags by their ordinal value. + this.materialList.sort(Comparator.comparing(tag -> tag.getKey().getKey())); + + this.selectedTags = new HashSet<>(); + + // Init without filters applied. + this.filterElements = this.materialList; + } + + + /** + * Open the Challenges Admin GUI. + */ + public static void open(CommonPanel parentGUI, Map, Integer> map) + { + new ManageTagsPanel(parentGUI, map).build(); + } + + +// --------------------------------------------------------------------- +// Section: Methods +// --------------------------------------------------------------------- + + + /** + * This method is called when filter value is updated. + */ + @Override + protected void updateFilters() + { + if (this.searchString == null || this.searchString.isBlank()) + { + this.filterElements = this.materialList; + } + else + { + this.filterElements = this.materialList.stream(). + filter(element -> { + // If element name is set and name contains search field, then do not filter out. + return element.getKey().getKey().toLowerCase(Locale.ENGLISH) + .contains(this.searchString.toLowerCase(Locale.ENGLISH)); + }). + distinct(). + collect(Collectors.toList()); + } + } + + + /** + * This method builds all necessary elements in GUI panel. + */ + @Override + protected void build() + { + PanelBuilder panelBuilder = new PanelBuilder().user(this.user). + name(this.user.getTranslation(Constants.TITLE + "manage-material-tags")); + + // Create nice border. + PanelUtils.fillBorder(panelBuilder); + + panelBuilder.item(3, this.createButton(Button.ADD_BLOCK)); + panelBuilder.item(5, this.createButton(Button.REMOVE_BLOCK)); + // Fill the box with what is selected + this.populateElements(panelBuilder, this.filterElements); + + // Add return button. + panelBuilder.item(44, this.returnButton); + + panelBuilder.build(); + } + + + /** + * This method creates PanelItem button of requested type. + * @param button Button which must be created. + * @return new PanelItem with requested functionality. + */ + private PanelItem createButton(Button button) + { + final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; + + final String name = this.user.getTranslation(reference + "name"); + final List description = new ArrayList<>(3); + description.add(this.user.getTranslation(reference + "description")); + + ItemStack icon; + PanelItem.ClickHandler clickHandler; + boolean glow; + + switch (button) + { + case ADD_BLOCK -> { + icon = new ItemStack(Material.BUCKET); + clickHandler = (panel, user1, clickType, slot) -> + { + MultiMaterialTagsSelector.open(this.user, MultiMaterialTagsSelector.Mode.BLOCKS, + new HashSet<>(this.materialList), + (status, materials) -> + { + if (status) + { + materials.forEach(material -> + { + this.tagMap.put(material, 1); + this.materialList.add(material); + }); + } + + this.build(); + }); + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-add")); + } + case REMOVE_BLOCK -> { + + if (!this.selectedTags.isEmpty()) + { + description.add(this.user.getTranslation(reference + "title")); + this.selectedTags.forEach(material -> + description.add(this.user.getTranslation(reference + "material", + "[material]", Utils.prettifyObject(material, this.user)))); + } + + icon = new ItemStack(Material.LAVA_BUCKET); + + clickHandler = (panel, user1, clickType, slot) -> + { + if (!this.selectedTags.isEmpty()) + { + this.tagMap.keySet().removeAll(this.selectedTags); + this.materialList.removeAll(this.selectedTags); + this.selectedTags.clear(); + this.build(); + } + + return true; + }; + + glow = !this.selectedTags.isEmpty(); + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-remove")); + } + default -> { + icon = new ItemStack(Material.PAPER); + clickHandler = null; + glow = false; + } + } + + return new PanelItemBuilder(). + icon(icon). + name(name). + description(description). + clickHandler(clickHandler). + glow(glow). + build(); + } + + + /** + * This method creates button for given material. + * @param tag material which button must be created. + * @return new Button for material. + */ + @Override + protected PanelItem createElementButton(Tag tag) + { + final String reference = Constants.BUTTON + "materialtag."; + + List description = new ArrayList<>(); + + if (this.selectedTags.contains(tag)) + { + description.add(this.user.getTranslation(reference + "selected")); + } + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "left-click-to-choose")); + + if (this.selectedTags.contains(tag)) + { + description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-deselect")); + } + else + { + description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-select")); + } + + return new PanelItemBuilder(). + name(this.user.getTranslation(reference + "name", "[tag]", + Utils.prettifyObject(tag, this.user))). + icon(getIcon(tag, this.tagMap.get(tag))). + description(description). + clickHandler((panel, user1, clickType, slot) -> { + // On right click change which entities are selected for deletion. + if (clickType.isRightClick()) + { + if (!this.selectedTags.add(tag)) + { + // Remove material if it is already selected + this.selectedTags.remove(tag); + } + + this.build(); + } + else + { + Consumer numberConsumer = number -> { + if (number != null) + { + this.tagMap.put(tag, number.intValue()); + } + + // reopen panel + this.build(); + }; + + ConversationUtils.createNumericInput(numberConsumer, + this.user, + this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), + 1, + Integer.MAX_VALUE); + } + return true; + }). + glow(this.selectedTags.contains(tag)). + build(); + } + + private @Nullable ItemStack getIcon(Tag materialTag, Integer quantity) { + Material m = MultiMaterialTagsSelector.ICONS.getOrDefault(materialTag, Registry.MATERIAL.stream() + .filter(materialTag::isTagged).filter(Material::isItem).findAny().orElse(Material.PAPER)); + return new ItemStack(m, quantity); + } + + +} diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiBlockSelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiBlockSelector.java index e847c97f..b5ce1240 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiBlockSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiBlockSelector.java @@ -18,7 +18,7 @@ /** - * This class contains all necessary things that allows to select single block from all ingame blocks. Selected + * This class contains all necessary things that allows to select single block from all in game blocks. Selected * block will be returned via BiConsumer. */ public class MultiBlockSelector extends PagedSelector diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java new file mode 100644 index 00000000..a62c8135 --- /dev/null +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java @@ -0,0 +1,326 @@ +package world.bentobox.challenges.panel.util; + + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Registry; +import org.bukkit.Tag; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.Nullable; + +import lv.id.bonne.panelutils.PanelUtils; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.builders.PanelBuilder; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.challenges.utils.Constants; +import world.bentobox.challenges.utils.Utils; + + +/** + * This class contains all necessary things that allows to select single material tag from all in game tags. Selected + * tag will be returned via BiConsumer. + */ +public class MultiMaterialTagsSelector extends PagedSelector> +{ + + public static final Map, Material> ICONS = Map.of(Tag.AIR, Material.BARRIER, Tag.FIRE, + Material.TORCH, Tag.CANDLE_CAKES, Material.CAKE, Tag.PORTALS, Material.MAGENTA_STAINED_GLASS_PANE, + Tag.WALL_HANGING_SIGNS, Material.ACACIA_SIGN, Tag.WALL_SIGNS, Material.OAK_SIGN, Tag.WALL_CORALS, + Material.BUBBLE_CORAL_FAN); + + /** + * Functional buttons in current GUI. + */ + private enum Button { + ACCEPT_SELECTED, CANCEL + } + + public enum Mode { + BLOCKS, ITEMS, ANY + } + + // --------------------------------------------------------------------- + // Section: Variables + // --------------------------------------------------------------------- + + private final List> elements = new ArrayList<>(); + + /** + * Set that contains selected materials. + */ + private final Set> selectedElements; + + /** + * This variable stores consumer. + */ + private final BiConsumer>> consumer; + + /** + * Stores filtered items. + */ + private List> filterElements; + + private MultiMaterialTagsSelector(User user, Mode mode, Set> excluded, + BiConsumer>> consumer) { + super(user); + this.consumer = consumer; + + this.selectedElements = new HashSet<>(); + Iterable> iterable = Bukkit.getTags("blocks", Material.class); + iterable.forEach(elements::add); + elements.sort(Comparator.comparing(tag -> tag.getKey().getKey())); + // Remove irrelevant tags + elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("SPAWNABLE")); + elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("PLACE")); + elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("TEMPT")); + elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("_ON")); + elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("BASE")); + elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("SOUND_BLOCKS")); + elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("DRAGON")); + elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("VALID")); + elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("INCORRECT")); + elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("INFINIBURN")); + elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("MINEABLE")); + elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("TOOL")); + elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("SNIFFER")); + elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("OVERRIDE")); + elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("OVERWORLD")); + elements.remove(Tag.BLOCKS_WIND_CHARGE_EXPLOSIONS); + elements.remove(Tag.CONVERTABLE_TO_MUD); + elements.remove(Tag.DAMPENS_VIBRATIONS); + elements.remove(Tag.DOES_NOT_BLOCK_HOPPERS); + elements.remove(Tag.ENCHANTMENT_POWER_PROVIDER); + elements.remove(Tag.ENCHANTMENT_POWER_TRANSMITTER); + elements.remove(Tag.ENDERMAN_HOLDABLE); + elements.remove(Tag.FEATURES_CANNOT_REPLACE); + elements.remove(Tag.FALL_DAMAGE_RESETTING); + elements.remove(Tag.FROG_PREFER_JUMP_TO); + elements.remove(Tag.MAINTAINS_FARMLAND); + elements.remove(Tag.MANGROVE_LOGS_CAN_GROW_THROUGH); + elements.remove(Tag.MANGROVE_ROOTS_CAN_GROW_THROUGH); + elements.remove(Tag.BEE_GROWABLES); + elements.remove(Tag.MOB_INTERACTABLE_DOORS); + elements.remove(Tag.HOGLIN_REPELLENTS); + elements.remove(Tag.PIGLIN_REPELLENTS); + elements.remove(Tag.SNAPS_GOAT_HORN); + elements.remove(Tag.SOUL_SPEED_BLOCKS); + elements.remove(Tag.STRIDER_WARM_BLOCKS); + elements.remove(Tag.SWORD_EFFICIENT); + elements.remove(Tag.UNSTABLE_BOTTOM_CENTER); + elements.remove(Tag.COMPLETES_FIND_TREE_TUTORIAL); + elements.remove(Tag.GUARDED_BY_PIGLINS); + elements.remove(Tag.IMPERMEABLE); + elements.remove(Tag.PREVENT_MOB_SPAWNING_INSIDE); + elements.remove(Tag.SMELTS_TO_GLASS); + elements.remove(Tag.WITHER_IMMUNE); + + // Init without filters applied. + this.filterElements = this.elements; + } + + + /** + * This method opens GUI that allows to select challenge type. + * + * @param user User who opens GUI. + * @param consumer Consumer that allows to get clicked type. + */ + public static void open(User user, Mode mode, Set> excluded, + BiConsumer>> consumer) + { + new MultiMaterialTagsSelector(user, mode, excluded, consumer).build(); + } + + + /** + * This method opens GUI that allows to select challenge type. + * + * @param user User who opens GUI. + * @param consumer Consumer that allows to get clicked type. + */ + public static void open(User user, BiConsumer>> consumer) + { + new MultiMaterialTagsSelector(user, Mode.ANY, new HashSet<>(), consumer).build(); + } + + +// --------------------------------------------------------------------- +// Section: Methods +// --------------------------------------------------------------------- + + + /** + * This method builds all necessary elements in GUI panel. + */ + @Override + protected void build() + { + PanelBuilder panelBuilder = new PanelBuilder().user(this.user); + panelBuilder.name(this.user.getTranslation(Constants.TITLE + "block-selector")); + + PanelUtils.fillBorder(panelBuilder, Material.BLUE_STAINED_GLASS_PANE); + + this.populateElements(panelBuilder, this.filterElements); + + panelBuilder.item(3, this.createButton(Button.ACCEPT_SELECTED)); + panelBuilder.item(5, this.createButton(Button.CANCEL)); + + panelBuilder.build(); + } + + + /** + * This method is called when filter value is updated. + */ + @Override + protected void updateFilters() + { + if (this.searchString == null || this.searchString.isBlank()) + { + this.filterElements = this.elements; + } + else + { + this.filterElements = this.elements.stream(). + filter(element -> { + // If element name is set and name contains search field, then do not filter out. + return element.getKey().getKey().toLowerCase(Locale.ENGLISH) + .contains(this.searchString.toLowerCase(Locale.ENGLISH)); + }). + distinct(). + collect(Collectors.toList()); + } + } + + + /** + * This method creates PanelItem button of requested type. + * @param button Button which must be created. + * @return new PanelItem with requested functionality. + */ + private PanelItem createButton(Button button) + { + final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; + + final String name = this.user.getTranslation(reference + "name"); + final List description = new ArrayList<>(3); + description.add(this.user.getTranslation(reference + "description")); + + ItemStack icon; + PanelItem.ClickHandler clickHandler; + + switch (button) + { + case ACCEPT_SELECTED -> { + if (!this.selectedElements.isEmpty()) + { + description.add(this.user.getTranslation(reference + "title")); + this.selectedElements.forEach(material -> + description.add(this.user.getTranslation(reference + "element", + "[element]", Utils.prettifyObject(material, this.user)))); + } + + icon = new ItemStack(Material.COMMAND_BLOCK); + clickHandler = (panel, user1, clickType, slot) -> + { + this.consumer.accept(true, this.selectedElements); + return true; + }; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-save")); + } + case CANCEL -> { + + icon = new ItemStack(Material.IRON_DOOR); + + clickHandler = (panel, user1, clickType, slot) -> + { + this.consumer.accept(false, null); + return true; + }; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-cancel")); + } + default -> { + icon = new ItemStack(Material.PAPER); + clickHandler = null; + } + } + + return new PanelItemBuilder(). + icon(icon). + name(name). + description(description). + clickHandler(clickHandler). + build(); + } + + + /** + * This method creates button for given material. + * @param materialTag material which button must be created. + * @return new Button for material. + */ + @Override + protected PanelItem createElementButton(Tag materialTag) + { + final String reference = Constants.BUTTON + "materialtag."; + + List description = new ArrayList<>(); + description.add(this.user.getTranslation(reference + "description", + "[id]", materialTag.getKey().getKey())); + + if (this.selectedElements.contains(materialTag)) + { + description.add(this.user.getTranslation(reference + "selected")); + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-deselect")); + } + else + { + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-select")); + } + + return new PanelItemBuilder(). + name(this.user.getTranslation(reference + "name", "[tag]", + Utils.prettifyObject(materialTag, this.user))). + icon(getIcon(materialTag)). + description(description). + clickHandler((panel, user1, clickType, slot) -> { + // On right click change which entities are selected for deletion. + if (!this.selectedElements.add(materialTag)) + { + // Remove material if it is already selected + this.selectedElements.remove(materialTag); + } + + this.build(); + return true; + }). + glow(this.selectedElements.contains(materialTag)). + build(); + } + + + private @Nullable Material getIcon(Tag materialTag) { + return ICONS.getOrDefault(materialTag, Registry.MATERIAL.stream().filter(materialTag::isTagged) + .filter(Material::isItem).findAny() + .orElse(Material.PAPER)); + } + +} diff --git a/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java b/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java index 971923a5..244ed941 100644 --- a/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java +++ b/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java @@ -2,24 +2,29 @@ -import com.google.common.collect.UnmodifiableIterator; -import java.time.*; +import java.time.Duration; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Map.Entry; import java.util.Objects; import java.util.PriorityQueue; import java.util.Queue; import java.util.UUID; +import java.util.function.BiPredicate; import java.util.stream.Collectors; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.GameMode; +import org.bukkit.Keyed; +import org.bukkit.Location; import org.bukkit.Material; +import org.bukkit.Tag; import org.bukkit.World; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; @@ -29,12 +34,13 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.util.BoundingBox; +import com.google.common.collect.UnmodifiableIterator; + 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.challenges.ChallengesAddon; -import world.bentobox.challenges.managers.ChallengesManager; import world.bentobox.challenges.database.object.Challenge; import world.bentobox.challenges.database.object.Challenge.ChallengeType; import world.bentobox.challenges.database.object.ChallengeLevel; @@ -42,6 +48,7 @@ import world.bentobox.challenges.database.object.requirements.IslandRequirements; import world.bentobox.challenges.database.object.requirements.OtherRequirements; import world.bentobox.challenges.database.object.requirements.StatisticRequirements; +import world.bentobox.challenges.managers.ChallengesManager; import world.bentobox.challenges.utils.Constants; import world.bentobox.challenges.utils.Utils; @@ -184,8 +191,7 @@ public static boolean complete(ChallengesAddon addon, int maxTimes) { return new TryToComplete(addon, user, challenge, world, topLabel, permissionPrefix). - build(maxTimes). - meetsRequirements; + build(maxTimes).meetsRequirements; } @@ -266,16 +272,14 @@ ChallengeResult build(int maxTimes) if (result.getFactor() == 1) { Utils.sendMessage(this.user, - this.world, - Constants.MESSAGES + "you-completed-challenge", - Constants.PARAMETER_VALUE, this.challenge.getFriendlyName()); + this.world, Constants.MESSAGES + "you-completed-challenge", Constants.PARAMETER_VALUE, + this.challenge.getFriendlyName()); } if (this.addon.getChallengesSettings().isBroadcastMessages()) { Bukkit.getOnlinePlayers().stream(). - map(User::getInstance). - forEach(user -> Utils.sendMessage(user, + map(User::getInstance).forEach(user -> Utils.sendMessage(user, this.world, Constants.MESSAGES + "name-has-completed-challenge", Constants.PARAMETER_NAME, this.user.getName(), @@ -286,11 +290,11 @@ ChallengeResult build(int maxTimes) if (this.addon.getChallengesSettings().isShowCompletionTitle()) { this.user.getPlayer().sendTitle( - this.parseChallenge(this.user.getTranslation("challenges.titles.challenge-title"), this.challenge), - this.parseChallenge(this.user.getTranslation("challenges.titles.challenge-subtitle"), this.challenge), - 10, - this.addon.getChallengesSettings().getTitleShowtime(), - 20); + this.parseChallenge(this.user.getTranslation("challenges.titles.challenge-title"), + this.challenge), + this.parseChallenge(this.user.getTranslation("challenges.titles.challenge-subtitle"), + this.challenge), + 10, this.addon.getChallengesSettings().getTitleShowtime(), 20); } } @@ -315,7 +319,7 @@ ChallengeResult build(int maxTimes) if (this.addon.isEconomyProvided()) { this.addon.getEconomyProvider().deposit(this.user, - this.challenge.getRepeatMoneyReward() * rewardFactor); + this.challenge.getRepeatMoneyReward() * rewardFactor); } // Experience Repeat Reward @@ -331,17 +335,14 @@ ChallengeResult build(int maxTimes) if (result.getFactor() > 1) { Utils.sendMessage(this.user, - this.world, - Constants.MESSAGES + "you-repeated-challenge-multiple", - Constants.PARAMETER_VALUE, this.challenge.getFriendlyName(), - "[count]", Integer.toString(result.getFactor())); + this.world, Constants.MESSAGES + "you-repeated-challenge-multiple", Constants.PARAMETER_VALUE, + this.challenge.getFriendlyName(), "[count]", Integer.toString(result.getFactor())); } else { Utils.sendMessage(this.user, - this.world, - Constants.MESSAGES + "you-repeated-challenge", - Constants.PARAMETER_VALUE, this.challenge.getFriendlyName()); + this.world, Constants.MESSAGES + "you-repeated-challenge", Constants.PARAMETER_VALUE, + this.challenge.getFriendlyName()); } } @@ -380,15 +381,13 @@ ChallengeResult build(int maxTimes) this.runCommands(level.getRewardCommands()); Utils.sendMessage(this.user, - this.world, - Constants.MESSAGES + "you-completed-level", - Constants.PARAMETER_VALUE, level.getFriendlyName()); + this.world, Constants.MESSAGES + "you-completed-level", Constants.PARAMETER_VALUE, + level.getFriendlyName()); if (this.addon.getChallengesSettings().isBroadcastMessages()) { Bukkit.getOnlinePlayers().stream(). - map(User::getInstance). - forEach(user -> Utils.sendMessage(user, + map(User::getInstance).forEach(user -> Utils.sendMessage(user, this.world, Constants.MESSAGES + "name-has-completed-level", Constants.PARAMETER_NAME, this.user.getName(), @@ -401,11 +400,9 @@ ChallengeResult build(int maxTimes) if (this.addon.getChallengesSettings().isShowCompletionTitle()) { this.user.getPlayer().sendTitle( - this.parseLevel(this.user.getTranslation("challenges.titles.level-title"), level), - this.parseLevel(this.user.getTranslation("challenges.titles.level-subtitle"), level), - 10, - this.addon.getChallengesSettings().getTitleShowtime(), - 20); + this.parseLevel(this.user.getTranslation("challenges.titles.level-title"), level), + this.parseLevel(this.user.getTranslation("challenges.titles.level-subtitle"), level), + 10, this.addon.getChallengesSettings().getTitleShowtime(), 20); } } } @@ -423,110 +420,174 @@ private void fullFillRequirements(ChallengeResult result) { switch (this.challenge.getChallengeType()) { - case ISLAND_TYPE -> { - IslandRequirements requirements = this.challenge.getRequirements(); + case ISLAND_TYPE -> { + IslandRequirements requirements = this.challenge.getRequirements(); - if (result.meetsRequirements && + if (result.meetsRequirements && requirements.isRemoveEntities() && !requirements.getRequiredEntities().isEmpty()) - { - this.removeEntities(result.entities, result.getFactor()); - } + { + this.removeEntities(result.entities, result.getFactor()); + } - if (result.meetsRequirements && + if (result.meetsRequirements && requirements.isRemoveBlocks() && !requirements.getRequiredBlocks().isEmpty()) - { - this.removeBlocks(result.blocks, result.getFactor()); - } + { + this.removeBlocks(result.blocks, result.getFactor()); } - case INVENTORY_TYPE -> { - // If remove items, then remove them - if (this.getInventoryRequirements().isTakeItems()) - { - int sumEverything = result.requiredItems.stream(). + } + case INVENTORY_TYPE -> { + // If remove items, then remove them + if (this.getInventoryRequirements().isTakeItems()) { + int sumEverything = result.requiredItems.stream(). mapToInt(itemStack -> itemStack.getAmount() * result.getFactor()). sum(); - Map removedItems = + Map removedItems = this.removeItems(result.requiredItems, result.getFactor()); - int removedAmount = removedItems.values().stream().mapToInt(num -> num).sum(); + int removedAmount = removedItems.values().stream().mapToInt(num -> num).sum(); - // Something is not removed. - if (sumEverything != removedAmount) - { - Utils.sendMessage(this.user, + // Something is not removed. + if (sumEverything != removedAmount) { + Utils.sendMessage(this.user, this.world, Constants.ERRORS + "cannot-remove-items"); - result.removedItems = removedItems; - result.meetsRequirements = false; - } + result.removedItems = removedItems; + result.meetsRequirements = false; } } - case OTHER_TYPE -> { - OtherRequirements requirements = this.challenge.getRequirements(); + } + case OTHER_TYPE -> { + OtherRequirements requirements = this.challenge.getRequirements(); - if (this.addon.isEconomyProvided() && requirements.isTakeMoney()) - { - this.addon.getEconomyProvider().withdraw(this.user, requirements.getRequiredMoney()); - } + if (this.addon.isEconomyProvided() && requirements.isTakeMoney()) { + this.addon.getEconomyProvider().withdraw(this.user, requirements.getRequiredMoney()); + } - if (requirements.isTakeExperience() && - this.user.getPlayer().getGameMode() != GameMode.CREATIVE) - { - // Cannot take anything from creative game mode. - this.user.getPlayer().setTotalExperience( + if (requirements.isTakeExperience() && this.user.getPlayer().getGameMode() != GameMode.CREATIVE) { + // Cannot take anything from creative game mode. + this.user.getPlayer().setTotalExperience( this.user.getPlayer().getTotalExperience() - requirements.getRequiredExperience()); - } } - case STATISTIC_TYPE -> { - StatisticRequirements requirements = this.challenge.getRequirements(); + } + case STATISTIC_TYPE -> { + StatisticRequirements requirements = this.challenge.getRequirements(); + + if (requirements.isReduceStatistic() && requirements.getStatistic() != null) { + int removeAmount = result.getFactor() * requirements.getAmount(); + + // Start to remove from player who called the completion. + switch (requirements.getStatistic().getType()) + { + case UNTYPED -> { + int statistic = this.user.getPlayer().getStatistic(requirements.getStatistic()); + + if (removeAmount >= statistic) { + this.user.getPlayer().setStatistic(requirements.getStatistic(), 0); + removeAmount -= statistic; + } else { + this.user.getPlayer().setStatistic(requirements.getStatistic(), statistic - removeAmount); + removeAmount = 0; + } + } + case ITEM, BLOCK -> { + if (requirements.getMaterial() == null) { + // Just a sanity check. Material cannot be null at this point of code. + removeAmount = 0; + } else { + int statistic = this.user.getPlayer().getStatistic(requirements.getStatistic(), + requirements.getMaterial()); + + if (removeAmount >= statistic) { + this.user.getPlayer().setStatistic(requirements.getStatistic(), requirements.getMaterial(), + 0); + removeAmount -= statistic; + } else { + this.user.getPlayer().setStatistic(requirements.getStatistic(), requirements.getMaterial(), + statistic - removeAmount); + removeAmount = 0; + } + } + } + case ENTITY -> { + if (requirements.getEntity() == null) { + // Just a sanity check. Entity cannot be null at this point of code. + removeAmount = 0; + } else { + int statistic = this.user.getPlayer().getStatistic(requirements.getStatistic(), + requirements.getEntity()); + + if (removeAmount >= statistic) { + this.user.getPlayer().setStatistic(requirements.getStatistic(), requirements.getEntity(), + 0); + removeAmount -= statistic; + } else { + this.user.getPlayer().setStatistic(requirements.getStatistic(), requirements.getEntity(), + statistic - removeAmount); + removeAmount = 0; + } + } + } + } - if (requirements.isReduceStatistic() && requirements.getStatistic() != null) + // If challenges are in sync with all island members, then punish others too. + if (this.addon.getChallengesSettings().isStoreAsIslandData()) { - int removeAmount = result.getFactor() * requirements.getAmount(); + Island island = this.addon.getIslands().getIsland(this.world, this.user); - // Start to remove from player who called the completion. - switch (requirements.getStatistic().getType()) + if (island == null) { + // hmm + return; + } + + for (UnmodifiableIterator iterator = island.getMemberSet().iterator(); iterator.hasNext() + && removeAmount > 0;) { + Player player = Bukkit.getPlayer(iterator.next()); + + if (player == null || player == this.user.getPlayer()) { + // cannot punish null or player who already was punished. + continue; + } + + switch (Objects.requireNonNull(requirements.getStatistic()).getType()) { case UNTYPED -> { - int statistic = this.user.getPlayer().getStatistic(requirements.getStatistic()); + int statistic = player.getStatistic(requirements.getStatistic()); if (removeAmount >= statistic) { - this.user.getPlayer().setStatistic(requirements.getStatistic(), 0); removeAmount -= statistic; + player.setStatistic(requirements.getStatistic(), 0); } else { - this.user.getPlayer().setStatistic(requirements.getStatistic(), statistic - removeAmount); + player.setStatistic(requirements.getStatistic(), statistic - removeAmount); removeAmount = 0; } } case ITEM, BLOCK -> { if (requirements.getMaterial() == null) { - // Just a sanity check. Material cannot be null at this point of code. + // Just a sanity check. Entity cannot be null at this point of code. removeAmount = 0; } else { - int statistic = this.user.getPlayer().getStatistic(requirements.getStatistic(), - requirements.getMaterial()); + int statistic = player.getStatistic(requirements.getStatistic(), + requirements.getMaterial()); if (removeAmount >= statistic) { - this.user.getPlayer() - .setStatistic(requirements.getStatistic(), requirements.getMaterial(), 0); removeAmount -= statistic; + player.setStatistic(requirements.getStatistic(), requirements.getMaterial(), 0); } else { - this.user.getPlayer().setStatistic(requirements.getStatistic(), - requirements.getMaterial(), - statistic - removeAmount); + player.setStatistic(requirements.getStatistic(), requirements.getMaterial(), + statistic - removeAmount); removeAmount = 0; } } @@ -539,123 +600,28 @@ private void fullFillRequirements(ChallengeResult result) } else { - int statistic = this.user.getPlayer().getStatistic(requirements.getStatistic(), - requirements.getEntity()); + int statistic = player.getStatistic(requirements.getStatistic(), + requirements.getEntity()); if (removeAmount >= statistic) { - this.user.getPlayer().setStatistic(requirements.getStatistic(), requirements.getEntity(), 0); removeAmount -= statistic; + player.setStatistic(requirements.getStatistic(), requirements.getEntity(), 0); } else { - this.user.getPlayer().setStatistic(requirements.getStatistic(), - requirements.getEntity(), - statistic - removeAmount); + player.setStatistic(requirements.getStatistic(), requirements.getEntity(), + statistic - removeAmount); removeAmount = 0; } } } - } - - // If challenges are in sync with all island members, then punish others too. - if (this.addon.getChallengesSettings().isStoreAsIslandData()) - { - Island island = this.addon.getIslands().getIsland(this.world, this.user); - - if (island == null) - { - // hmm - return; - } - - for (UnmodifiableIterator iterator = island.getMemberSet().iterator(); - iterator.hasNext() && removeAmount > 0; ) - { - Player player = Bukkit.getPlayer(iterator.next()); - - if (player == null || player == this.user.getPlayer()) - { - // cannot punish null or player who already was punished. - continue; - } - - switch (Objects.requireNonNull(requirements.getStatistic()).getType()) - { - case UNTYPED -> { - int statistic = player.getStatistic(requirements.getStatistic()); - - if (removeAmount >= statistic) - { - removeAmount -= statistic; - player.setStatistic(requirements.getStatistic(), 0); - } - else - { - player.setStatistic(requirements.getStatistic(), statistic - removeAmount); - removeAmount = 0; - } - } - case ITEM, BLOCK -> { - if (requirements.getMaterial() == null) - { - // Just a sanity check. Entity cannot be null at this point of code. - removeAmount = 0; - } - else - { - int statistic = player.getStatistic(requirements.getStatistic(), - requirements.getMaterial()); - - if (removeAmount >= statistic) - { - removeAmount -= statistic; - player.setStatistic(requirements.getStatistic(), - requirements.getMaterial(), - 0); - } - else - { - player.setStatistic(requirements.getStatistic(), - requirements.getMaterial(), - statistic - removeAmount); - removeAmount = 0; - } - } - } - case ENTITY -> { - if (requirements.getEntity() == null) - { - // Just a sanity check. Entity cannot be null at this point of code. - removeAmount = 0; - } - else - { - int statistic = player.getStatistic(requirements.getStatistic(), - requirements.getEntity()); - - if (removeAmount >= statistic) - { - removeAmount -= statistic; - player.setStatistic(requirements.getStatistic(), - requirements.getEntity(), - 0); - } - else - { - player.setStatistic(requirements.getStatistic(), - requirements.getEntity(), - statistic - removeAmount); - removeAmount = 0; - } - } - } - } } } } } } + } } @@ -667,7 +633,7 @@ private void fullFillRequirements(ChallengeResult result) private ChallengeResult checkIfCanCompleteChallenge(int maxTimes) { ChallengeResult result; - + ChallengeType type = this.challenge.getChallengeType(); // Check the world if (!this.challenge.isDeployed()) @@ -681,14 +647,14 @@ else if (maxTimes < 1) result = EMPTY_RESULT; } else if (Util.getWorld(this.world) != Util.getWorld(this.user.getWorld()) || - !this.challenge.matchGameMode(Utils.getGameMode(this.world))) + !this.challenge.matchGameMode(Utils.getGameMode(this.world))) { Utils.sendMessage(this.user, this.world, "general.errors.wrong-world"); result = EMPTY_RESULT; } // Player is not on island else if (this.user.getLocation() == null || - ChallengesAddon.CHALLENGES_WORLD_PROTECTION.isSetForWorld(this.world) && + ChallengesAddon.CHALLENGES_WORLD_PROTECTION.isSetForWorld(this.world) && !this.addon.getIslands().locationIsOnIsland(this.user.getPlayer(), this.user.getLocation())) { Utils.sendMessage(this.user, this.world, Constants.MESSAGES + "not-on-island"); @@ -696,22 +662,21 @@ else if (this.user.getLocation() == null || } // Check player permission else if (!this.addon.getIslands().getIslandAt(this.user.getLocation()). - map(i -> i.isAllowed(this.user, ChallengesAddon.CHALLENGES_ISLAND_PROTECTION)). - orElse(false)) + map(i -> i.isAllowed(this.user, ChallengesAddon.CHALLENGES_ISLAND_PROTECTION)).orElse(false)) { Utils.sendMessage(this.user, this.world, Constants.MESSAGES + "no-rank"); result = EMPTY_RESULT; } // Check if user has unlocked challenges level. else if (!this.challenge.getLevel().equals(ChallengesManager.FREE) && - !this.manager.isLevelUnlocked(this.user, this.world, this.manager.getLevel(this.challenge.getLevel()))) + !this.manager.isLevelUnlocked(this.user, this.world, this.manager.getLevel(this.challenge.getLevel()))) { Utils.sendMessage(this.user, this.world, Constants.ERRORS + "challenge-level-not-available"); result = EMPTY_RESULT; } // Check max times else if (this.challenge.isRepeatable() && this.challenge.getMaxTimes() > 0 && - this.manager.getChallengeTimes(this.user, this.world, this.challenge) >= this.challenge.getMaxTimes()) + this.manager.getChallengeTimes(this.user, this.world, this.challenge) >= this.challenge.getMaxTimes()) { Utils.sendMessage(this.user, this.world, Constants.ERRORS + "not-repeatable"); result = EMPTY_RESULT; @@ -726,16 +691,16 @@ else if (!this.challenge.isRepeatable() && this.manager.isChallengeComplete(this else if (this.manager.isBreachingTimeOut(this.user, this.world, this.challenge)) { long missing = this.manager.getLastCompletionDate(this.user, this.world, challenge) + - this.challenge.getTimeout() - System.currentTimeMillis(); + this.challenge.getTimeout() - System.currentTimeMillis(); Utils.sendMessage(this.user, this.world, Constants.ERRORS + "timeout", - "[timeout]", Utils.parseDuration(Duration.ofMillis(this.challenge.getTimeout()), this.user), - "[wait-time]", Utils.parseDuration(Duration.ofMillis(missing), this.user)); + "[timeout]", Utils.parseDuration(Duration.ofMillis(this.challenge.getTimeout()), this.user), + "[wait-time]", Utils.parseDuration(Duration.ofMillis(missing), this.user)); result = EMPTY_RESULT; } // Check environment else if (!this.challenge.getEnvironment().isEmpty() && - !this.challenge.getEnvironment().contains(this.user.getWorld().getEnvironment())) + !this.challenge.getEnvironment().contains(this.user.getWorld().getEnvironment())) { Utils.sendMessage(this.user, this.world, Constants.ERRORS + "wrong-environment"); result = EMPTY_RESULT; @@ -784,7 +749,8 @@ else if (type.equals(ChallengeType.STATISTIC_TYPE)) private boolean checkPermissions() { return this.challenge.getRequirements().getRequiredPermissions().isEmpty() || - this.challenge.getRequirements().getRequiredPermissions().stream().allMatch(s -> this.user.hasPermission(s)); + this.challenge.getRequirements().getRequiredPermissions().stream() + .allMatch(s -> this.user.hasPermission(s)); } @@ -838,10 +804,11 @@ private void runCommands(List commands) String alert = "Running command '" + cmd + "' as " + this.user.getName(); this.addon.getLogger().info(alert); cmd = cmd.substring(6). - replaceAll(Constants.ESC + Constants.PARAMETER_PLAYER, this.user.getName()). - replaceAll(Constants.ESC + Constants.PARAMETER_OWNER, owner). - replaceAll(Constants.ESC + Constants.PARAMETER_NAME, island == null || island.getName() == null ? "" : island.getName()). - trim(); + replaceAll(Constants.ESC + Constants.PARAMETER_PLAYER, this.user.getName()) + .replaceAll(Constants.ESC + Constants.PARAMETER_OWNER, owner) + .replaceAll(Constants.ESC + Constants.PARAMETER_NAME, + island == null || island.getName() == null ? "" : island.getName()) + .trim(); try { if (!user.performCommand(cmd)) @@ -862,9 +829,10 @@ private void runCommands(List commands) try { cmd = cmd.replaceAll(Constants.ESC + Constants.PARAMETER_PLAYER, this.user.getName()). - replaceAll(Constants.ESC + Constants.PARAMETER_OWNER, owner). - replaceAll(Constants.ESC + Constants.PARAMETER_NAME, island == null || island.getName() == null ? "" : island.getName()). - trim(); + replaceAll(Constants.ESC + Constants.PARAMETER_OWNER, owner) + .replaceAll(Constants.ESC + Constants.PARAMETER_NAME, + island == null || island.getName() == null ? "" : island.getName()) + .trim(); if (!this.addon.getServer().dispatchCommand(this.addon.getServer().getConsoleSender(), cmd)) { @@ -914,7 +882,7 @@ private ChallengeResult checkInventory(int maxTimes) if (this.user.getPlayer().getGameMode() != GameMode.CREATIVE) { requiredItems = Utils.groupEqualItems(this.getInventoryRequirements().getRequiredItems(), - this.getInventoryRequirements().getIgnoreMetaData()); + this.getInventoryRequirements().getIgnoreMetaData()); // Check if all required items are in players inventory. for (ItemStack required : requiredItems) @@ -924,25 +892,20 @@ private ChallengeResult checkInventory(int maxTimes) if (this.getInventoryRequirements().getIgnoreMetaData().contains(required.getType())) { numInInventory = Arrays.stream(this.user.getInventory().getContents()). - filter(Objects::nonNull). - filter(i -> i.getType().equals(required.getType())). - mapToInt(ItemStack::getAmount). - sum(); + filter(Objects::nonNull).filter(i -> i.getType().equals(required.getType())) + .mapToInt(ItemStack::getAmount).sum(); } else { numInInventory = Arrays.stream(this.user.getInventory().getContents()). - filter(Objects::nonNull). - filter(i -> i.isSimilar(required)). - mapToInt(ItemStack::getAmount). - sum(); + filter(Objects::nonNull).filter(i -> i.isSimilar(required)).mapToInt(ItemStack::getAmount) + .sum(); } if (numInInventory < required.getAmount()) { Utils.sendMessage(this.user, this.world, Constants.ERRORS + "not-enough-items", - "[items]", - Utils.prettifyObject(required, this.user)); + "[items]", Utils.prettifyObject(required, this.user)); return EMPTY_RESULT; } @@ -987,17 +950,14 @@ else if (this.getInventoryRequirements().getIgnoreMetaData().contains(required.g { // Use collecting method that ignores item meta. itemsInInventory = Arrays.stream(user.getInventory().getContents()). - filter(Objects::nonNull). - filter(i -> i.getType().equals(required.getType())). - collect(Collectors.toList()); + filter(Objects::nonNull).filter(i -> i.getType().equals(required.getType())) + .collect(Collectors.toList()); } else { // Use collecting method that compares item meta. itemsInInventory = Arrays.stream(user.getInventory().getContents()). - filter(Objects::nonNull). - filter(i -> i.isSimilar(required)). - collect(Collectors.toList()); + filter(Objects::nonNull).filter(i -> i.isSimilar(required)).collect(Collectors.toList()); } for (ItemStack itemStack : itemsInInventory) @@ -1102,8 +1062,7 @@ private ChallengeResult checkSurrounding(int factor) // Protection code. Do not allow to select too large region for completing challenge. if (boundingBox.getWidthX() > distance * 2 + 3 || - boundingBox.getWidthZ() > distance * 2 + 3 || - boundingBox.getHeight() > distance * 2 + 3) + boundingBox.getWidthZ() > distance * 2 + 3 || boundingBox.getHeight() > distance * 2 + 3) { this.addon.logError("BoundingBox is larger than SearchRadius. " + " | BoundingBox: " + boundingBox + @@ -1118,15 +1077,118 @@ private ChallengeResult checkSurrounding(int factor) ChallengeResult result = this.searchForEntities(requirements.getRequiredEntities(), factor, boundingBox); + // For Material tags + if (result.isMeetsRequirements() && !requirements.getRequiredMaterialTags().isEmpty()) { + result = searchForTags(requirements.getRequiredMaterialTags(), factor, boundingBox, Tag::isTagged, + (world, x, y, z) -> world.getBlockAt(x, y, z).getType()); + } + + // For EntityType tags + if (result.isMeetsRequirements() && !requirements.getRequiredEntityTypeTags().isEmpty()) { + result = searchForTags(requirements.getRequiredEntityTypeTags(), factor, boundingBox, Tag::isTagged, + (world, x, y, z) -> { + Collection entities = world.getNearbyEntities(new Location(world, x, y, z), 1, 1, 1); + return entities.isEmpty() ? null : entities.iterator().next().getType(); + }); + } if (result.isMeetsRequirements() && !requirements.getRequiredBlocks().isEmpty()) { // Search for items only if entities found result = this.searchForBlocks(requirements.getRequiredBlocks(), result.getFactor(), boundingBox); } + return result; } + /** + * Generic method to search for required tags in given challenge boundingBox. + * @param The type parameter for the Tag (must extend Keyed) + * @param requiredMap Required Tag Map + * @param factor Requirement multiplier + * @param boundingBox Bounding box of island challenge + * @param typeChecker Function to check if an element matches the tag + * @param elementGetter Function to get the element at a specific location + * @return ChallengeResult + */ + private ChallengeResult searchForTags(Map, Integer> requiredMap, int factor, + BoundingBox boundingBox, BiPredicate, T> typeChecker, LocationElementGetter elementGetter) { + + if (requiredMap.isEmpty()) { + return new ChallengeResult().setMeetsRequirements().setCompleteFactor(factor); + } + + Map, Integer> tags = new HashMap<>(requiredMap); + Map, Integer> tagsFound = new HashMap<>(requiredMap.size()); + + Queue blockQueue = new PriorityQueue<>((o1, o2) -> { + if (this.user.getLocation() != null) { + return Double.compare(o1.getLocation().distance(this.user.getLocation()), + o2.getLocation().distance(this.user.getLocation())); + } else { + return 0; + } + }); + + for (int x = (int) boundingBox.getMinX(); x <= boundingBox.getMaxX(); x++) { + for (int y = (int) boundingBox.getMinY(); y <= boundingBox.getMaxY(); y++) { + for (int z = (int) boundingBox.getMinZ(); z <= boundingBox.getMaxZ(); z++) { + T element = elementGetter.getElement(this.user.getWorld(), x, y, z); + if (element == null) + continue; + + for (Entry, Integer> en : requiredMap.entrySet()) { + if (typeChecker.test(en.getKey(), element)) { + Block block = this.user.getWorld().getBlockAt(x, y, z); + blockQueue.add(block); + tagsFound.putIfAbsent(en.getKey(), 1); + tagsFound.computeIfPresent(en.getKey(), (k, v) -> v + 1); + // Remove one + tags.computeIfPresent(en.getKey(), (k, v) -> v - 1); + // Remove any that have an amount of 0 + tags.entrySet().removeIf(e -> e.getValue() <= 0); + + if (tags.isEmpty() && factor == 1) { + return new ChallengeResult().setMeetsRequirements().setCompleteFactor(factor) + .setBlockQueue(blockQueue); + } + } + } + } + } + } + + if (tags.isEmpty()) { + if (factor > 1) { + // Calculate minimal completion count + for (Map.Entry, Integer> entry : tagsFound.entrySet()) { + factor = Math.min(factor, entry.getValue() / requiredMap.get(entry.getKey())); + } + } + + tagsFound.clear(); + return new ChallengeResult().setMeetsRequirements().setCompleteFactor(factor).setBlockQueue(blockQueue); + } + + Utils.sendMessage(this.user, this.world, Constants.ERRORS + "not-close-enough", Constants.PARAMETER_NUMBER, + String.valueOf(this.getIslandRequirements().getSearchRadius())); + + tags.forEach((k, v) -> Utils.sendMessage(this.user, this.world, Constants.ERRORS + "you-still-need", "[amount]", + String.valueOf(v), "[item]", Utils.prettifyObject(k, this.user))); + + // kick garbage collector + tags.clear(); + tagsFound.clear(); + blockQueue.clear(); + + return EMPTY_RESULT; + } + + // Interface to get elements at a specific location + @FunctionalInterface + private interface LocationElementGetter { + T getElement(World world, int x, int y, int z); + } /** * This method search required blocks in given challenge boundingBox. @@ -1135,39 +1197,29 @@ private ChallengeResult checkSurrounding(int factor) * @param boundingBox Bounding box of island challenge * @return ChallengeResult */ - private ChallengeResult searchForBlocks(Map requiredMap, int factor, BoundingBox boundingBox) - { - if (requiredMap.isEmpty()) - { + private ChallengeResult searchForBlocks(Map requiredMap, int factor, BoundingBox boundingBox) { + if (requiredMap.isEmpty()) { return new ChallengeResult().setMeetsRequirements().setCompleteFactor(factor); } - Map blocks = new EnumMap<>(requiredMap); Map blocksFound = new HashMap<>(requiredMap.size()); - // This queue will contain only blocks whit required type ordered by distance till player. + // This queue will contain only blocks with the required type ordered by distance till player. Queue blockFromWorld = new PriorityQueue<>((o1, o2) -> { - if (o1.getType().equals(o2.getType()) && this.user.getLocation() != null) - { + if (o1.getType().equals(o2.getType()) && this.user.getLocation() != null) { return Double.compare(o1.getLocation().distance(this.user.getLocation()), - o2.getLocation().distance(this.user.getLocation())); - } - else - { + o2.getLocation().distance(this.user.getLocation())); + } else { return o1.getType().compareTo(o2.getType()); } }); - for (int x = (int) boundingBox.getMinX(); x <= boundingBox.getMaxX(); x++) - { - for (int y = (int) boundingBox.getMinY(); y <= boundingBox.getMaxY(); y++) - { - for (int z = (int) boundingBox.getMinZ(); z <= boundingBox.getMaxZ(); z++) - { + for (int x = (int) boundingBox.getMinX(); x <= boundingBox.getMaxX(); x++) { + for (int y = (int) boundingBox.getMinY(); y <= boundingBox.getMaxY(); y++) { + for (int z = (int) boundingBox.getMinZ(); z <= boundingBox.getMaxZ(); z++) { Block block = this.user.getWorld().getBlockAt(x, y, z); - if (requiredMap.containsKey(block.getType())) - { + if (requiredMap.containsKey(block.getType())) { blockFromWorld.add(block); blocksFound.putIfAbsent(block.getType(), 1); @@ -1178,26 +1230,22 @@ private ChallengeResult searchForBlocks(Map requiredMap, int // Remove any that have an amount of 0 blocks.entrySet().removeIf(en -> en.getValue() <= 0); - if (blocks.isEmpty() && factor == 1) - { + if (blocks.isEmpty() && factor == 1) { // Return as soon as it s empty as no point to search more. - return new ChallengeResult().setMeetsRequirements().setCompleteFactor(factor).setBlockQueue(blockFromWorld); + return new ChallengeResult().setMeetsRequirements().setCompleteFactor(factor) + .setBlockQueue(blockFromWorld); } } } } } - if (blocks.isEmpty()) - { - if (factor > 1) - { + if (blocks.isEmpty()) { + if (factor > 1) { // Calculate minimal completion count. - for (Map.Entry entry : blocksFound.entrySet()) - { - factor = Math.min(factor, - entry.getValue() / requiredMap.get(entry.getKey())); + for (Map.Entry entry : blocksFound.entrySet()) { + factor = Math.min(factor, entry.getValue() / requiredMap.get(entry.getKey())); } } @@ -1207,16 +1255,11 @@ private ChallengeResult searchForBlocks(Map requiredMap, int return new ChallengeResult().setMeetsRequirements().setCompleteFactor(factor).setBlockQueue(blockFromWorld); } - Utils.sendMessage(this.user, - this.world, - Constants.ERRORS + "not-close-enough", - Constants.PARAMETER_NUMBER, String.valueOf(this.getIslandRequirements().getSearchRadius())); + Utils.sendMessage(this.user, this.world, Constants.ERRORS + "not-close-enough", Constants.PARAMETER_NUMBER, + String.valueOf(this.getIslandRequirements().getSearchRadius())); - blocks.forEach((k, v) -> Utils.sendMessage(this.user, - this.world, - Constants.ERRORS + "you-still-need", - "[amount]", String.valueOf(v), - "[item]", Utils.prettifyObject(k, this.user))); + blocks.forEach((k, v) -> Utils.sendMessage(this.user, this.world, Constants.ERRORS + "you-still-need", + "[amount]", String.valueOf(v), "[item]", Utils.prettifyObject(k, this.user))); // kick garbage collector @@ -1235,12 +1278,9 @@ private ChallengeResult searchForBlocks(Map requiredMap, int * @param boundingBox Bounding box of island challenge * @return ChallengeResult */ - private ChallengeResult searchForEntities(Map requiredMap, - int factor, - BoundingBox boundingBox) - { - if (requiredMap.isEmpty()) - { + private ChallengeResult searchForEntities(Map requiredMap, int factor, + BoundingBox boundingBox) { + if (requiredMap.isEmpty()) { return new ChallengeResult().setMeetsRequirements().setCompleteFactor(factor); } @@ -1250,12 +1290,10 @@ private ChallengeResult searchForEntities(Map requiredMap, // Create queue that contains all required entities ordered by distance till player. Queue entityQueue = new PriorityQueue<>((o1, o2) -> { - if (o1.getType().equals(o2.getType()) && this.user.getLocation() != null) - { + if (o1.getType().equals(o2.getType()) && this.user.getLocation() != null) { return Double.compare(o1.getLocation().distance(this.user.getLocation()), - o2.getLocation().distance(this.user.getLocation())); - } - else + o2.getLocation().distance(this.user.getLocation())); + } else { return o1.getType().compareTo(o2.getType()); } @@ -1276,16 +1314,14 @@ private ChallengeResult searchForEntities(Map requiredMap, } }); - if (minimalRequirements.isEmpty()) - { + if (minimalRequirements.isEmpty()) { if (factor > 1) { // Calculate minimal completion count. for (Map.Entry entry : entitiesFound.entrySet()) { - factor = Math.min(factor, - entry.getValue() / requiredMap.get(entry.getKey())); + factor = Math.min(factor, entry.getValue() / requiredMap.get(entry.getKey())); } } @@ -1295,12 +1331,9 @@ private ChallengeResult searchForEntities(Map requiredMap, return new ChallengeResult().setMeetsRequirements().setCompleteFactor(factor).setEntityQueue(entityQueue); } - minimalRequirements.forEach((reqEnt, amount) -> - Utils.sendMessage(this.user, - this.world, - Constants.ERRORS + "you-still-need", - "[amount]", String.valueOf(amount), - "[item]", Utils.prettifyObject(reqEnt, this.user))); + minimalRequirements.forEach( + (reqEnt, amount) -> Utils.sendMessage(this.user, this.world, Constants.ERRORS + "you-still-need", + "[amount]", String.valueOf(amount), "[item]", Utils.prettifyObject(reqEnt, this.user))); // Kick garbage collector entitiesFound.clear(); @@ -1316,8 +1349,7 @@ private ChallengeResult searchForEntities(Map requiredMap, * @param blockQueue Queue with blocks that could be removed * @param factor requirement factor for each block type. */ - private void removeBlocks(Queue blockQueue, int factor) - { + private void removeBlocks(Queue blockQueue, int factor) { Map blocks = new EnumMap<>(this.getIslandRequirements().getRequiredBlocks()); // Increase required blocks by factor. @@ -1340,23 +1372,22 @@ private void removeBlocks(Queue blockQueue, int factor) * @param entityQueue Queue with entities that could be removed * @param factor requirement factor for each entity type. */ - private void removeEntities(Queue entityQueue, int factor) - { - Map entities = this.getIslandRequirements().getRequiredEntities().isEmpty() ? - new EnumMap<>(EntityType.class) : new EnumMap<>(this.getIslandRequirements().getRequiredEntities()); - - // Increase required entities by factor. - entities.entrySet().forEach(entry -> entry.setValue(entry.getValue() * factor)); - - // Go through entity queue and remove entities that are requried. - entityQueue.forEach(entity -> { - if (entities.containsKey(entity.getType())) - { - entities.computeIfPresent(entity.getType(), (reqEntity, amount) -> amount - 1); - entities.entrySet().removeIf(e -> e.getValue() == 0); - entity.remove(); - } - }); + private void removeEntities(Queue entityQueue, int factor) { + Map entities = this.getIslandRequirements().getRequiredEntities().isEmpty() + ? new EnumMap<>(EntityType.class) + : new EnumMap<>(this.getIslandRequirements().getRequiredEntities()); + + // Increase required entities by factor. + entities.entrySet().forEach(entry -> entry.setValue(entry.getValue() * factor)); + + // Go through entity queue and remove entities that are requried. + entityQueue.forEach(entity -> { + if (entities.containsKey(entity.getType())) { + entities.computeIfPresent(entity.getType(), (reqEntity, amount) -> amount - 1); + entities.entrySet().removeIf(e -> e.getValue() == 0); + entity.remove(); + } + }); } @@ -1370,71 +1401,48 @@ private void removeEntities(Queue entityQueue, int factor) * It returns ChallengeResult. * @param factor - times that user wanted to complete */ - private ChallengeResult checkOthers(int factor) - { - if (factor <= 0) - { + private ChallengeResult checkOthers(int factor) { + if (factor <= 0) { return EMPTY_RESULT; } OtherRequirements requirements = this.getOtherRequirements(); - if (!this.addon.isLevelProvided() && requirements.getRequiredIslandLevel() != 0) - { + if (!this.addon.isLevelProvided() && requirements.getRequiredIslandLevel() != 0) { Utils.sendMessage(this.user, this.world, Constants.ERRORS + "missing-addon"); - } - else if (!this.addon.isEconomyProvided() && - requirements.getRequiredMoney() != 0) - { + } else if (!this.addon.isEconomyProvided() && requirements.getRequiredMoney() != 0) { Utils.sendMessage(this.user, this.world, Constants.ERRORS + "missing-addon"); - } - else if (this.addon.isEconomyProvided() && requirements.getRequiredMoney() < 0) - { + } else if (this.addon.isEconomyProvided() && requirements.getRequiredMoney() < 0) { Utils.sendMessage(this.user, this.world, Constants.ERRORS + "incorrect"); - } - else if (this.addon.isEconomyProvided() && - !this.addon.getEconomyProvider().has(this.user, requirements.getRequiredMoney())) - { - Utils.sendMessage(this.user, - this.world, - Constants.ERRORS + "not-enough-money", - Constants.PARAMETER_VALUE, Double.toString(requirements.getRequiredMoney())); - } - else if (requirements.getRequiredExperience() < 0) - { + } else if (this.addon.isEconomyProvided() + && !this.addon.getEconomyProvider().has(this.user, requirements.getRequiredMoney())) { + Utils.sendMessage(this.user, this.world, Constants.ERRORS + "not-enough-money", Constants.PARAMETER_VALUE, + Double.toString(requirements.getRequiredMoney())); + } else if (requirements.getRequiredExperience() < 0) { Utils.sendMessage(this.user, this.world, Constants.ERRORS + "incorrect"); - } - else if (this.user.getPlayer().getTotalExperience() < requirements.getRequiredExperience() && - this.user.getPlayer().getGameMode() != GameMode.CREATIVE) - { + } else if (this.user.getPlayer().getTotalExperience() < requirements.getRequiredExperience() + && this.user.getPlayer().getGameMode() != GameMode.CREATIVE) { // Players in creative gamemode has infinite amount of EXP. + Utils.sendMessage(this.user, this.world, Constants.ERRORS + "not-enough-experience", + Constants.PARAMETER_VALUE, Integer.toString(requirements.getRequiredExperience())); + } else if (this.addon.isLevelProvided() && this.addon.getLevelAddon().getIslandLevel(this.world, + this.user.getUniqueId()) < requirements.getRequiredIslandLevel()) { Utils.sendMessage(this.user, - this.world, - Constants.ERRORS + "not-enough-experience", - Constants.PARAMETER_VALUE, - Integer.toString(requirements.getRequiredExperience())); - } - else if (this.addon.isLevelProvided() && - this.addon.getLevelAddon().getIslandLevel(this.world, this.user.getUniqueId()) < requirements.getRequiredIslandLevel()) - { - Utils.sendMessage(this.user, - this.world, - Constants.ERRORS + "island-level", - TextVariables.NUMBER, - String.valueOf(requirements.getRequiredIslandLevel())); + this.world, Constants.ERRORS + "island-level", TextVariables.NUMBER, + String.valueOf(requirements.getRequiredIslandLevel())); } else { // calculate factor - if (this.addon.isEconomyProvided() && requirements.isTakeMoney()) - { - factor = Math.min(factor, (int) (this.addon.getEconomyProvider().getBalance(this.user) / requirements.getRequiredMoney())); + if (this.addon.isEconomyProvided() && requirements.isTakeMoney()) { + factor = Math.min(factor, (int) (this.addon.getEconomyProvider().getBalance(this.user) + / requirements.getRequiredMoney())); } - if (requirements.getRequiredExperience() > 0 && requirements.isTakeExperience()) - { - factor = Math.min(factor, this.user.getPlayer().getTotalExperience() / requirements.getRequiredExperience()); + if (requirements.getRequiredExperience() > 0 && requirements.isTakeExperience()) { + factor = Math.min(factor, + this.user.getPlayer().getTotalExperience() / requirements.getRequiredExperience()); } return new ChallengeResult().setMeetsRequirements().setCompleteFactor(factor); @@ -1443,7 +1451,6 @@ else if (this.addon.isLevelProvided() && return EMPTY_RESULT; } - // --------------------------------------------------------------------- // Section: Statistic Challenge // --------------------------------------------------------------------- @@ -1454,10 +1461,8 @@ else if (this.addon.isLevelProvided() && * It returns ChallengeResult. * @param factor - times that user wanted to complete */ - private ChallengeResult checkStatistic(int factor) - { - if (factor <= 0) - { + private ChallengeResult checkStatistic(int factor) { + if (factor <= 0) { return EMPTY_RESULT; } @@ -1465,57 +1470,49 @@ private ChallengeResult checkStatistic(int factor) int currentValue; - if (requirements.getStatistic() == null) - { + if (requirements.getStatistic() == null) { // Sanity check. return EMPTY_RESULT; } switch (Objects.requireNonNull(requirements.getStatistic()).getType()) { - case UNTYPED -> currentValue = - this.manager.getStatisticData(this.user, this.world, requirements.getStatistic()); - case ITEM, BLOCK -> currentValue = - this.manager.getStatisticData(this.user, this.world, requirements.getStatistic(), requirements.getMaterial()); - case ENTITY -> currentValue = - this.manager.getStatisticData(this.user, this.world, requirements.getStatistic(), requirements.getEntity()); - default -> currentValue = 0; + case UNTYPED -> + currentValue = this.manager.getStatisticData(this.user, this.world, requirements.getStatistic()); + case ITEM, BLOCK -> currentValue = this.manager.getStatisticData(this.user, this.world, + requirements.getStatistic(), requirements.getMaterial()); + case ENTITY -> currentValue = this.manager.getStatisticData(this.user, this.world, requirements.getStatistic(), + requirements.getEntity()); + default -> currentValue = 0; } - if (currentValue < requirements.getAmount()) - { + if (currentValue < requirements.getAmount()) { switch (Objects.requireNonNull(requirements.getStatistic()).getType()) { - case ITEM, BLOCK -> { - Utils.sendMessage(this.user, - this.world, - Constants.ERRORS + "requirement-not-met-material", - TextVariables.NUMBER, String.valueOf(requirements.getAmount()), - "[statistic]", Utils.prettifyObject(requirements.getStatistic(), this.user), - "[material]", Utils.prettifyObject(requirements.getMaterial(), this.user), - Constants.PARAMETER_VALUE, String.valueOf(currentValue)); - } - case ENTITY -> { - Utils.sendMessage(this.user, - this.world, - Constants.ERRORS + "requirement-not-met-entity", - TextVariables.NUMBER, String.valueOf(requirements.getAmount()), - "[statistic]", Utils.prettifyObject(requirements.getStatistic(), this.user), - "[entity]", Utils.prettifyObject(requirements.getEntity(), this.user), - Constants.PARAMETER_VALUE, String.valueOf(currentValue)); - } - default -> { - Utils.sendMessage(this.user, - this.world, - Constants.ERRORS + "requirement-not-met", - TextVariables.NUMBER, String.valueOf(requirements.getAmount()), - "[statistic]", Utils.prettifyObject(requirements.getStatistic(), this.user), - Constants.PARAMETER_VALUE, String.valueOf(currentValue)); - } + case ITEM, BLOCK -> { + Utils.sendMessage(this.user, this.world, Constants.ERRORS + "requirement-not-met-material", + TextVariables.NUMBER, String.valueOf(requirements.getAmount()), "[statistic]", + Utils.prettifyObject(requirements.getStatistic(), this.user), "[material]", + Utils.prettifyObject(requirements.getMaterial(), this.user), Constants.PARAMETER_VALUE, + String.valueOf(currentValue)); } - } - else - { + case ENTITY -> { + Utils.sendMessage(this.user, + this.world, Constants.ERRORS + "requirement-not-met-entity", TextVariables.NUMBER, + String.valueOf(requirements.getAmount()), "[statistic]", + Utils.prettifyObject(requirements.getStatistic(), this.user), "[entity]", + Utils.prettifyObject(requirements.getEntity(), this.user), Constants.PARAMETER_VALUE, + String.valueOf(currentValue)); + } + default -> { + Utils.sendMessage(this.user, + this.world, Constants.ERRORS + "requirement-not-met", TextVariables.NUMBER, + String.valueOf(requirements.getAmount()), "[statistic]", + Utils.prettifyObject(requirements.getStatistic(), this.user), Constants.PARAMETER_VALUE, + String.valueOf(currentValue)); + } + } + } else { factor = requirements.getAmount() == 0 ? factor : Math.min(factor, currentValue / requirements.getAmount()); return new ChallengeResult().setMeetsRequirements().setCompleteFactor(factor); @@ -1524,24 +1521,20 @@ private ChallengeResult checkStatistic(int factor) return EMPTY_RESULT; } - // --------------------------------------------------------------------- // Section: Title parsings // --------------------------------------------------------------------- - /** * This method pareses input message by replacing all challenge variables in [] with their values. * @param inputMessage inputMessage string * @param challenge Challenge from which these values should be taken * @return new String that replaces [VALUE] with correct value from challenge. */ - private String parseChallenge(String inputMessage, Challenge challenge) - { + private String parseChallenge(String inputMessage, Challenge challenge) { String outputMessage = inputMessage; - if (inputMessage.contains("[") && inputMessage.contains("]")) - { + if (inputMessage.contains("[") && inputMessage.contains("]")) { outputMessage = outputMessage.replace("[friendlyName]", challenge.getFriendlyName()); ChallengeLevel level = challenge.getLevel().isEmpty() ? null : this.manager.getLevel(challenge.getLevel()); @@ -1560,8 +1553,7 @@ private String parseChallenge(String inputMessage, Challenge challenge) * @param level level from which these values should be taken * @return new String that replaces [VALUE] with correct value from level. */ - private String parseLevel(String inputMessage, ChallengeLevel level) - { + private String parseLevel(String inputMessage, ChallengeLevel level) { String outputMessage = inputMessage; if (inputMessage.contains("[") && inputMessage.contains("]")) @@ -1573,7 +1565,6 @@ private String parseLevel(String inputMessage, ChallengeLevel level) return ChatColor.translateAlternateColorCodes('&', outputMessage); } - // --------------------------------------------------------------------- // Section: Simple getter methods // --------------------------------------------------------------------- @@ -1583,8 +1574,7 @@ private String parseLevel(String inputMessage, ChallengeLevel level) * This is simple cast method. Easier access to IslandRequirements. * @return Island Requirements */ - private IslandRequirements getIslandRequirements() - { + private IslandRequirements getIslandRequirements() { return this.challenge.getRequirements(); } @@ -1593,8 +1583,7 @@ private IslandRequirements getIslandRequirements() * This is simple cast method. Easier access to InventoryRequirements. * @return Inventory Requirements */ - private InventoryRequirements getInventoryRequirements() - { + private InventoryRequirements getInventoryRequirements() { return this.challenge.getRequirements(); } @@ -1603,8 +1592,7 @@ private InventoryRequirements getInventoryRequirements() * This is simple cast method. Easier access to OtherRequirements. * @return Other Requirements */ - private OtherRequirements getOtherRequirements() - { + private OtherRequirements getOtherRequirements() { return this.challenge.getRequirements(); } @@ -1619,8 +1607,7 @@ private OtherRequirements getOtherRequirements() * * @author tastybento */ - static class ChallengeResult - { + static class ChallengeResult { /** * This method sets that challenge meets all requirements at least once. * @return Current object. @@ -1637,8 +1624,7 @@ ChallengeResult setMeetsRequirements() * @param completed boolean that indicate that challenge has been already completed. * @return Current object. */ - ChallengeResult setCompleted(boolean completed) - { + ChallengeResult setCompleted(boolean completed) { this.completed = completed; return this; } @@ -1715,8 +1701,7 @@ boolean wasCompleted() * This method returns how many times challenge can be completed. * @return completion count. */ - int getFactor() - { + int getFactor() { return this.factor; } @@ -1725,8 +1710,7 @@ int getFactor() * This method returns if challenge requirements has been met at least once. * @return value of meets requirements variable. */ - boolean isMeetsRequirements() - { + boolean isMeetsRequirements() { return this.meetsRequirements; } diff --git a/src/main/java/world/bentobox/challenges/utils/Utils.java b/src/main/java/world/bentobox/challenges/utils/Utils.java index 120dc9b4..6a3e8780 100644 --- a/src/main/java/world/bentobox/challenges/utils/Utils.java +++ b/src/main/java/world/bentobox/challenges/utils/Utils.java @@ -11,6 +11,7 @@ import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.Statistic; +import org.bukkit.Tag; import org.bukkit.World; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.EntityType; @@ -284,6 +285,25 @@ public static String prettifyDescription(World.Environment object, User user) return ""; } + /** + * Prettify the Tag object for user. + * @param object a tag, like ALL_HANGING_SIGNS + * @param user user + * @return prettified tag + */ + public static String prettifyObject(@Nullable Tag object, User user) { + // Nothing to translate + if (object == null) { + return ""; + } + String translation = user.getTranslationOrNothing( + Constants.MATERIALS + object.getKey().getKey().toLowerCase(Locale.ENGLISH) + ".name"); + String any = user.getTranslationOrNothing(Constants.MATERIALS + "any"); + // Prettify and remove last s + String tag = any + Util.prettifyText(object.getKey().getKey()).replaceAll("s$", ""); + + return translation.isEmpty() ? tag : translation; + } /** * Prettify Material object for user. diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index fe517be4..1ced8d8b 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -8,6 +8,14 @@ meta: - BONNe challenges: + materials: + any: "Any " + all_hanging_signs: + name: "Any Hanging Sign" + all_signs: + name: "Any Sign" + + commands: admin: main: @@ -53,6 +61,8 @@ challenges: choose-player: "&0&l Choose Player" library: "&0&l Library" manage-blocks: "&0&l Manage Blocks" + manage-material-tags: "&0&l Manage Block Groups" + manage-entity-tags: "&0&l Manage Entity Groups" manage-entities: "&0&l Manage Entities" type-selector: "&0&l Challenge Type Selector" item-selector: "&0&l Item Selector" @@ -303,6 +313,15 @@ challenges: title: "&7 Blocks: " list: " &8 - [number] x [block]" none: "&7 Blocks are not added." + required_materialtags: + name: "&f&l Required Material Tags" + description: |- + &7 Allows to change required + &7 material tags for this + &7 challenge to be completable. + title: "&7 Tag: " + list: " &8 - [number] x [tag]" + none: "&7 Tags are not added." search_radius: name: "&f&l Search Radius" description: |- @@ -736,6 +755,10 @@ challenges: description: |- &7 Material ID: [id] selected: "&2 Selected" + materialtag: + name: "&f&l [tag]" + description: "" + selected: "&2 Selected" add_entity: name: "&f&l Add Entity" description: |- @@ -915,11 +938,13 @@ challenges: # Message that will generate for island type requirements and replace [type-requirements] island: lore: |- + [tags] [blocks] [entities] [search-radius] [warning-block] [warning-entity] + tags-title: "&7&l Required:" # Title that will be used if there are defined blocks in island challenge blocks-title: "&7&l Required Blocks:" # Listing of blocks that are required on the island. From 88a5ceec32e4958fbc53af12ca77da928ade7f47 Mon Sep 17 00:00:00 2001 From: tastybento Date: Tue, 4 Feb 2025 20:20:27 -0800 Subject: [PATCH 03/34] Added entity groups. Still a lot of bugs but it basically works. --- .../panel/admin/EditChallengePanel.java | 34 +- ...Panel.java => ManageBlockGroupsPanel.java} | 22 +- .../panel/admin/ManageEntityGroupsPanel.java | 332 ++++++++++++++++++ .../util/MultiEntityTypeTagsSelector.java | 307 ++++++++++++++++ .../panel/util/MultiMaterialTagsSelector.java | 2 +- src/main/resources/locales/en-US.yml | 63 +++- 6 files changed, 731 insertions(+), 29 deletions(-) rename src/main/java/world/bentobox/challenges/panel/admin/{ManageTagsPanel.java => ManageBlockGroupsPanel.java} (92%) create mode 100644 src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java create mode 100644 src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java diff --git a/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java index 2382f493..c63f77b8 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java @@ -11,15 +11,14 @@ import java.util.function.Consumer; import java.util.stream.Collectors; -import org.bukkit.Fluid; import org.bukkit.Material; -import org.bukkit.Tag; import org.bukkit.World; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.inventory.ItemStack; import lv.id.bonne.panelutils.PanelUtils; +import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.panels.PanelItem; import world.bentobox.bentobox.api.panels.PanelListener; import world.bentobox.bentobox.api.panels.builders.PanelBuilder; @@ -172,7 +171,9 @@ private void buildMainPropertiesPanel(PanelBuilder panelBuilder) { * @param panelBuilder PanelBuilder where icons must be added. */ private void buildIslandRequirementsPanel(PanelBuilder panelBuilder) { + BentoBox.getInstance().logDebug("build Island Req pan"); panelBuilder.item(19, this.createRequirementButton(RequirementButton.REQUIRED_ENTITIES)); + panelBuilder.item(20, this.createRequirementButton(RequirementButton.REQUIRED_ENTITYTAGS)); panelBuilder.item(28, this.createRequirementButton(RequirementButton.REMOVE_ENTITIES)); panelBuilder.item(21, this.createRequirementButton(RequirementButton.REQUIRED_BLOCKS)); @@ -619,7 +620,7 @@ private PanelItem createRequirementButton(RequirementButton button) { } // Buttons for Island Requirements case REQUIRED_ENTITIES, REMOVE_ENTITIES, REQUIRED_BLOCKS, REMOVE_BLOCKS, SEARCH_RADIUS, - REQUIRED_MATERIALTAGS -> { + REQUIRED_MATERIALTAGS, REQUIRED_ENTITYTAGS -> { return this.createIslandRequirementButton(button); } // Buttons for Inventory Requirements @@ -697,6 +698,7 @@ private PanelItem createIslandRequirementButton(RequirementButton button) { description.add(""); description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); } + case REQUIRED_MATERIALTAGS -> { if (requirements.getRequiredMaterialTags().isEmpty()) { description.add(this.user.getTranslation(reference + "none")); @@ -705,12 +707,34 @@ private PanelItem createIslandRequirementButton(RequirementButton button) { // Add Material Tags only requirements.getRequiredMaterialTags() .forEach((block, count) -> description.add(this.user.getTranslation(reference + "list", - "[block]", Utils.prettifyObject(block, this.user), "[number]", String.valueOf(count)))); + "[tag]", Utils.prettifyObject(block, this.user), "[number]", String.valueOf(count)))); } icon = new ItemStack(Material.STONE_BRICKS); clickHandler = (panel, user, clickType, slot) -> { - ManageTagsPanel.open(this, requirements.getRequiredMaterialTags()); + ManageBlockGroupsPanel.open(this, requirements.getRequiredMaterialTags()); + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + + case REQUIRED_ENTITYTAGS -> { + if (requirements.getRequiredEntityTypeTags().isEmpty()) { + description.add(this.user.getTranslation(reference + "none")); + } else { + description.add(this.user.getTranslation(reference + "title")); + // Add Material Tags only + requirements.getRequiredEntityTypeTags() + .forEach((block, count) -> description.add(this.user.getTranslation(reference + "list", + "[tag]", Utils.prettifyObject(block, this.user), "[number]", String.valueOf(count)))); + } + + icon = new ItemStack(Material.ZOMBIE_HEAD); + clickHandler = (panel, user, clickType, slot) -> { + ManageEntityGroupsPanel.open(this, requirements.getRequiredEntityTypeTags()); return true; }; glow = false; diff --git a/src/main/java/world/bentobox/challenges/panel/admin/ManageTagsPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/ManageBlockGroupsPanel.java similarity index 92% rename from src/main/java/world/bentobox/challenges/panel/admin/ManageTagsPanel.java rename to src/main/java/world/bentobox/challenges/panel/admin/ManageBlockGroupsPanel.java index 38fd2bd2..56d20a5b 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/ManageTagsPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/ManageBlockGroupsPanel.java @@ -30,9 +30,9 @@ /** - * This class allows to edit material that are in required material map. + * This class allows to edit Block Groups that are in required. */ -public class ManageTagsPanel extends CommonPagedPanel> +public class ManageBlockGroupsPanel extends CommonPagedPanel> { // --------------------------------------------------------------------- @@ -43,7 +43,7 @@ public class ManageTagsPanel extends CommonPagedPanel> * Functional buttons in current GUI. */ private enum Button { - ADD_BLOCK, REMOVE_BLOCK + ADD_BLOCK_GROUP, REMOVE_BLOCK_GROUP } // --------------------------------------------------------------------- @@ -70,7 +70,7 @@ private enum Button { */ private List> filterElements; - private ManageTagsPanel(CommonPanel parentGUI, Map, Integer> map) + private ManageBlockGroupsPanel(CommonPanel parentGUI, Map, Integer> map) { super(parentGUI); this.tagMap = map; @@ -91,7 +91,7 @@ private ManageTagsPanel(CommonPanel parentGUI, Map, Integer> map) */ public static void open(CommonPanel parentGUI, Map, Integer> map) { - new ManageTagsPanel(parentGUI, map).build(); + new ManageBlockGroupsPanel(parentGUI, map).build(); } @@ -131,13 +131,13 @@ protected void updateFilters() protected void build() { PanelBuilder panelBuilder = new PanelBuilder().user(this.user). - name(this.user.getTranslation(Constants.TITLE + "manage-material-tags")); + name(this.user.getTranslation(Constants.TITLE + "manage-block-groups")); // Create nice border. PanelUtils.fillBorder(panelBuilder); - panelBuilder.item(3, this.createButton(Button.ADD_BLOCK)); - panelBuilder.item(5, this.createButton(Button.REMOVE_BLOCK)); + panelBuilder.item(3, this.createButton(Button.ADD_BLOCK_GROUP)); + panelBuilder.item(5, this.createButton(Button.REMOVE_BLOCK_GROUP)); // Fill the box with what is selected this.populateElements(panelBuilder, this.filterElements); @@ -167,7 +167,7 @@ private PanelItem createButton(Button button) switch (button) { - case ADD_BLOCK -> { + case ADD_BLOCK_GROUP -> { icon = new ItemStack(Material.BUCKET); clickHandler = (panel, user1, clickType, slot) -> { @@ -193,7 +193,7 @@ private PanelItem createButton(Button button) description.add(""); description.add(this.user.getTranslation(Constants.TIPS + "click-to-add")); } - case REMOVE_BLOCK -> { + case REMOVE_BLOCK_GROUP -> { if (!this.selectedTags.isEmpty()) { @@ -248,7 +248,7 @@ private PanelItem createButton(Button button) @Override protected PanelItem createElementButton(Tag tag) { - final String reference = Constants.BUTTON + "materialtag."; + final String reference = Constants.BUTTON + "block-group."; List description = new ArrayList<>(); diff --git a/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java new file mode 100644 index 00000000..7dc7a8ca --- /dev/null +++ b/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java @@ -0,0 +1,332 @@ +package world.bentobox.challenges.panel.admin; + + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import org.bukkit.Material; +import org.bukkit.Registry; +import org.bukkit.Tag; +import org.bukkit.entity.EntityType; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.eclipse.jdt.annotation.Nullable; + +import lv.id.bonne.panelutils.PanelUtils; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.builders.PanelBuilder; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.challenges.panel.CommonPagedPanel; +import world.bentobox.challenges.panel.CommonPanel; +import world.bentobox.challenges.panel.ConversationUtils; +import world.bentobox.challenges.panel.util.MultiEntityTypeTagsSelector; +import world.bentobox.challenges.utils.Constants; +import world.bentobox.challenges.utils.Utils; + + +/** + * This class allows to edit material that are in required material map. + */ +public class ManageEntityGroupsPanel extends CommonPagedPanel> +{ + + // --------------------------------------------------------------------- + // Section: Enums + // --------------------------------------------------------------------- + + /** + * Functional buttons in current GUI. + */ + private enum Button { + ADD_ENTITY_GROUP, REMOVE_ENTITY_GROUP + } + + // --------------------------------------------------------------------- + // Section: Variables + // --------------------------------------------------------------------- + + /** + * Contains selected materials. + */ + private final Set> selectedTags; + + /** + * List of materials to avoid order issues. + */ + private final List> materialList; + + /** + * List of required materials. + */ + private final Map, Integer> tagMap; + + /** + * Stores filtered items. + */ + private List> filterElements; + + private ManageEntityGroupsPanel(CommonPanel parentGUI, Map, Integer> map) + { + super(parentGUI); + this.tagMap = map; + this.materialList = new ArrayList<>(this.tagMap.keySet()); + + // Sort tags by their ordinal value. + this.materialList.sort(Comparator.comparing(tag -> tag.getKey().getKey())); + + this.selectedTags = new HashSet<>(); + + // Init without filters applied. + this.filterElements = this.materialList; + } + + + /** + * Open the Challenges Admin GUI. + */ + public static void open(CommonPanel parentGUI, Map, Integer> map) + { + new ManageEntityGroupsPanel(parentGUI, map).build(); + } + + +// --------------------------------------------------------------------- +// Section: Methods +// --------------------------------------------------------------------- + + + /** + * This method is called when filter value is updated. + */ + @Override + protected void updateFilters() + { + if (this.searchString == null || this.searchString.isBlank()) + { + this.filterElements = this.materialList; + } + else + { + this.filterElements = this.materialList.stream(). + filter(element -> { + // If element name is set and name contains search field, then do not filter out. + return element.getKey().getKey().toLowerCase(Locale.ENGLISH) + .contains(this.searchString.toLowerCase(Locale.ENGLISH)); + }). + distinct(). + collect(Collectors.toList()); + } + } + + + /** + * This method builds all necessary elements in GUI panel. + */ + @Override + protected void build() + { + PanelBuilder panelBuilder = new PanelBuilder().user(this.user). + name(this.user.getTranslation(Constants.TITLE + "manage-entity-groups")); + + // Create nice border. + PanelUtils.fillBorder(panelBuilder); + + panelBuilder.item(3, this.createButton(Button.ADD_ENTITY_GROUP)); + panelBuilder.item(5, this.createButton(Button.REMOVE_ENTITY_GROUP)); + // Fill the box with what is selected + this.populateElements(panelBuilder, this.filterElements); + + // Add return button. + panelBuilder.item(44, this.returnButton); + + panelBuilder.build(); + } + + + /** + * This method creates PanelItem button of requested type. + * @param button Button which must be created. + * @return new PanelItem with requested functionality. + */ + private PanelItem createButton(Button button) + { + final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; + + final String name = this.user.getTranslation(reference + "name"); + final List description = new ArrayList<>(3); + description.add(this.user.getTranslation(reference + "description")); + + ItemStack icon; + PanelItem.ClickHandler clickHandler; + boolean glow; + + switch (button) + { + case ADD_ENTITY_GROUP -> { + icon = new ItemStack(Material.BUCKET); + clickHandler = (panel, user1, clickType, slot) -> + { + MultiEntityTypeTagsSelector.open(this.user, MultiEntityTypeTagsSelector.Mode.ENTITY_TYPE, + new HashSet<>(this.materialList), + (status, materials) -> + { + if (status) + { + materials.forEach(material -> + { + this.tagMap.put(material, 1); + this.materialList.add(material); + }); + } + + this.build(); + }); + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-add")); + } + case REMOVE_ENTITY_GROUP -> { + + if (!this.selectedTags.isEmpty()) + { + description.add(this.user.getTranslation(reference + "title")); + this.selectedTags.forEach(material -> + description.add(this.user.getTranslation(reference + "material", + "[material]", Utils.prettifyObject(material, this.user)))); + } + + icon = new ItemStack(Material.LAVA_BUCKET); + + clickHandler = (panel, user1, clickType, slot) -> + { + if (!this.selectedTags.isEmpty()) + { + this.tagMap.keySet().removeAll(this.selectedTags); + this.materialList.removeAll(this.selectedTags); + this.selectedTags.clear(); + this.build(); + } + + return true; + }; + + glow = !this.selectedTags.isEmpty(); + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-remove")); + } + default -> { + icon = new ItemStack(Material.PAPER); + clickHandler = null; + glow = false; + } + } + + return new PanelItemBuilder(). + icon(icon). + name(name). + description(description). + clickHandler(clickHandler). + glow(glow). + build(); + } + + + /** + * This method creates button for given material. + * @param tag material which button must be created. + * @return new Button for material. + */ + @Override + protected PanelItem createElementButton(Tag tag) + { + final String reference = Constants.BUTTON + "entity-group."; + + List description = new ArrayList<>(); + + if (this.selectedTags.contains(tag)) + { + description.add(this.user.getTranslation(reference + "selected")); + } + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "left-click-to-choose")); + + if (this.selectedTags.contains(tag)) + { + description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-deselect")); + } + else + { + description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-select")); + } + + return new PanelItemBuilder(). + name(this.user.getTranslation(reference + "name", "[tag]", + Utils.prettifyObject(tag, this.user))). + icon(getIcon(tag, this.tagMap.get(tag))). + description(description). + clickHandler((panel, user1, clickType, slot) -> { + // On right click change which entities are selected for deletion. + if (clickType.isRightClick()) + { + if (!this.selectedTags.add(tag)) + { + // Remove material if it is already selected + this.selectedTags.remove(tag); + } + + this.build(); + } + else + { + Consumer numberConsumer = number -> { + if (number != null) + { + this.tagMap.put(tag, number.intValue()); + } + + // reopen panel + this.build(); + }; + + ConversationUtils.createNumericInput(numberConsumer, + this.user, + this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), + 1, + Integer.MAX_VALUE); + } + return true; + }). + glow(this.selectedTags.contains(tag)). + build(); + } + + private @Nullable ItemStack getIcon(Tag entityTag, Integer quantity) { + if (entityTag.getKey().getKey().contains("boat")) { + return new ItemStack(Material.OAK_PLANKS, quantity); // Boats cannot be stacked + } + EntityType entType = Registry.ENTITY_TYPE.stream().filter(entityTag::isTagged).findAny().orElse(null); + String eggName = entType.getKey().getKey().toUpperCase(Locale.ENGLISH) + "_SPAWN_EGG"; + Material result; + try { + result = Material.valueOf(eggName); + } catch (Exception e) { + result = Material.PAPER; + } + return new ItemStack(result, quantity); + + } + + +} diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java new file mode 100644 index 00000000..681050ff --- /dev/null +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java @@ -0,0 +1,307 @@ +package world.bentobox.challenges.panel.util; + + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Registry; +import org.bukkit.Tag; +import org.bukkit.entity.EntityType; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.Nullable; + +import lv.id.bonne.panelutils.PanelUtils; +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.builders.PanelBuilder; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.challenges.utils.Constants; +import world.bentobox.challenges.utils.Utils; + + +/** + * This class contains all necessary things that allows to select single entitytype tag from all in game tags. Selected + * tag will be returned via BiConsumer. + */ +public class MultiEntityTypeTagsSelector extends PagedSelector> +{ + + public static final Map, Material> ICONS = Map.of(); + + /** + * Functional buttons in current GUI. + */ + private enum Button { + ACCEPT_SELECTED, CANCEL + } + + public enum Mode { + ENTITY_TYPE, ANY + } + + // --------------------------------------------------------------------- + // Section: Variables + // --------------------------------------------------------------------- + + private final List> elements = new ArrayList<>(); + + /** + * Set that contains selected materials. + */ + private final Set> selectedElements; + + /** + * This variable stores consumer. + */ + private final BiConsumer>> consumer; + + /** + * Stores filtered items. + */ + private List> filterElements; + + private MultiEntityTypeTagsSelector(User user, Mode mode, Set> excluded, + BiConsumer>> consumer) { + super(user); + this.consumer = consumer; + + this.selectedElements = new HashSet<>(); + Iterable> iterable = Bukkit.getTags("entity_types", EntityType.class); + iterable.forEach(elements::add); + elements.sort(Comparator.comparing(tag -> tag.getKey().getKey())); + // Remove irrelevant tags + elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("AXOLOTL")); + elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("IMMUNE")); + elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("IGNORES")); + elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("FRIEND")); + elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("SENSITIVE")); + elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("PROJECTILE")); + elements.remove(Tag.ENTITY_TYPES_ARROWS); + elements.remove(Tag.ENTITY_TYPES_BEEHIVE_INHABITORS); + elements.remove(Tag.ENTITY_TYPES_CAN_TURN_IN_BOATS); + elements.remove(Tag.ENTITY_TYPES_DISMOUNTS_UNDERWATER); + elements.remove(Tag.ENTITY_TYPES_FALL_DAMAGE_IMMUNE); + elements.remove(Tag.ENTITY_TYPES_FREEZE_HURTS_EXTRA_TYPES); + elements.remove(Tag.ENTITY_TYPES_INVERTED_HEALING_AND_HARM); + elements.remove(Tag.ENTITY_TYPES_NO_ANGER_FROM_WIND_CHARGE); + elements.remove(Tag.ENTITY_TYPES_NON_CONTROLLING_RIDER); + elements.remove(Tag.ENTITY_TYPES_NOT_SCARY_FOR_PUFFERFISH); + elements.remove(Tag.ENTITY_TYPES_FROG_FOOD); + // Init without filters applied. + this.filterElements = this.elements; + } + + + /** + * This method opens GUI that allows to select challenge type. + * + * @param user User who opens GUI. + * @param consumer Consumer that allows to get clicked type. + */ + public static void open(User user, Mode mode, Set> excluded, + BiConsumer>> consumer) + { + new MultiEntityTypeTagsSelector(user, mode, excluded, consumer).build(); + } + + + /** + * This method opens GUI that allows to select challenge type. + * + * @param user User who opens GUI. + * @param consumer Consumer that allows to get clicked type. + */ + public static void open(User user, BiConsumer>> consumer) + { + new MultiEntityTypeTagsSelector(user, Mode.ANY, new HashSet<>(), consumer).build(); + } + + +// --------------------------------------------------------------------- +// Section: Methods +// --------------------------------------------------------------------- + + + /** + * This method builds all necessary elements in GUI panel. + */ + @Override + protected void build() + { + PanelBuilder panelBuilder = new PanelBuilder().user(this.user); + panelBuilder.name(this.user.getTranslation(Constants.TITLE + "entity-selector")); + + PanelUtils.fillBorder(panelBuilder, Material.BLUE_STAINED_GLASS_PANE); + + this.populateElements(panelBuilder, this.filterElements); + + panelBuilder.item(3, this.createButton(Button.ACCEPT_SELECTED)); + panelBuilder.item(5, this.createButton(Button.CANCEL)); + + panelBuilder.build(); + } + + + /** + * This method is called when filter value is updated. + */ + @Override + protected void updateFilters() + { + if (this.searchString == null || this.searchString.isBlank()) + { + this.filterElements = this.elements; + } + else + { + this.filterElements = this.elements.stream(). + filter(element -> { + // If element name is set and name contains search field, then do not filter out. + return element.getKey().getKey().toLowerCase(Locale.ENGLISH) + .contains(this.searchString.toLowerCase(Locale.ENGLISH)); + }). + distinct(). + collect(Collectors.toList()); + } + } + + + /** + * This method creates PanelItem button of requested type. + * @param button Button which must be created. + * @return new PanelItem with requested functionality. + */ + private PanelItem createButton(Button button) + { + final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; + + final String name = this.user.getTranslation(reference + "name"); + final List description = new ArrayList<>(3); + description.add(this.user.getTranslation(reference + "description")); + + ItemStack icon; + PanelItem.ClickHandler clickHandler; + + switch (button) + { + case ACCEPT_SELECTED -> { + if (!this.selectedElements.isEmpty()) + { + description.add(this.user.getTranslation(reference + "title")); + this.selectedElements.forEach(material -> + description.add(this.user.getTranslation(reference + "element", + "[element]", Utils.prettifyObject(material, this.user)))); + } + + icon = new ItemStack(Material.COMMAND_BLOCK); + clickHandler = (panel, user1, clickType, slot) -> + { + this.consumer.accept(true, this.selectedElements); + return true; + }; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-save")); + } + case CANCEL -> { + + icon = new ItemStack(Material.IRON_DOOR); + + clickHandler = (panel, user1, clickType, slot) -> + { + this.consumer.accept(false, null); + return true; + }; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-cancel")); + } + default -> { + icon = new ItemStack(Material.PAPER); + clickHandler = null; + } + } + + return new PanelItemBuilder(). + icon(icon). + name(name). + description(description). + clickHandler(clickHandler). + build(); + } + + + /** + * This method creates button for given material. + * @param materialTag material which button must be created. + * @return new Button for material. + */ + @Override + protected PanelItem createElementButton(Tag materialTag) + { + final String reference = Constants.BUTTON + "entity-group."; + + List description = new ArrayList<>(); + description.add(this.user.getTranslation(reference + "description", + "[id]", materialTag.getKey().getKey())); + + if (this.selectedElements.contains(materialTag)) + { + description.add(this.user.getTranslation(reference + "selected")); + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-deselect")); + } + else + { + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-select")); + } + + return new PanelItemBuilder(). + name(this.user.getTranslation(reference + "name", "[tag]", + Utils.prettifyObject(materialTag, this.user))). + icon(getIcon(materialTag)). + description(description). + clickHandler((panel, user1, clickType, slot) -> { + // On right click change which entities are selected for deletion. + if (!this.selectedElements.add(materialTag)) + { + // Remove material if it is already selected + this.selectedElements.remove(materialTag); + } + + this.build(); + return true; + }). + glow(this.selectedElements.contains(materialTag)). + build(); + } + + + private @Nullable Material getIcon(Tag materialTag) { + if (materialTag.getKey().getKey().contains("boat")) { + return Material.OAK_BOAT; + } + EntityType entType = Registry.ENTITY_TYPE.stream().filter(materialTag::isTagged).findAny().orElse(null); + String eggName = entType.getKey().getKey().toUpperCase(Locale.ENGLISH) + "_SPAWN_EGG"; + Material result; + try { + result = Material.valueOf(eggName); + } catch (Exception e) { + result = Material.PAPER; + } + return result; + } + +} diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java index a62c8135..c682b527 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java @@ -278,7 +278,7 @@ private PanelItem createButton(Button button) @Override protected PanelItem createElementButton(Tag materialTag) { - final String reference = Constants.BUTTON + "materialtag."; + final String reference = Constants.BUTTON + "block-group."; List description = new ArrayList<>(); description.add(this.user.getTranslation(reference + "description", diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 1ced8d8b..36b1dc42 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -61,8 +61,8 @@ challenges: choose-player: "&0&l Choose Player" library: "&0&l Library" manage-blocks: "&0&l Manage Blocks" - manage-material-tags: "&0&l Manage Block Groups" - manage-entity-tags: "&0&l Manage Entity Groups" + manage-block-groups: "&0&l Manage Block Groups" + manage-entity-groups: "&0&l Manage Entity Groups" manage-entities: "&0&l Manage Entities" type-selector: "&0&l Challenge Type Selector" item-selector: "&0&l Item Selector" @@ -313,15 +313,6 @@ challenges: title: "&7 Blocks: " list: " &8 - [number] x [block]" none: "&7 Blocks are not added." - required_materialtags: - name: "&f&l Required Material Tags" - description: |- - &7 Allows to change required - &7 material tags for this - &7 challenge to be completable. - title: "&7 Tag: " - list: " &8 - [number] x [tag]" - none: "&7 Tags are not added." search_radius: name: "&f&l Search Radius" description: |- @@ -390,6 +381,24 @@ challenges: &7 required island level &7 for the challenge. value: "&7 Current level: &e [number]" + required_materialtags: + name: "&f&l Required Block Groups" + description: |- + &7 Allows to change required + &7 block groups for this + &7 challenge to be completable. + title: "&7 Block group: " + list: " &8 - [number] x [tag]" + none: "&7 Block groups are not added." + required_entitytags: + name: "&f&l Required Entity Groups" + description: |- + &7 Allows to change required + &7 entity groups for this + &7 challenge to be completable. + title: "&7 Entity group: " + list: " &8 - [number] x [tag]" + none: "&7 Entity groups are not added." remove_money: name: "&f&l Remove Money" description: |- @@ -742,6 +751,11 @@ challenges: description: |- &7 Allows to add a new &7 block to the list. + add_block_group: + name: "&f&l Add Block Group" + description: |- + &7 Allows to add a new + &7 block group to the list. remove_block: name: "&f&l Remove Block" description: |- @@ -750,12 +764,20 @@ challenges: &7 from lists. title: "&7 Selected Materials:" material: "&8 - [material]" + remove_block_group: + name: "&f&l Remove Block Group" + description: |- + &7 Allows to remove + &7 selected block groups + &7 from lists. + title: "&7 Selected Block Groups:" + material: "&8 - [tag]" material: name: "&f&l [material]" description: |- &7 Material ID: [id] selected: "&2 Selected" - materialtag: + block-group: name: "&f&l [tag]" description: "" selected: "&2 Selected" @@ -764,6 +786,11 @@ challenges: description: |- &7 Allows to add a new &7 entity to the list. + add_entity_group: + name: "&f&l Add Entity Group" + description: |- + &7 Allows to add a new + &7 entity group to the list. switch_entity: name: "&f&l Switch Eggs" description: |- @@ -777,11 +804,23 @@ challenges: &7 from lists. title: "&7 Selected Entities:" entity: "&8 - [entity]" + remove_entity_group: + name: "&f&l Remove Entity Group" + description: |- + &7 Allows to remove + &7 selected entity group + &7 from lists. + title: "&7 Selected Entity Groups:" + entity: "&8 - [tag]" entity: name: "&f&l [entity]" description: |- &7 Entity ID: [id] selected: "&2 Selected" + entity-group: + name: "&f&l [tag]" + description: "" + selected: "&2 Selected" inventory_type: name: "&f&l Inventory Type" description: |- From a06dfd73d8e644f5dac94f297084dace8e20ec5c Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 5 Feb 2025 18:57:23 -0800 Subject: [PATCH 04/34] Fixed/improved the challenge requirements list --- .../challenges/panel/CommonPagedPanel.java | 1 + .../challenges/panel/CommonPanel.java | 123 +++++++++++------- src/main/resources/locales/en-US.yml | 2 - 3 files changed, 76 insertions(+), 50 deletions(-) diff --git a/src/main/java/world/bentobox/challenges/panel/CommonPagedPanel.java b/src/main/java/world/bentobox/challenges/panel/CommonPagedPanel.java index 947c10e5..24482124 100644 --- a/src/main/java/world/bentobox/challenges/panel/CommonPagedPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/CommonPagedPanel.java @@ -103,6 +103,7 @@ else if (this.pageIndex > (size / MAX_ELEMENTS)) { if (!panelBuilder.slotOccupied(index)) { + // Show a challenge panelBuilder.item(index, this.createElementButton(objectList.get(objectIndex++))); } diff --git a/src/main/java/world/bentobox/challenges/panel/CommonPanel.java b/src/main/java/world/bentobox/challenges/panel/CommonPanel.java index e3162398..8cedb18e 100644 --- a/src/main/java/world/bentobox/challenges/panel/CommonPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/CommonPanel.java @@ -38,6 +38,8 @@ * This class contains common methods for all panels. */ public abstract class CommonPanel { + private static final long MAXSIZE = 10; + /** * This is default constructor for all classes that extends CommonPanel. * @@ -148,7 +150,7 @@ protected List generateChallengeDescription(Challenge challenge, @Nullab String requirements = isCompletedAll ? "" : this.generateRequirements(challenge, target); // Get rewards in single string String rewards = isCompletedAll ? "" : this.generateRewards(challenge, isCompletedOnce); - // Get coolDown in singe string + // Get coolDown in single string String coolDown = isCompletedAll || challenge.getTimeout() <= 0 ? "" : this.generateCoolDown(challenge, target); if (!description.replaceAll("(?m)^[ \\t]*\\r?\\n", "").isEmpty()) { @@ -284,74 +286,47 @@ private String generateRequirements(Challenge challenge, @Nullable User target) private String generateIslandChallenge(IslandRequirements requirement) { final String reference = Constants.DESCRIPTIONS + "challenge.requirements.island."; - // Required Tags - Tags cover both blocks and entities - String tags; - if (!requirement.getRequiredMaterialTags().isEmpty()) { - StringBuilder builder = new StringBuilder(); - builder.append(this.user.getTranslationOrNothing(reference + "tags-title")); - requirement.getRequiredMaterialTags().entrySet().stream().forEach(entry -> { - builder.append("\n"); - - if (entry.getValue() > 1) { - builder.append(this.user.getTranslationOrNothing(reference + "blocks-value", - Constants.PARAMETER_NUMBER, String.valueOf(entry.getValue()), Constants.PARAMETER_MATERIAL, - Utils.prettifyObject(entry.getKey(), this.user))); - } else { - builder.append(this.user.getTranslationOrNothing(reference + "block-value", - Constants.PARAMETER_MATERIAL, Utils.prettifyObject(entry.getKey(), this.user))); - } - }); - - tags = builder.toString(); - } else { - tags = ""; - } - // Required Blocks - String blocks; - + StringBuilder blocks = new StringBuilder(); + blocks.append(getBlocksTagsDescription(requirement, reference)); if (!requirement.getRequiredBlocks().isEmpty()) { - StringBuilder builder = new StringBuilder(); - builder.append(this.user.getTranslationOrNothing(reference + "blocks-title")); requirement.getRequiredBlocks().entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry -> { - builder.append("\n"); + blocks.append("\n"); if (entry.getValue() > 1) { - builder.append(this.user.getTranslationOrNothing(reference + "blocks-value", + blocks.append(this.user.getTranslationOrNothing(reference + "blocks-value", Constants.PARAMETER_NUMBER, String.valueOf(entry.getValue()), Constants.PARAMETER_MATERIAL, Utils.prettifyObject(entry.getKey(), this.user))); } else { - builder.append(this.user.getTranslationOrNothing(reference + "block-value", + blocks.append(this.user.getTranslationOrNothing(reference + "block-value", Constants.PARAMETER_MATERIAL, Utils.prettifyObject(entry.getKey(), this.user))); } }); - - blocks = builder.toString(); - } else { - blocks = ""; + } + // Add title if there is something here + if (!blocks.isEmpty()) { + blocks.insert(0, this.user.getTranslationOrNothing(reference + "blocks-title")); } - String entities; - + StringBuilder entities = new StringBuilder(); + entities.append(getEntityTypeTagsDescription(requirement, reference)); if (!requirement.getRequiredEntities().isEmpty()) { - StringBuilder builder = new StringBuilder(); - builder.append(this.user.getTranslationOrNothing(reference + "entities-title")); requirement.getRequiredEntities().entrySet().stream().sorted(Map.Entry.comparingByKey()).forEach(entry -> { - builder.append("\n"); + entities.append("\n"); if (entry.getValue() > 1) { - builder.append(this.user.getTranslationOrNothing(reference + "entities-value", + entities.append(this.user.getTranslationOrNothing(reference + "entities-value", Constants.PARAMETER_NUMBER, String.valueOf(entry.getValue()), Constants.PARAMETER_ENTITY, Utils.prettifyObject(entry.getKey(), this.user))); } else { - builder.append(this.user.getTranslationOrNothing(reference + "entity-value", + entities.append(this.user.getTranslationOrNothing(reference + "entity-value", Constants.PARAMETER_ENTITY, Utils.prettifyObject(entry.getKey(), this.user))); } }); - - entities = builder.toString(); - } else { - entities = ""; + } + // Add title if there is something here + if (!entities.isEmpty()) { + entities.insert(0, this.user.getTranslationOrNothing(reference + "entities-title")); } String searchRadius = this.user.getTranslationOrNothing(reference + "search-radius", Constants.PARAMETER_NUMBER, @@ -364,11 +339,63 @@ private String generateIslandChallenge(IslandRequirements requirement) { ? this.user.getTranslationOrNothing(reference + "warning-entity") : ""; - return this.user.getTranslationOrNothing(reference + "lore", "[blocks]", blocks, "[entities]", entities, - "[tags]", tags, + return this.user.getTranslationOrNothing(reference + "lore", "[blocks]", blocks.toString(), "[entities]", + entities.toString(), "[warning-block]", warningBlocks, "[warning-entity]", warningEntities, "[search-radius]", searchRadius); } + private String getBlocksTagsDescription(IslandRequirements requirement, String reference) { + String tags = ""; + if (!requirement.getRequiredMaterialTags().isEmpty()) { + StringBuilder builder = new StringBuilder(); + requirement.getRequiredMaterialTags().entrySet().stream().limit(MAXSIZE).forEach(entry -> { + builder.append("\n"); + + if (entry.getValue() > 1) { + builder.append(this.user.getTranslationOrNothing(reference + "blocks-value", + Constants.PARAMETER_NUMBER, String.valueOf(entry.getValue()), Constants.PARAMETER_MATERIAL, + Utils.prettifyObject(entry.getKey(), this.user))); + } else { + builder.append(this.user.getTranslationOrNothing(reference + "block-value", + Constants.PARAMETER_MATERIAL, Utils.prettifyObject(entry.getKey(), this.user))); + } + }); + if (requirement.getRequiredMaterialTags().size() > MAXSIZE) { + builder.append("...\n"); + } + tags = builder.toString(); + } + + return tags; + } + + private String getEntityTypeTagsDescription(IslandRequirements requirement, String reference) { + String tags = ""; + if (!requirement.getRequiredEntityTypeTags().isEmpty()) { + StringBuilder builder = new StringBuilder(); + requirement.getRequiredEntityTypeTags().entrySet().stream().limit(MAXSIZE).forEach(entry -> { + builder.append("\n"); + + if (entry.getValue() > 1) { + builder.append(this.user.getTranslationOrNothing(reference + "blocks-value", + Constants.PARAMETER_NUMBER, String.valueOf(entry.getValue()), Constants.PARAMETER_MATERIAL, + Utils.prettifyObject(entry.getKey(), this.user))); + } else { + builder.append(this.user.getTranslationOrNothing(reference + "block-value", + Constants.PARAMETER_MATERIAL, Utils.prettifyObject(entry.getKey(), this.user))); + } + }); + if (requirement.getRequiredEntityTypeTags().size() > MAXSIZE) { + builder.append("...\n"); + } + + tags = builder.toString(); + } + + return tags; + } + + /** * This method generates lore message for inventory requirement. * diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 36b1dc42..2ecf3d1d 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -977,13 +977,11 @@ challenges: # Message that will generate for island type requirements and replace [type-requirements] island: lore: |- - [tags] [blocks] [entities] [search-radius] [warning-block] [warning-entity] - tags-title: "&7&l Required:" # Title that will be used if there are defined blocks in island challenge blocks-title: "&7&l Required Blocks:" # Listing of blocks that are required on the island. From 63b7af85f0dae0d49b2834520ded8bca3f4ae4dc Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 6 Feb 2025 07:39:54 -0800 Subject: [PATCH 05/34] Fix list of entity groups in remove icon --- .../panel/admin/ManageEntityGroupsPanel.java | 4 ++-- src/main/resources/locales/en-US.yml | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java index 7dc7a8ca..42328fec 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java @@ -201,8 +201,8 @@ private PanelItem createButton(Button button) { description.add(this.user.getTranslation(reference + "title")); this.selectedTags.forEach(material -> - description.add(this.user.getTranslation(reference + "material", - "[material]", Utils.prettifyObject(material, this.user)))); + description.add(this.user.getTranslation(reference + "entity", "[tag]", + Utils.prettifyObject(material, this.user)))); } icon = new ItemStack(Material.LAVA_BUCKET); diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 2ecf3d1d..8d0b3fa4 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -353,14 +353,14 @@ challenges: remove_ignored_meta: name: "&f&l Remove Ignore Metadata" description: |- - &7 Allows to remove which - &7 items should ignore - &7 any meta-data that - &7 is assigned to them. + &7 Remove + &7 items that ignore + &7 meta-data + &7 assigned to them. remove_experience: name: "&f&l Remove Experience" description: |- - &7 Allows to toggle if + &7 Toggle if &7 required experience will &7 be removed from player &7 after completing the From 1230869d9ea8c82eb192c3ab037ec43cdd6b3273 Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 6 Feb 2025 11:27:47 -0800 Subject: [PATCH 06/34] Update en-US.yml --- src/main/resources/locales/en-US.yml | 800 +++++++++++---------------- 1 file changed, 318 insertions(+), 482 deletions(-) diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 8d0b3fa4..dae87d9b 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -15,7 +15,6 @@ challenges: all_signs: name: "Any Sign" - commands: admin: main: @@ -25,33 +24,33 @@ challenges: description: "Reload challenges from the database" parameters: '' show: - description: 'Prints all challenges in the chat which exist in this world.' + description: "Displays all challenges in the chat that exist in this world." parameters: '' complete: - description: 'Complete a challenge for a player.' + description: "Completes a challenge for a player." parameters: ' ' reset: - description: 'Reset a challenge for a player. If "challenge_id" is set to "all", then it will reset all challenges.' + description: "Resets a challenge for a player. If \"challenge_id\" is set to \"all\", then all challenges will be reset." parameters: ' ' migrate: - description: 'Migrate current game world challenges data to 0.8.0 storage format.' + description: "Migrates the current game world’s challenge data to the 0.8.0 storage format." parameters: '' user: main: - description: 'Open Challenges GUI.' + description: "Opens the Challenges GUI." parameters: '' complete: - description: 'Complete challenge.' + description: "Completes a challenge." parameters: ' [count]' gui: titles: # The title for the Main GUI player-gui: "&0&l Challenges Menu" # The title for the Main GUI - gamemode-gui: "&0&l Select GameMode" + gamemode-gui: "&0&l Select Game Mode" # The title for the Multiple Completion GUI multiple-gui: "&0&l How many times?" - # GUI titles below is visible just for Admins. + # GUI titles below are visible just for Admins. admin-gui: "&0&l Challenges Admin Menu" edit-challenge: "&0&l Edit [challenge]" edit-level: "&0&l Edit [level]" @@ -82,23 +81,23 @@ challenges: return: name: "&f&l Return" description: |- - &7 Return to previous menu - &7 or exit GUI + &7 Return to the previous menu + &7 or exit the GUI # Button that is used in multi-page GUIs which allows to return to previous page. previous: name: "&f&l Previous Page" description: |- - &7 Switch to &e [number] &7 page + &7 Switch to page &e [number] # Button that is used in multi-page GUIs which allows to go to next page. next: name: "&f&l Next Page" description: |- - &7 Switch to &e [number] &7 page + &7 Switch to page &e [number] # Button that allows to reduce number reduce: name: "&f&l Reduce" description: |- - &7 Reduce by &e [number] + &7 Decrease by &e [number] # Button that allows to increase number increase: name: "&f&l Increase" @@ -109,24 +108,23 @@ challenges: name: "&f&l Complete" description: |- &7 Complete challenge &e [number] - &7 time(-s) + &7 time(s) # Button that allows to quit the current gui. quit: name: "&f&l Quit" description: |- - &7 Exit from the GUI. + &7 Exit the GUI. # All following buttons are mainly for Admin GUI. complete_user_challenges: name: "&f&l Complete User Challenge(-s)" description: |- - &7 Allows to choose user and - &7 complete challenge(-s) for - &7 him + &7 Allows you to select a user and + &7 complete challenge(s) for them. reset_user_challenges: name: "&f&l Reset User Challenges" description: |- - &7 Allows to choose user and - &7 reset his challenges + &7 Allows you to select a user and + &7 reset their challenges. add_challenge: name: "&f&l Create Challenge" description: |- @@ -140,63 +138,63 @@ challenges: edit_challenge: name: "&f&l Edit Challenge" description: |- - &7 Allows to choose and edit + &7 Allows you to select and edit &7 a challenge. edit_level: name: "&f&l Edit Level" description: |- - &7 Allows to choose and edit + &7 Allows you to select and edit &7 a level. delete_challenge: name: "&f&l Delete Challenge" description: |- - &7 Allows to choose and delete + &7 Allows you to select and delete &7 a challenge. delete_level: name: "&f&l Delete Level" description: |- - &7 Allows to choose and delete + &7 Allows you to select and delete &7 a level. edit_settings: name: "&f&l Settings" description: |- - &7 Allows to view and edit - &7 an addon settings. + &7 Allows you to view and edit + &7 the add-on settings. complete_wipe: name: "&f&l Complete Wipe" description: |- - &7 Completely clears challenges - &7 addon database, including + &7 Completely clears the challenges + &7 add-on database, including &7 user data. challenge_wipe: name: "&f&l Challenge Wipe" description: |- - &7 Completely clears challenges - &7 and levels from database. + &7 Completely clears the challenges + &7 and levels from the database. user_wipe: name: "&f&l User Wipe" description: |- &7 Completely clears user - &7 data from database. + &7 data from the database. library: name: "&f&l Library" description: |- - &7 Opens a public + &7 Opens the public &7 challenges library. import_database: name: "&f&l Import Database" description: |- - &7 Allows to import exported + &7 Allows you to import an exported &7 challenges database. import_template: name: "&f&l Import Template" description: |- - &7 Allows to import template - &7 file with challenges. + &7 Allows you to import a template + &7 file containing challenges. export_challenges: name: "&f&l Export Challenges" description: |- - &7 Allows to export database + &7 Allows you to export the database &7 to a local file. properties: name: "&f&l Properties" @@ -205,376 +203,316 @@ challenges: requirements: name: "&f&l Requirements" description: |- - &7 View requirements properties. + &7 View the requirements properties. rewards: name: "&f&l Rewards" description: |- - &7 View rewards properties. + &7 View the rewards properties. deployed: name: "&f&l Deployment" description: |- - &7 Toggle if challenge is - &7 deployed and users can - &7 complete it. + &7 Toggle whether the challenge is + &7 deployed and can be completed by users. enabled: "&2 Enabled" disabled: "&c Disabled" name: name: "&f&l Name" description: |- - &7 Allows to change + &7 Allows you to change &7 the display name. value: "&7 Currently: &r [name]" remove_on_complete: name: "&f&l Hide After Completion" description: |- - &7 Toggle if challenge should - &7 hidden from player after + &7 Toggle whether the challenge should + &7 be hidden from the player after &7 it is completed. enabled: "&2 Enabled" disabled: "&c Disabled" description: name: "&f&l Description" description: |- - &7 The specific description - &7 for the challenge. The color - &7 codes must be applied to it. + &7 Specifies the challenge's description. + &7 Color codes must be applied to it. value: "&7 Current description:" environment: name: "&f&l Dimension" description: |- - &7 Allows to limit in which - &7 dimension the challenge - &7 can be completed. + &7 Allows you to restrict the challenge + &7 to a specific dimension. enabled: "&2" disabled: "&c" order: name: "&f&l Order" description: |- - &7 Allows to change order of + &7 Allows you to change the order of &7 objects. - &7 Objects with equal numbers + &7 Objects with the same number &7 will be ordered by their - &7 unique id names. + &7 unique ID names. value: "&7 Current order: &e [number]" icon: name: "&f&l Icon" description: |- - &7 Allows to change icon + &7 Allows you to change the icon &7 for this challenge. locked_icon: name: "&f&l Locked Icon" description: |- - &7 Allows to change locked + &7 Allows you to change the locked &7 level icon. required_permissions: name: "&f&l Required Permissions" description: |- - &7 Allows to change required - &7 permissions for this - &7 challenge to be completable. + &7 Allows you to change the required + &7 permissions for this challenge to be completed. title: "&7 Permissions: " permission: " &8 - [permission]" - none: "&7 Permissions are not set." + none: "&7 No permissions are set." remove_entities: name: "&f&l Remove Entities" description: |- - &7 Allows to toggle if - &7 required entities will - &7 be removed from world - &7 after completing the - &7 challenge. + &7 Toggle whether required entities will + &7 be removed from the world after completing + &7 the challenge. enabled: "&2 Enabled" disabled: "&c Disabled" required_entities: name: "&f&l Required Entities" description: |- - &7 Allows to change required - &7 entities for this - &7 challenge to be completable. + &7 Allows you to change the required + &7 entities for this challenge to be completed. title: "&7 Entities: " list: " &8 - [number] x [entity]" - none: "&7 Entities are not added." + none: "&7 No entities have been added." remove_blocks: name: "&f&l Remove Blocks" description: |- - &7 Allows to toggle if - &7 required blocks will - &7 be removed from world - &7 after completing the - &7 challenge. + &7 Toggle whether required blocks will + &7 be removed from the world after completing + &7 the challenge. enabled: "&2 Enabled" disabled: "&c Disabled" required_blocks: name: "&f&l Required Blocks" description: |- - &7 Allows to change required - &7 blocks for this - &7 challenge to be completable. + &7 Allows you to change the required + &7 blocks for this challenge to be completed. title: "&7 Blocks: " list: " &8 - [number] x [block]" - none: "&7 Blocks are not added." + none: "&7 No blocks have been added." search_radius: name: "&f&l Search Radius" description: |- - &7 Allows to change the radius - &7 around player from which - &7 blocks and/or entities are - &7 detected. + &7 Allows you to change the radius + &7 around the player from which + &7 blocks and/or entities are detected. value: "&7 Current distance: &e [number]" remove_items: name: "&f&l Remove Items" description: |- - &7 Allows to toggle if - &7 required items will - &7 be removed from inventory - &7 after completing the - &7 challenge. + &7 Toggle whether required items will + &7 be removed from the inventory after completing + &7 the challenge. enabled: "&2 Enabled" disabled: "&c Disabled" required_items: name: "&f&l Required Items" description: |- - &7 Allows to change required - &7 items for this - &7 challenge to be completable. + &7 Allows you to change the required + &7 items for this challenge to be completed. title: "&7 Items: " list: " &8 - [number] x [item]" - none: "&7 Items are not added." + none: "&7 No items have been added." add_ignored_meta: name: "&f&l Add Ignore Metadata" description: |- - &7 Allows to add which - &7 items should ignore - &7 any meta-data that - &7 is assigned to them. + &7 Allows you to specify which items should ignore + &7 any metadata assigned to them. title: "&7 Items: " list: " &8 - [number] x [item]" - none: "&7 Items are not added." + none: "&7 No items have been added." remove_ignored_meta: name: "&f&l Remove Ignore Metadata" description: |- - &7 Remove - &7 items that ignore - &7 meta-data + &7 Removes items that ignore metadata &7 assigned to them. remove_experience: name: "&f&l Remove Experience" description: |- - &7 Toggle if - &7 required experience will - &7 be removed from player - &7 after completing the - &7 challenge. + &7 Toggle whether the required experience will + &7 be removed from the player after completing + &7 the challenge. enabled: "&2 Enabled" disabled: "&c Disabled" required_experience: name: "&f&l Required Experience" description: |- - &7 Allows to change the - &7 required experience for + &7 Allows you to change the required experience for &7 the player. value: "&7 Current experience: &e [number]" required_level: name: "&f&l Required Island Level" description: |- - &7 Allows to change the - &7 required island level + &7 Allows you to change the required island level &7 for the challenge. value: "&7 Current level: &e [number]" required_materialtags: name: "&f&l Required Block Groups" description: |- - &7 Allows to change required - &7 block groups for this - &7 challenge to be completable. + &7 Allows you to change the required + &7 block groups for this challenge to be completed. title: "&7 Block group: " list: " &8 - [number] x [tag]" - none: "&7 Block groups are not added." + none: "&7 No block groups have been added." required_entitytags: name: "&f&l Required Entity Groups" description: |- - &7 Allows to change required - &7 entity groups for this - &7 challenge to be completable. + &7 Allows you to change the required + &7 entity groups for this challenge to be completed. title: "&7 Entity group: " list: " &8 - [number] x [tag]" - none: "&7 Entity groups are not added." + none: "&7 No entity groups have been added." remove_money: name: "&f&l Remove Money" description: |- - &7 Allows to toggle if - &7 required money will - &7 be removed from player - &7 account after completing + &7 Toggle whether the required money will + &7 be removed from the player's account after completing &7 the challenge. enabled: "&2 Enabled" disabled: "&c Disabled" required_money: name: "&f&l Required Money" description: |- - &7 Allows to change the - &7 required money on player + &7 Allows you to change the required money on the player's &7 account for the challenge. value: "&7 Current value: &e [number]" statistic: name: "&f&l Statistic" description: |- - &7 Allows to change the - &7 statistic type that is - &7 checked in this challenge. + &7 Allows you to change the statistic type that is + &7 checked for this challenge. value: "&7 Current value: &e [statistic]" statistic_amount: name: "&f&l Target Value" description: |- - &7 Allows to change the - &7 statistic target value + &7 Allows you to change the target statistic value &7 that must be met. value: "&7 Current value: &e [number]" remove_statistic: name: "&f&l Reduce Statistic" description: |- - &7 Allows to toggle if - &7 statistic value will - &7 be reduced after completing - &7 the challenge. + &7 Toggle whether the statistic value will + &7 be reduced after completing the challenge. enabled: "&2 Enabled" disabled: "&c Disabled" statistic_blocks: name: "&f&l Target Block" description: |- - &7 Allows to change the - &7 statistic target block. + &7 Allows you to change the target block for the statistic. value: "&7 Current block: &e [block]" statistic_items: name: "&f&l Target Item" description: |- - &7 Allows to change the - &7 statistic target item. + &7 Allows you to change the target item for the statistic. value: "&7 Current item: &e [item]" statistic_entities: name: "&f&l Target Entity" description: |- - &7 Allows to change the - &7 statistic target entity. + &7 Allows you to change the target entity for the statistic. value: "&7 Current entity: &e [entity]" reward_text: name: "&f&l Reward Text" description: |- - &7 The specific reward text. - &7 The color codes must be - &7 applied to it. + &7 Specifies the reward text. + &7 Color codes must be applied. value: "&7 Current text:" repeat_reward_text: name: "&f&l Repeat Reward Text" description: |- - &7 The specific repeat reward text - &7 for the challenge. The color - &7 codes must be applied to it. + &7 Specifies the repeat reward text for the challenge. + &7 Color codes must be applied. value: "&7 Current text:" reward_items: name: "&f&l Reward Items" description: |- - &7 Allows to change reward - &7 items. + &7 Allows you to change the reward items. title: "&7 Items: " list: " &8 - [number] x [item]" - none: "&7 Items are not added." + none: "&7 No items have been added." repeat_reward_items: name: "&f&l Repeat Reward Items" description: |- - &7 Allows to change repeat - &7 reward items for this - &7 challenge. + &7 Allows you to change the repeat reward items for this challenge. title: "&7 Items: " list: " &8 - [number] x [item]" - none: "&7 Items are not added." + none: "&7 No items have been added." reward_experience: name: "&f&l Reward Experience" description: |- - &7 Allows to change the - &7 reward experience for + &7 Allows you to change the reward experience for &7 the player. value: "&7 Reward experience: &e [number]" repeat_reward_experience: name: "&f&l Repeat Reward Experience" description: |- - &7 Allows to change the - &7 repeat reward experience + &7 Allows you to change the repeat reward experience &7 for the player. value: "&7 Reward experience: &e [number]" reward_money: name: "&f&l Reward Money" description: |- - &7 Allows to change the - &7 reward money. + &7 Allows you to change the reward money. value: "&7 Current value: &e [number]" repeat_reward_money: name: "&f&l Repeat Reward Money" description: |- - &7 Allows to change the - &7 repeat reward money + &7 Allows you to change the repeat reward money &7 for the challenge. value: "&7 Current value: &e [number]" reward_commands: name: "&f&l Reward Commands" description: |- - &7 The specific reward commands. + &7 Specifies the reward commands. &8 Tip: - &8 The command does not requires - &8 writing first `/` as it will - &8 be applied automatically. - &8 By default commands will be - &8 executed by server. However - &8 adding `[SELF]` at the start - &8 will allow command to be - &8 executed by player. It also - &8 supports one placeholder - &8 `[player]` that will be - &8 replaced with a player name - &8 who completed challenge. + &8 The command does not require a leading `/` as it is + &8 applied automatically. + &8 By default, commands are executed by the server. + &8 However, adding `[SELF]` at the beginning will cause + &8 the command to be executed by the player. + &8 It also supports the placeholder `[player]` which will be + &8 replaced with the name of the player who completed the challenge. value: "&7 Current commands:" repeat_reward_commands: name: "&f&l Repeat Reward Commands" description: |- - &7 The specific repeat reward - &7 commands for the challenge. + &7 Specifies the repeat reward commands for the challenge. &8 Tip: - &8 The command does not requires - &8 writing first `/` as it will - &8 be applied automatically. - &8 By default commands will be - &8 executed by server. However - &8 adding `[SELF]` at the start - &8 will allow command to be - &8 executed by player. It also - &8 supports one placeholder - &8 `[player]` that will be - &8 replaced with a player name - &8 who completed challenge. + &8 The command does not require a leading `/` as it is + &8 applied automatically. + &8 By default, commands are executed by the server. + &8 However, adding `[SELF]` at the beginning will cause + &8 the command to be executed by the player. + &8 It also supports the placeholder `[player]` which will be + &8 replaced with the name of the player who completed the challenge. value: "&7 Current commands:" repeatable: name: "&f&l Repeatable" description: |- - &7 Allows to toggle if - &7 the challenge is - &7 repeatable. + &7 Toggle whether the challenge is repeatable. enabled: "&2 Enabled" disabled: "&c Disabled" repeat_count: name: "&f&l Repeat Count" description: |- - &7 Allows to change the - &7 number of repeats + &7 Allows you to change the number of repeats &7 for the challenge. value: "&7 Current value: &e [number]" cool_down: name: "&f&l Cool Down" description: |- - &7 Allows to change the - &7 cool down seconds that - &7 must be waited between - &7 repeatable challenge - &7 completions. + &7 Allows you to change the cooldown period (in seconds) + &7 that must elapse between repeatable challenge completions. value: "&7 Current value: &e [time]" challenges: name: "&f&l Challenges" @@ -584,128 +522,98 @@ challenges: waiver_amount: name: "&f&l Waiver Amount" description: |- - &7 Allows to set a number - &7 of challenges that can - &7 be left uncompleted for - &7 unlocking next level. + &7 Allows you to set the number of challenges that can + &7 be left uncompleted to unlock the next level. value: "&7 Current value: &e [number]" add_challenges: name: "&f&l Add Challenge(-s)" description: |- - &7 Allows to select and - &7 adds challenges to the - &7 level. + &7 Allows you to select and add challenges to the level. remove_challenges: name: "&f&l Remove Challenge(-s)" description: |- - &7 Allows to select and - &7 remove challenges to the - &7 level. + &7 Allows you to select and remove challenges from the level. reset_on_new: name: "&f&l Reset On New" description: |- - &7 Allows to toggle if - &7 challenges should be - &7 reset when user leaves - &7 island or creates a new - &7 island. + &7 Toggle whether challenges should be reset when a user leaves + &7 their island or creates a new one. enabled: "&2 Enabled" disabled: "&c Disabled" broadcast: name: "&f&l Broadcast" description: |- - &7 Broadcasts challenge and - &7 level first time completion - &7 to everyone. + &7 Broadcasts the first-time completion of a challenge or level to everyone. enabled: "&2 Enabled" disabled: "&c Disabled" remove_completed: name: "&f&l Hide Completed" description: |- - &7 Hides compleated challenges - &7 from the menu. + &7 Hides completed challenges from the menu. enabled: "&2 Enabled" disabled: "&c Disabled" glow_completed: name: "&f&l Glow Completed" description: |- - &7 Adds enchantment glow - &7 to the completed challenges. + &7 Applies an enchantment glow to completed challenges. enabled: "&2 Enabled" disabled: "&c Disabled" store_history: name: "&f&l Store History" description: |- - &7 Stores internal history - &7 when each challenge is - &7 completed. - &7 Currently viewable only - &7 in the database. + &7 Stores internal history each time a challenge is completed. + &7 Currently viewable only in the database. enabled: "&2 Enabled" disabled: "&c Disabled" data_per_island: name: "&f&l Store Per Island" description: |- - &7 Stores the completed - &7 challenges per island. - &7 Progress will be shared - &7 with all players in team. + &7 Stores completed challenges on a per-island basis. + &7 Progress will be shared with all team members. enabled: "&2 Enabled" disabled: "&c Disabled" show_title: name: "&f&l Show Title" description: |- - &7 Shows title when a - &7 challenge or level - &7 is completed. + &7 Displays a title when a challenge or level is completed. enabled: "&2 Enabled" disabled: "&c Disabled" gamemode_gui: name: "&f&l GameMode Selection GUI" description: |- - &7 Enables single GUI that - &7 is available via /challenges - &7 command. - &c Requires server restart. + &7 Enables a single GUI accessible via the /challenges command. + &7 (Requires server restart.) enabled: "&2 Enabled" disabled: "&c Disabled" locked_level_icon: name: "&f&l Default Locked Level Icon" description: |- - &7 Default icon for all locked - &7 levels. Each level can change - &7 this icon. + &7 Default icon for all locked levels. + &7 Each level can override this icon. purge_history: name: "&f&l History Lifetime" description: |- - &7 Number of days how long - &7 history data is stored + &7 Specifies the number of days that history data is stored &7 in user data. - &7 0 means that data will - &7 not be removed. + &7 A value of 0 means the data will not be removed. value: "&7 Current value: &e [number]" title_showtime: name: "&f&l Title Showtime" description: |- - &7 Number of ticks that title - &7 will be showed to the player. + &7 Number of ticks the title will be displayed to the player. value: "&7 Current value: &e [number]" active_world_list: name: "&f&l Show Only Active World" description: |- - &7 If GameMode Selection GUI - &7 is enabled, this can switch - &7 if GUI shows GameMode selection - &7 or challenges for current world. - &c Requires server restart. + &7 If the GameMode Selection GUI is enabled, this setting determines + &7 whether the GUI displays the GameMode selection or challenges for the current world. + &7 (Requires server restart.) enabled: "&2 Enabled" disabled: "&c Disabled" visibility_mode: name: "&f&l Visibility Mode" description: |- - &7 Visibility Mode for - &7 challenges that are - &7 hiddend. + &7 Visibility mode for challenges that are hidden. enabled: "&2" disabled: "&c" visible: "Show visible challenges" @@ -714,17 +622,13 @@ challenges: include_undeployed: name: "&f&l Include Undeployed Challenges" description: |- - &7 Indicates if undeployed - &7 challenges should be - &7 counted towards level - &7 completion. + &7 Indicates whether undeployed challenges should be counted towards level completion. enabled: "&2 Enabled" disabled: "&c Disabled" download: name: "&f&l Download Libraries" description: |- - &7 Manually update available - &7 challenges libraries. + &7 Manually updates the available challenges libraries. enabled: "&2 With cache clear" disabled: "&c Without cache clear" player: @@ -734,13 +638,13 @@ challenges: members: "&7 Island Members:" member: "&8 - [name]" no-island: |- - &c Player does not have + &c This player does not have &c an island. player_list: name: "&f&l Choose User List" description: |- &7 Choose which user list - &7 should be showed. + &7 should be displayed. enabled: "&2" disabled: "&c" online: "Online Players" @@ -749,27 +653,27 @@ challenges: add_block: name: "&f&l Add Block" description: |- - &7 Allows to add a new + &7 Allows you to add a new &7 block to the list. add_block_group: name: "&f&l Add Block Group" description: |- - &7 Allows to add a new + &7 Allows you to add a new &7 block group to the list. remove_block: name: "&f&l Remove Block" description: |- - &7 Allows to remove + &7 Allows you to remove &7 selected blocks - &7 from lists. + &7 from the list. title: "&7 Selected Materials:" material: "&8 - [material]" remove_block_group: name: "&f&l Remove Block Group" description: |- - &7 Allows to remove + &7 Allows you to remove &7 selected block groups - &7 from lists. + &7 from the list. title: "&7 Selected Block Groups:" material: "&8 - [tag]" material: @@ -784,32 +688,32 @@ challenges: add_entity: name: "&f&l Add Entity" description: |- - &7 Allows to add a new + &7 Allows you to add a new &7 entity to the list. add_entity_group: name: "&f&l Add Entity Group" description: |- - &7 Allows to add a new + &7 Allows you to add a new &7 entity group to the list. switch_entity: name: "&f&l Switch Eggs" description: |- - &7 Allows to switch from - &7 eggs to the mob heads. + &7 Allows you to switch from + &7 eggs to mob heads. remove_entity: name: "&f&l Remove Entity" description: |- - &7 Allows to remove + &7 Allows you to remove &7 selected entities - &7 from lists. + &7 from the list. title: "&7 Selected Entities:" entity: "&8 - [entity]" remove_entity_group: name: "&f&l Remove Entity Group" description: |- - &7 Allows to remove - &7 selected entity group - &7 from lists. + &7 Allows you to remove + &7 selected entity groups + &7 from the list. title: "&7 Selected Entity Groups:" entity: "&8 - [tag]" entity: @@ -818,31 +722,26 @@ challenges: &7 Entity ID: [id] selected: "&2 Selected" entity-group: - name: "&f&l [tag]" - description: "" - selected: "&2 Selected" + name: "&f&l [tag]" + description: "" + selected: "&2 Selected" inventory_type: name: "&f&l Inventory Type" description: |- - &7 Challenge that checks - &7 items in player inventory + &7 Challenge that checks items in the player's inventory. island_type: name: "&f&l Island Type" description: |- - &7 Challenge that checks - &7 blocks or entities around - &7 player. + &7 Challenge that checks blocks or entities around the player. other_type: name: "&f&l Other Type" description: |- - &7 Challenge that uses - &7 plugins or addons things, - &7 like level and money. + &7 Challenge that utilizes plugins or add-on features, + &7 such as level and money. statistic_type: name: "&f&l Statistic Type" description: |- - &7 Challenge that checks - &7 player statistic data. + &7 Challenge that checks the player's statistic data. save: name: "&f&l Save" description: |- @@ -856,8 +755,8 @@ challenges: accept_selected: name: "&f&l Accept Selected" description: |- - &7 Returns selected elements - &7 and opens previous GUI. + &7 Returns the selected elements + &7 and reopens the previous GUI. title: "&7 Selected: " element: "&8 - [element]" statistic_element: @@ -869,15 +768,14 @@ challenges: search: name: "&f&l Search" description: |- - &7 Allows to search an - &7 element with input - &7 text value. + &7 Allows you to search for an + &7 element using a text input value. search: "&b Value: [value]" tips: click-to-select: "&e Click &7 to select." click-to-choose: "&e Click &7 to choose." click-to-complete: "&e Click &7 to complete." - right-click-multiple-open: "&e Right Click &7 to chose completion count." + right-click-multiple-open: "&e Right Click &7 to choose a completion count." shift-left-click-to-complete-all: "&e Shift Click &7 to complete all." left-click-to-accept: "&e Left Click &7 to complete." right-click-to-write: "&e Right Click &7 to write." @@ -892,7 +790,7 @@ challenges: click-to-export: "&e Click &7 to export." click-to-create: "&e Click &7 to create." left-click-to-open: "&e Left Click &7 to open." - right-click-to-reset-all: "&e Right Click &7 to wipe all." + right-click-to-reset-all: "&e Right Click &7 to reset all." click-to-toggle: "&e Click &7 to toggle." click-to-change: "&e Click &7 to change." shift-click-to-reset: "&e Shift Click &7 to reset." @@ -912,7 +810,7 @@ challenges: click-to-save: "&e Click &7 to save." click-to-deselect: "&e Click &7 to deselect." click-on-item: |- - &e Click &7 on item in + &e Click &7 on an item in &7 your inventory. left-click-to-edit: "&e Left Click &7 to edit." right-click-to-clear: "&e Right Click &7 to clear." @@ -934,7 +832,7 @@ challenges: # Status message for completed unrepeatable challenge completed: "&2&l Completed" # Status message that contains number of completions for unlimited repeatable challenge - completed-times: "&2 Completed &7&l [number] &r&2 time(-s)" + completed-times: "&2 Completed &7&l [number] &r&2 time(s)" # Status message that contains number of completions from max available for repeatable challenge completed-times-of: "&2 Completed &7&l [number] &r&2 out of &7&l [max] &r&2 times" # Status message that indicates that max completion count reached for repeatable challenge @@ -944,9 +842,9 @@ challenges: lore: |- [timeout] [wait-time] - # Text message that shows challenges timeout. + # Text message that shows challenge timeout. timeout: "&7&l Cool down: &r&7 [time]" - # Text message that shows challenges wait time if it is larger than 0. + # Text message that shows challenge wait time if it is larger than 0. wait-time: "&c&l Available after: &r&c [time]" # Text message that replaces days if number > 1 in-days: "[number] d " @@ -969,7 +867,7 @@ challenges: # Message that will be added after environment-title-multiple. environment-list: " &7 - &e [environment]" # Message that will replace [permissions] placeholder if there is just a single permission. - permission-single: "&c Requires [permission] permission" + permission-single: "&c Requires [permission] permission." # Message that will replace [permissions] placeholder if there are multiple permissions. permissions-title: "&c Requires permissions: " # Message that will be added after permissions-title-multiple. @@ -982,19 +880,19 @@ challenges: [search-radius] [warning-block] [warning-entity] - # Title that will be used if there are defined blocks in island challenge + # Title that will be used if there are defined blocks in an island challenge blocks-title: "&7&l Required Blocks:" # Listing of blocks that are required on the island. block-value: " &7 - &e [material]" blocks-value: " &7 - &e [number] x [material]" - # Title that will be used if there are defined entities in island challenge + # Title that will be used if there are defined entities in an island challenge entities-title: "&7&l Required Entities:" # Listing of entities that are required on the island. entity-value: " &7 - &e [entity]" entities-value: " &7 - &e [number] x [entity]" # Search radius for the blocks/entities - search-radius: "&7 Not further than &e [number] &7 meters" - # Waning about block/entity removing + search-radius: "&7 No further than &e [number] &7 meters" + # Warning about block/entity removal warning-block: "&e Blocks will be &c removed" warning-entity: "&e Entities will be &c removed" # Message that will generate for inventory type requirements and replace [type-requirements] @@ -1002,13 +900,13 @@ challenges: lore: |- [items] [warning] - # Title that will be used if there are list of items for challenge + # Title that will be used if there is a list of items for the challenge item-title: "&7&l Required Items:" - # Listing of an item that are required multiple times. + # Listing of an item that is required once or multiple times. item-value: " &7 - &e [item]" items-value: " &7 - &e [number] x [item]" # Warning that items will be removed - warning: "&e Item(-s) will be &c removed" + warning: "&e Item(s) will be &c removed" # Message that will generate for other type requirements and replace [type-requirements] other: lore: |- @@ -1027,22 +925,21 @@ challenges: money-warning: "&e Money will be &c removed" # Text for required island level level: "&7&l Required island level: &r&e [number]" - # Message that will generate for statistic type requirements and replace [type-requirements] + # Message that will generate for statistic type requirements and replace [type-requirement] statistic: lore: |- [statistic] [warning] # Type of statistic for multiple target counter. Target may be entity or material/block multiple-target: "&7&l [statistic]: &r&e [number] x [target]" - # Type of statistic for single target. Target may be entity or material/block + # Type of statistic for a single target. Target may be entity or material/block single-target: "&7&l [statistic]: &r&e [target]" - # Type of statistic without entity/block target + # Type of statistic without an entity/block target statistic: "&7&l [statistic] &r&e [number]" - # Warning that statistic will be removed + # Warning that statistic will be reduced warning: "&e Statistic data will be &c reduced" # Contains a text generated inside [rewards] lore rewards: - # [text] comes from challenge.rewardText and challenge.repeatRewardText lore: |- &7&l Rewards: [text] @@ -1050,9 +947,9 @@ challenges: [experience] [money] [commands] - # Title that will be used if there are list of items for rewards + # Title that will be used if there is a list of items for rewards item-title: "&7 Items:" - # Listing of an item that are rewards multiple times. + # Listing of an item that is rewarded multiple times. item-value: " &7 - &e [item]" items-value: " &7 - &e [number] x [item]" # Text for reward experience @@ -1070,27 +967,25 @@ challenges: [status] [waiver] [rewards] - # Status is either challengeLevel.unlockMessage or current status of the level + # Status is either challengeLevel.unlockMessage or the current status of the level status: - # Status message for completed unrepeatable challenge + # Status message for a completed unrepeatable challenge completed: "&2&l Completed" - # Status message that contains number of completed challenges from all challenges + # Status message that contains the number of completed challenges out of all challenges completed-challenges-of: |- - &2 Completed &7&l [number] &r&2 out of - &7&l [max] &r&2 challenges. - # Status message for locked level + &2 Completed &7&l [number] &r&2 out of &7&l [max] &r&2 challenges. + # Status message for a locked level locked: "&c&l Locked" - # Status message for locked level that will show missing challenge count. + # Status message for a locked level that will show the missing challenge count. missing-challenges: |- - &7 [number] more challenges must be + &7 [number] more challenges need to be &7 completed to unlock this level. - # Contains a text for waiver amount to unlock next level + # Contains text for the waiver amount to unlock the next level waiver: |- - &7&l [number] challenge(-s) &r&7 can be - &7 skipped to unlock next level. - # Contains a text generated inside [rewards] lore + &7&l [number] challenge(s) can be + &7 skipped to unlock the next level. + # Contains text generated inside [rewards] lore rewards: - # [text] comes from challengeLevel.rewardText lore: |- &7&l Rewards: [text] @@ -1098,185 +993,126 @@ challenges: [experience] [money] [commands] - # Title that will be used if there are list of items for rewards item-title: "&7 Items:" - # Listing of an item that are rewards single time. item-value: " &7 - &e [item]" items-value: " &7 - &e [number] x [item]" - # Text for reward experience experience: "&7 Experience: &r&e [number]" - # Text for reward money money: "&7 Money: &r&e [number]" - # Title for commands listing: commands-title: "&7 Commands:" - # Command listing element command: " &7 - &e [command]" # This part generates description for the Library Entry library: - author: '&7 by &e [author]' - version: '&7 Made with Challenges &e [version]' - lang: '&7 Language: &e [lang]' - gamemode: '&7 Primary for &e [gamemode]' + author: "&7 by &e [author]" + version: "&7 Made with Challenges &e [version]" + lang: "&7 Language: &e [lang]" + gamemode: "&7 Primary for &e [gamemode]" conversations: - # Prefix for messages that are send from server. prefix: "&l&6 [BentoBox]: &r" - # List of strings that are valid for confirming input. (separated with ,) confirm-string: "true, on, yes, confirm, y, valid, correct" - # List of strings that are valid for denying input. (separated with ,) deny-string: "false, off, no, deny, n, invalid, incorrect" - # String that allows to cancel conversation. (can be only one) cancel-string: "cancel" - # List of strings that allows to exit conversation. (separated with ,) exit-string: "cancel, exit, quit" - # Message that is send to user when conversation is cancelled. cancelled: "&c Conversation cancelled!" - # Message that appears when admin clicks on number editing button. - input-number: "&e Please enter a number in chat." - # Message that appears when admin clicks on seconds editing button. - input-seconds: "&e Please enter a seconds in chat." - # Error message that is showed if user input a value that is not a number. + input-number: "&e Please enter a number in the chat." + input-seconds: "&e Please enter a number of seconds in the chat." numeric-only: "&c The given [value] is not a number!" - # Error message that is showed if user input a number that is smaller or larger that allowed. - not-valid-value: "&c The given number [value] is not valid. It must be larger than [min] and smaller than [max]!" - # Message that confirms user data removing. - user-data-removed: "&a All user data for [gamemode] is cleared from the database." - # Message that asks confirmation for user data removing. - confirm-user-data-deletion: "&e Please confirm that you want to clear user database for [gamemode]." - # Message that confirms user data removing. - challenge-data-removed: "&a All challenges data for [gamemode] is cleared from the database." - # Message that asks confirmation for user data removing. - confirm-challenge-data-deletion: "&e Please confirm that you want to clear challenges database for [gamemode]." - # Message that confirms user data removing. - all-data-removed: "&a All addon data for [gamemode] is cleared from the database." - # Message that asks confirmation for user data removing. - confirm-all-data-deletion: "&e Please confirm that you want to clear addon data for [gamemode]." - # Message that asks user to write a name - write-name: "&e Please write a name in the chat." - # Message that confirms new object creation. - new-object-created: "&a New object for [gamemode] is created." - # Error message that sends that object cannot be created with a given name - object-already-exists: "&c Object &7 [id] &c already exists. Choose different name." - # Error message that sends information that challenge cannot be deployed. - invalid-challenge: "&c Challenge [challenge] contains invalid data. It cannot be deployed!" - # Message that confirms name changing - name-changed: "&a Success, the name was updated." - # Message that appears after clicking - write-description: "&e Please enter a new description in chat and 'quit' on a line by itself to finish." - # Message that appears after successful description change. - description-changed: "&a Success, the description was updated." - # Message that appears when admin clicks on permission editing button. - write-permissions: "&e Please enter the required permissions, one per line in chat, and 'quit' on a line by itself to finish." - # Message that appears after successful permission updating. - permissions-changed: "&a Success, challenge permissions were updated." - # Message that appears after clicking - write-reward-text: "&e Please enter a new reward text in chat and 'quit' on a line by itself to finish." - # Message that appears after successful reward-text change. - reward-text-changed: "&a Success, the reward text was updated." - # Message that appears after clicking - write-repeat-reward-text: "&e Please enter a new repeat reward text in chat and 'quit' on a line by itself to finish." - # Message that appears after successful repeat-reward-text change. - repeat-reward-text-changed: "&a Success, the repeat reward text was updated." - # Message that appears after clicking - write-reward-commands: "&e Please enter a new reward command per line in chat and 'quit' on a line by itself to finish." - # Message that appears after successful commands-text change. - reward-commands-changed: "&a Success, the reward commands was updated." - # Message that appears after clicking - write-repeat-reward-commands: "&e Please enter a new repeat reward command per line in chat and 'quit' on a line by itself to finish." - # Message that appears after successful repeat-commands-text change. - repeat-reward-commands-changed: "&a Success, the repeat reward commands was updated." - # Message that confirms user data removing. - challenge-removed: "&a Challenges [challenge] for [gamemode] is removed from the database." - # Message that asks confirmation for user data removing. - confirm-challenge-deletion: "&e Please confirm that you want to remove [challenge] for [gamemode] from database." - # Message that confirms user data removing. - level-removed: "&a Level [level] for [gamemode] is removed from the database." - # Message that asks confirmation for user data removing. - confirm-level-deletion: "&e Please confirm that you want to remove [level] for [gamemode] from database." - # Message that appears when user clicks on library installation. - start-downloading: "&a Starting to download and import Challenges Library." - # Message that appears when writing multiline text. + not-valid-value: "&c The given number [value] is not valid. It must be greater than [min] and less than [max]!" + user-data-removed: "&a All user data for [gamemode] has been cleared from the database." + confirm-user-data-deletion: "&e Please confirm that you want to clear the user database for [gamemode]." + challenge-data-removed: "&a All challenge data for [gamemode] has been cleared from the database." + confirm-challenge-data-deletion: "&e Please confirm that you want to clear the challenges database for [gamemode]." + all-data-removed: "&a All add-on data for [gamemode] has been cleared from the database." + confirm-all-data-deletion: "&e Please confirm that you want to clear the add-on data for [gamemode]." + write-name: "&e Please enter a name in the chat." + new-object-created: "&a A new object for [gamemode] has been created." + object-already-exists: "&c Object &7 [id] &c already exists. Please choose a different name." + invalid-challenge: "&c Challenge [challenge] contains invalid data and cannot be deployed!" + name-changed: "&a Success: the name has been updated." + write-description: "&e Please enter a new description in the chat. Type 'quit' on a new line to finish." + description-changed: "&a Success: the description has been updated." + write-permissions: "&e Please enter the required permissions, one per line, in the chat. Type 'quit' on a new line to finish." + permissions-changed: "&a Success: the challenge permissions have been updated." + write-reward-text: "&e Please enter a new reward text in the chat. Type 'quit' on a new line to finish." + reward-text-changed: "&a Success: the reward text has been updated." + write-repeat-reward-text: "&e Please enter a new repeat reward text in the chat. Type 'quit' on a new line to finish." + repeat-reward-text-changed: "&a Success: the repeat reward text has been updated." + write-reward-commands: "&e Please enter a new reward command on each line in the chat. Type 'quit' on a new line to finish." + reward-commands-changed: "&a Success: the reward commands have been updated." + write-repeat-reward-commands: "&e Please enter a new repeat reward command on each line in the chat. Type 'quit' on a new line to finish." + repeat-reward-commands-changed: "&a Success: the repeat reward commands have been updated." + challenge-removed: "&a Challenge [challenge] for [gamemode] has been removed from the database." + confirm-challenge-deletion: "&e Please confirm that you want to remove [challenge] for [gamemode] from the database." + level-removed: "&a Level [level] for [gamemode] has been removed from the database." + confirm-level-deletion: "&e Please confirm that you want to remove [level] for [gamemode] from the database." + start-downloading: "&a Starting to download and import the Challenges Library." written-text: "&a Input Text:" - # Message that appears after importing library data into database. - confirm-data-replacement: "&e Please confirm that you want to replace your current challenges with new one." - # Message that appears after successful data importing - new-challenges-imported: "&a Success, new Challenges for [gamemode] were imported." - # Message that appears after admin clicks on database exporting button. - exported-file-name: "&e Please enter a file name for the exported database file. (write 'cancel' to exit)" - # Message that appears after successful database exporting to file. - database-export-completed: "&a Success, the database export for [world] is completed. File [file] generated." - # Message that appears if input file name is already taken. - file-name-exist: "&c File with name '[id]' exists. Cannot overwrite." - # Message that asks for search value input. - write-search: "&e Please write a search value. (write 'cancel' to exit)" - # Message that appears after updating search value. + confirm-data-replacement: "&e Please confirm that you want to replace your current challenges with the new ones." + new-challenges-imported: "&a Success: new challenges for [gamemode] have been imported." + exported-file-name: "&e Please enter a file name for the exported database file. (Type 'cancel' to exit)" + database-export-completed: "&a Success: the database export for [world] is complete. File [file] generated." + file-name-exist: "&c A file named '[id]' already exists. Cannot overwrite." + write-search: "&e Please enter a search value. (Type 'cancel' to exit)" search-updated: "&a Search value updated." titles: - # Title and subtitle may contain variables in [] that will be replaced with a proper message from the challenge object. - # [friendlyName] will be replaced with challenge friendly name. - # [level] will be replaced with level friendly name. - # [rewardText] will be replaced with the challenge reward text. - challenge-title: 'Successfully completed' - challenge-subtitle: '[friendlyName]' - # Title and subtitle may contain variables in [] that will be replaced with a proper message from the level object. - # [friendlyName] will be replaced with level friendly name. - # [rewardText] will be replaced with the level reward text. - level-title: 'Successfully completed' - level-subtitle: '[friendlyName]' + challenge-title: "Successfully completed" + challenge-subtitle: "[friendlyName]" + level-title: "Successfully completed" + level-subtitle: "[friendlyName]" messages: - completed: '&2 You completed challenge [name] for [player]!' - already-completed: '&2 This challenge was already completed!' - reset: '&2 You reset challenge [name] for [player]!' - reset-all: '&2 All [player] challenges were reset!' - not-completed: '&2 This challenge is not completed yet!' - migrate-start: '&2 Start migrating challenges addon data.' - migrate-end: '&2 Challenges addon data updated to new format.' - migrate-not: '&2 All data is valid.' - start-downloading: '&5 Starting to download and import Challenges Library.' - you-completed-challenge: '&2 You completed the [value] &r &2 challenge!' - you-repeated-challenge: '&2 You repeated the [value] &r &2 challenge!' - you-repeated-challenge-multiple: '&2 You repeated the [value] &r &2 challenge [count] times!' - you-completed-level: '&2 You completed the [value] &r &2 level!' - name-has-completed-challenge: '&5 [name] has completed the [value] &r &5 challenge!' - name-has-completed-level: '&5 [name] has completed the [value] &r &5 level!' - load-skipping: '"[value]" already exists - skipping' - load-overwriting: 'Overwriting "[value]"' - load-add: 'Adding new object: [value]' + completed: "&2 You completed the challenge [name] for [player]!" + already-completed: "&2 This challenge has already been completed!" + reset: "&2 You have reset the challenge [name] for [player]!" + reset-all: "&2 All challenges for [player] have been reset!" + not-completed: "&2 This challenge has not been completed yet!" + migrate-start: "&2 Starting migration of challenges add-on data." + migrate-end: "&2 Challenges add-on data has been updated to the new format." + migrate-not: "&2 All data is valid." + start-downloading: "&5 Starting to download and import the Challenges Library." + you-completed-challenge: "&2 You completed the [value] &r &2 challenge!" + you-repeated-challenge: "&2 You repeated the [value] &r &2 challenge!" + you-repeated-challenge-multiple: "&2 You repeated the [value] &r &2 challenge [count] times!" + you-completed-level: "&2 You completed the [value] &r &2 level!" + name-has-completed-challenge: "&5 [name] has completed the [value] &r &5 challenge!" + name-has-completed-level: "&5 [name] has completed the [value] &r &5 level!" + load-skipping: "\"[value]\" already exists - skipping" + load-overwriting: "Overwriting \"[value]\"" + load-add: "Adding new object: [value]" errors: - no-name: '&c Missing challenge name' - unknown-challenge: '&c Unknown challenge' + no-name: "&c Missing challenge name" + unknown-challenge: "&c Unknown challenge" not-valid-integer: |- - &c Given integer "[value]" is not valid! - Value should be between [min] and [max]. - not-deployed: '&c Challenge is not deployed!' - not-on-island: '&c You must be on your island to do that!' - challenge-level-not-available: '&c You have not unlocked the required level to complete this challenge.' - not-repeatable: '&c This challenge is not repeatable!' - wrong-environment: '&c You are in the wrong environment!' - not-enough-items: '&c You do not have enough [items] to complete this challenge!' - not-close-enough: '&c You must be standing within [number] blocks of all required items.' - you-still-need: '&c You still need [amount] x [item]' - missing-addon: '&c Cannot complete challenge: Required addon or plugin is missing.' - incorrect: '&c Cannot complete challenge: Requirements are incorrect.' - not-enough-money: '&c It is necessary to have [value] on your account to complete the challenge.' - not-enough-experience: '&c It is necessary to have [value] EXP to complete this challenge.' - island-level: '&c Your island must be level [number] or greater to complete this challenge!' - no-load: '&c Error: Could not load [file]. Error [message]' - load-error: '&c Error: Cannot load [value].' - no-rank: "&c You do not have rank that is high enough to do that." - cannot-remove-items: '&c Some items cannot be removed from your inventory!' - exist-challenges-or-levels: '&c Challenges already exist in your world. Cannot proceed!' - no-challenges: '&c Challenges are not implemented in this world yet!' - no-challenges-admin: '&c Challenges are not implemented in this world yet! Use &5 /[command] &c to add them!' - missing-arguments: '&c Command is missing arguments.' + &c The given integer "[value]" is not valid! + It should be between [min] and [max]. + not-deployed: "&c The challenge is not deployed!" + not-on-island: "&c You must be on your island to do that!" + challenge-level-not-available: "&c You have not unlocked the required level to complete this challenge." + not-repeatable: "&c This challenge is not repeatable!" + wrong-environment: "&c You are in the wrong environment!" + not-enough-items: "&c You do not have enough [items] to complete this challenge!" + not-close-enough: "&c You must be standing within [number] blocks of all required items." + you-still-need: "&c You still need [amount] x [item]" + missing-addon: "&c Cannot complete the challenge: a required add-on or plugin is missing." + incorrect: "&c Cannot complete the challenge: the requirements are not met." + not-enough-money: "&c You must have [value] in your account to complete the challenge." + not-enough-experience: "&c You must have [value] EXP to complete this challenge." + island-level: "&c Your island must be at least level [number] to complete this challenge!" + no-load: "&c Error: Could not load [file]. Error: [message]." + load-error: "&c Error: Cannot load [value]." + no-rank: "&c You do not have a high enough rank to do that." + cannot-remove-items: "&c Some items cannot be removed from your inventory!" + exist-challenges-or-levels: "&c Challenges already exist in your world. Cannot proceed!" + no-challenges: "&c Challenges are not implemented in this world yet!" + no-challenges-admin: "&c Challenges are not implemented in this world yet! Use &5 /[command] &c to add them!" + missing-arguments: "&c The command is missing arguments." no-multiple-permission: "&c You do not have permission to complete this challenge multiple times at once." - invalid-level: "&c Level [level] contains invalid data. It will not be loaded from database!" - invalid-challenge: "&c Challenge [challenge] contains invalid data. It will not be loaded from database!" - no-library-entries: "&c Cannot find any library entries. Nothing to show." - not-hooked: "&c Challenges Addon could not find any GameMode." - timeout: "&c This challenge requires to wait [timeout] between completions. You must wait [wait-time] till complete it again." - requirement-not-met: "&c This challenge requires [statistic] to have [number]. You have only [value]. " - requirement-not-met-entity: "&c This challenge requires [statistic] [entity] to have [number]. You have only [value]. " - requirement-not-met-material: "&c This challenge requires [statistic] [material] to have [number]. You have only [value]. " + invalid-level: "&c Level [level] contains invalid data and will not be loaded from the database!" + invalid-challenge: "&c Challenge [challenge] contains invalid data and will not be loaded from the database!" + no-library-entries: "&c No library entries found. Nothing to show." + not-hooked: "&c The Challenges add-on could not find any GameMode." + timeout: "&c This challenge requires a wait time of [timeout] between completions. You must wait [wait-time] before completing it again." + requirement-not-met: "&c This challenge requires [statistic] of [number]. You only have [value]." + requirement-not-met-entity: "&c This challenge requires [statistic] for [entity] to be [number]. You only have [value]." + requirement-not-met-material: "&c This challenge requires [statistic] for [material] to be [number]. You only have [value]." # # Showcase for manual material translation # materials: # # Names should be lowercase. @@ -1368,7 +1204,7 @@ protection: description: "&5 &o Toggle who can\n&5 &o complete challenges" name: "Challenges protection" CHALLENGES_WORLD_PROTECTION: - description: "&5 &o Enable/disable\n&5 &o requirement for players to\n&5 &o be on their island to\n&5 &o complete a challenge." + description: "&5 &o Enable/disable\n&5 &o the requirement for players to\n&5 &o be on their island to\n&5 &o complete a challenge." name: "Challenges Island limitation" - hint: "No challenges outside island" + hint: "No challenges outside the island" version: 12 From 3859260ac3009addb777f35e6f6f0282959f4f2f Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 6 Feb 2025 12:30:36 -0800 Subject: [PATCH 07/34] Remove items already selected in the tags chooser. Remove debug. --- .../bentobox/challenges/panel/admin/EditChallengePanel.java | 1 - .../challenges/panel/admin/ManageEntityGroupsPanel.java | 1 - .../challenges/panel/util/MultiEntityTypeTagsSelector.java | 2 ++ .../challenges/panel/util/MultiMaterialTagsSelector.java | 3 ++- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java index c63f77b8..300652ee 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java @@ -171,7 +171,6 @@ private void buildMainPropertiesPanel(PanelBuilder panelBuilder) { * @param panelBuilder PanelBuilder where icons must be added. */ private void buildIslandRequirementsPanel(PanelBuilder panelBuilder) { - BentoBox.getInstance().logDebug("build Island Req pan"); panelBuilder.item(19, this.createRequirementButton(RequirementButton.REQUIRED_ENTITIES)); panelBuilder.item(20, this.createRequirementButton(RequirementButton.REQUIRED_ENTITYTAGS)); panelBuilder.item(28, this.createRequirementButton(RequirementButton.REMOVE_ENTITIES)); diff --git a/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java index 42328fec..2274012b 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java @@ -16,7 +16,6 @@ import org.bukkit.Tag; import org.bukkit.entity.EntityType; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; import org.eclipse.jdt.annotation.Nullable; import lv.id.bonne.panelutils.PanelUtils; diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java index 681050ff..f9931160 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java @@ -98,6 +98,8 @@ private MultiEntityTypeTagsSelector(User user, Mode mode, Set> e elements.remove(Tag.ENTITY_TYPES_NON_CONTROLLING_RIDER); elements.remove(Tag.ENTITY_TYPES_NOT_SCARY_FOR_PUFFERFISH); elements.remove(Tag.ENTITY_TYPES_FROG_FOOD); + // Remove excluded tags + excluded.forEach(tag -> elements.removeIf(tag2 -> tag2.equals(tag))); // Init without filters applied. this.filterElements = this.elements; } diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java index c682b527..a793e93e 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java @@ -125,7 +125,8 @@ private MultiMaterialTagsSelector(User user, Mode mode, Set> exclu elements.remove(Tag.PREVENT_MOB_SPAWNING_INSIDE); elements.remove(Tag.SMELTS_TO_GLASS); elements.remove(Tag.WITHER_IMMUNE); - + // Remove excluded tags + excluded.forEach(tag -> elements.removeIf(tag2 -> tag2.equals(tag))); // Init without filters applied. this.filterElements = this.elements; } From a6f2dc02d4dcfa7af8f3da78d84e361e4f42e76b Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 6 Feb 2025 12:31:17 -0800 Subject: [PATCH 08/34] Reorg imports --- .../bentobox/challenges/ChallengesAddon.java | 2 +- .../challenges/database/object/Challenge.java | 6 +++++- .../database/object/ChallengeLevel.java | 6 +++++- .../adapters/EntityCompatibilityAdapter.java | 12 ++++++++++-- .../object/adapters/TypeMigrationAdapter.java | 9 ++++++++- .../requirements/InventoryRequirements.java | 6 +++++- .../requirements/StatisticRequirements.java | 3 ++- .../events/ChallengeCompletedEvent.java | 3 ++- .../events/ChallengeResetAllEvent.java | 3 ++- .../challenges/events/ChallengeResetEvent.java | 3 ++- .../challenges/events/LevelCompletedEvent.java | 3 ++- .../managers/ChallengesImportManager.java | 11 ++++++++++- .../challenges/managers/ChallengesManager.java | 14 +++++++++++++- .../challenges/panel/CommonPagedPanel.java | 8 ++++---- .../challenges/panel/ConversationUtils.java | 16 ++++++++++++---- .../panel/admin/EditChallengePanel.java | 1 - .../challenges/panel/admin/EditLevelPanel.java | 14 +++++++++++--- .../challenges/panel/admin/ListLevelsPanel.java | 6 +++--- .../challenges/panel/user/ChallengesPanel.java | 11 ++++++----- .../challenges/panel/user/GameModePanel.java | 9 +++++---- .../challenges/panel/user/MultiplePanel.java | 6 +++--- .../panel/util/ChallengeTypeSelector.java | 6 +++++- .../panel/util/MultiBlockSelector.java | 8 +++++++- .../panel/util/MultiEntitySelector.java | 13 ++++++++++--- .../panel/util/MultiEntityTypeTagsSelector.java | 1 - .../challenges/panel/util/PagedSelector.java | 5 +++-- .../panel/util/SingleBlockSelector.java | 12 +++++++++--- .../panel/util/SingleEntitySelector.java | 7 ++++++- .../challenges/panel/util/StatisticSelector.java | 10 +++++++--- .../bentobox/challenges/utils/UtilsTest.java | 6 +++++- 30 files changed, 163 insertions(+), 57 deletions(-) diff --git a/src/main/java/world/bentobox/challenges/ChallengesAddon.java b/src/main/java/world/bentobox/challenges/ChallengesAddon.java index ee9340b8..847bbd98 100644 --- a/src/main/java/world/bentobox/challenges/ChallengesAddon.java +++ b/src/main/java/world/bentobox/challenges/ChallengesAddon.java @@ -15,8 +15,8 @@ import world.bentobox.bentobox.database.DatabaseSetup.DatabaseType; import world.bentobox.bentobox.hooks.VaultHook; import world.bentobox.bentobox.managers.RanksManager; -import world.bentobox.challenges.commands.ChallengesPlayerCommand; import world.bentobox.challenges.commands.ChallengesGlobalPlayerCommand; +import world.bentobox.challenges.commands.ChallengesPlayerCommand; import world.bentobox.challenges.commands.admin.ChallengesAdminCommand; import world.bentobox.challenges.commands.admin.ChallengesGlobalAdminCommand; import world.bentobox.challenges.config.Settings; diff --git a/src/main/java/world/bentobox/challenges/database/object/Challenge.java b/src/main/java/world/bentobox/challenges/database/object/Challenge.java index c595a0a7..6187529c 100644 --- a/src/main/java/world/bentobox/challenges/database/object/Challenge.java +++ b/src/main/java/world/bentobox/challenges/database/object/Challenge.java @@ -1,7 +1,11 @@ package world.bentobox.challenges.database.object; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; import org.bukkit.Material; diff --git a/src/main/java/world/bentobox/challenges/database/object/ChallengeLevel.java b/src/main/java/world/bentobox/challenges/database/object/ChallengeLevel.java index 4a7ae9fc..518ec828 100644 --- a/src/main/java/world/bentobox/challenges/database/object/ChallengeLevel.java +++ b/src/main/java/world/bentobox/challenges/database/object/ChallengeLevel.java @@ -1,7 +1,11 @@ package world.bentobox.challenges.database.object; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; import org.bukkit.Material; diff --git a/src/main/java/world/bentobox/challenges/database/object/adapters/EntityCompatibilityAdapter.java b/src/main/java/world/bentobox/challenges/database/object/adapters/EntityCompatibilityAdapter.java index 8abcf5f6..25f9d02a 100644 --- a/src/main/java/world/bentobox/challenges/database/object/adapters/EntityCompatibilityAdapter.java +++ b/src/main/java/world/bentobox/challenges/database/object/adapters/EntityCompatibilityAdapter.java @@ -7,12 +7,20 @@ package world.bentobox.challenges.database.object.adapters; -import com.google.gson.*; -import org.bukkit.entity.EntityType; import java.lang.reflect.Type; import java.util.EnumMap; import java.util.Map; +import org.bukkit.entity.EntityType; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + import world.bentobox.bentobox.BentoBox; diff --git a/src/main/java/world/bentobox/challenges/database/object/adapters/TypeMigrationAdapter.java b/src/main/java/world/bentobox/challenges/database/object/adapters/TypeMigrationAdapter.java index 2c8c434c..a720ea00 100644 --- a/src/main/java/world/bentobox/challenges/database/object/adapters/TypeMigrationAdapter.java +++ b/src/main/java/world/bentobox/challenges/database/object/adapters/TypeMigrationAdapter.java @@ -7,9 +7,16 @@ package world.bentobox.challenges.database.object.adapters; -import com.google.gson.*; import java.lang.reflect.Type; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + import world.bentobox.challenges.database.object.Challenge; diff --git a/src/main/java/world/bentobox/challenges/database/object/requirements/InventoryRequirements.java b/src/main/java/world/bentobox/challenges/database/object/requirements/InventoryRequirements.java index 1f3686ec..946d0a44 100644 --- a/src/main/java/world/bentobox/challenges/database/object/requirements/InventoryRequirements.java +++ b/src/main/java/world/bentobox/challenges/database/object/requirements/InventoryRequirements.java @@ -7,7 +7,11 @@ package world.bentobox.challenges.database.object.requirements; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.stream.Collectors; import org.bukkit.Material; diff --git a/src/main/java/world/bentobox/challenges/database/object/requirements/StatisticRequirements.java b/src/main/java/world/bentobox/challenges/database/object/requirements/StatisticRequirements.java index b90ec701..2951bbfb 100644 --- a/src/main/java/world/bentobox/challenges/database/object/requirements/StatisticRequirements.java +++ b/src/main/java/world/bentobox/challenges/database/object/requirements/StatisticRequirements.java @@ -7,12 +7,13 @@ package world.bentobox.challenges.database.object.requirements; -import com.google.gson.annotations.Expose; import org.bukkit.Material; import org.bukkit.Statistic; import org.bukkit.entity.EntityType; import org.eclipse.jdt.annotation.Nullable; +import com.google.gson.annotations.Expose; + public class StatisticRequirements extends Requirements { diff --git a/src/main/java/world/bentobox/challenges/events/ChallengeCompletedEvent.java b/src/main/java/world/bentobox/challenges/events/ChallengeCompletedEvent.java index 50c072ee..f9cfc704 100644 --- a/src/main/java/world/bentobox/challenges/events/ChallengeCompletedEvent.java +++ b/src/main/java/world/bentobox/challenges/events/ChallengeCompletedEvent.java @@ -2,9 +2,10 @@ -import org.bukkit.event.HandlerList; import java.util.UUID; +import org.bukkit.event.HandlerList; + import world.bentobox.bentobox.api.events.BentoBoxEvent; diff --git a/src/main/java/world/bentobox/challenges/events/ChallengeResetAllEvent.java b/src/main/java/world/bentobox/challenges/events/ChallengeResetAllEvent.java index fdd34f45..1988f7fe 100644 --- a/src/main/java/world/bentobox/challenges/events/ChallengeResetAllEvent.java +++ b/src/main/java/world/bentobox/challenges/events/ChallengeResetAllEvent.java @@ -1,9 +1,10 @@ package world.bentobox.challenges.events; -import org.bukkit.event.HandlerList; import java.util.UUID; +import org.bukkit.event.HandlerList; + import world.bentobox.bentobox.api.events.BentoBoxEvent; diff --git a/src/main/java/world/bentobox/challenges/events/ChallengeResetEvent.java b/src/main/java/world/bentobox/challenges/events/ChallengeResetEvent.java index 3c2eea3d..8294a1fb 100644 --- a/src/main/java/world/bentobox/challenges/events/ChallengeResetEvent.java +++ b/src/main/java/world/bentobox/challenges/events/ChallengeResetEvent.java @@ -1,9 +1,10 @@ package world.bentobox.challenges.events; -import org.bukkit.event.HandlerList; import java.util.UUID; +import org.bukkit.event.HandlerList; + import world.bentobox.bentobox.api.events.BentoBoxEvent; diff --git a/src/main/java/world/bentobox/challenges/events/LevelCompletedEvent.java b/src/main/java/world/bentobox/challenges/events/LevelCompletedEvent.java index 0928b1c8..1a21cc34 100644 --- a/src/main/java/world/bentobox/challenges/events/LevelCompletedEvent.java +++ b/src/main/java/world/bentobox/challenges/events/LevelCompletedEvent.java @@ -1,9 +1,10 @@ package world.bentobox.challenges.events; -import org.bukkit.event.HandlerList; import java.util.UUID; +import org.bukkit.event.HandlerList; + import world.bentobox.bentobox.api.events.BentoBoxEvent; diff --git a/src/main/java/world/bentobox/challenges/managers/ChallengesImportManager.java b/src/main/java/world/bentobox/challenges/managers/ChallengesImportManager.java index 4788607d..ef99daa5 100644 --- a/src/main/java/world/bentobox/challenges/managers/ChallengesImportManager.java +++ b/src/main/java/world/bentobox/challenges/managers/ChallengesImportManager.java @@ -9,7 +9,16 @@ import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; import java.util.stream.Collectors; import org.bukkit.Material; diff --git a/src/main/java/world/bentobox/challenges/managers/ChallengesManager.java b/src/main/java/world/bentobox/challenges/managers/ChallengesManager.java index 4a655f7b..89036cef 100644 --- a/src/main/java/world/bentobox/challenges/managers/ChallengesManager.java +++ b/src/main/java/world/bentobox/challenges/managers/ChallengesManager.java @@ -1,7 +1,19 @@ package world.bentobox.challenges.managers; -import java.util.*; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; +import java.util.UUID; import java.util.stream.Collectors; import org.bukkit.Bukkit; diff --git a/src/main/java/world/bentobox/challenges/panel/CommonPagedPanel.java b/src/main/java/world/bentobox/challenges/panel/CommonPagedPanel.java index 24482124..84de54fa 100644 --- a/src/main/java/world/bentobox/challenges/panel/CommonPagedPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/CommonPagedPanel.java @@ -7,15 +7,15 @@ package world.bentobox.challenges.panel; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; + import org.bukkit.Material; import org.bukkit.World; import org.bukkit.inventory.ItemStack; import org.eclipse.jdt.annotation.NonNull; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Consumer; - import world.bentobox.bentobox.api.panels.PanelItem; import world.bentobox.bentobox.api.panels.builders.PanelBuilder; import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; diff --git a/src/main/java/world/bentobox/challenges/panel/ConversationUtils.java b/src/main/java/world/bentobox/challenges/panel/ConversationUtils.java index 270528f9..e4ca2e90 100644 --- a/src/main/java/world/bentobox/challenges/panel/ConversationUtils.java +++ b/src/main/java/world/bentobox/challenges/panel/ConversationUtils.java @@ -7,16 +7,24 @@ package world.bentobox.challenges.panel; -import org.bukkit.ChatColor; -import org.bukkit.conversations.*; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; import java.util.function.Function; +import org.bukkit.ChatColor; +import org.bukkit.conversations.ConversationAbandonedListener; +import org.bukkit.conversations.ConversationContext; +import org.bukkit.conversations.ConversationFactory; +import org.bukkit.conversations.MessagePrompt; +import org.bukkit.conversations.NumericPrompt; +import org.bukkit.conversations.Prompt; +import org.bukkit.conversations.StringPrompt; +import org.bukkit.conversations.ValidatingPrompt; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.user.User; import world.bentobox.challenges.utils.Constants; diff --git a/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java index 300652ee..7c73d7cb 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java @@ -18,7 +18,6 @@ import org.bukkit.inventory.ItemStack; import lv.id.bonne.panelutils.PanelUtils; -import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.panels.PanelItem; import world.bentobox.bentobox.api.panels.PanelListener; import world.bentobox.bentobox.api.panels.builders.PanelBuilder; diff --git a/src/main/java/world/bentobox/challenges/panel/admin/EditLevelPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/EditLevelPanel.java index 07af626c..0b3dcedd 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/EditLevelPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/EditLevelPanel.java @@ -1,7 +1,15 @@ package world.bentobox.challenges.panel.admin; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.function.Consumer; import java.util.stream.Collectors; @@ -19,14 +27,14 @@ import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.util.Util; import world.bentobox.challenges.ChallengesAddon; -import world.bentobox.challenges.managers.ChallengesManager; import world.bentobox.challenges.database.object.Challenge; import world.bentobox.challenges.database.object.ChallengeLevel; +import world.bentobox.challenges.managers.ChallengesManager; import world.bentobox.challenges.panel.CommonPagedPanel; import world.bentobox.challenges.panel.CommonPanel; import world.bentobox.challenges.panel.ConversationUtils; -import world.bentobox.challenges.panel.util.ItemSelector; import world.bentobox.challenges.panel.util.ChallengeSelector; +import world.bentobox.challenges.panel.util.ItemSelector; import world.bentobox.challenges.panel.util.MultiBlockSelector; import world.bentobox.challenges.utils.Constants; import world.bentobox.challenges.utils.Utils; diff --git a/src/main/java/world/bentobox/challenges/panel/admin/ListLevelsPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/ListLevelsPanel.java index 9b344f53..9e688881 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/ListLevelsPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/ListLevelsPanel.java @@ -1,13 +1,13 @@ package world.bentobox.challenges.panel.admin; -import org.bukkit.Material; -import org.bukkit.World; - import java.util.List; import java.util.function.Consumer; import java.util.stream.Collectors; +import org.bukkit.Material; +import org.bukkit.World; + import lv.id.bonne.panelutils.PanelUtils; import world.bentobox.bentobox.api.panels.PanelItem; import world.bentobox.bentobox.api.panels.builders.PanelBuilder; diff --git a/src/main/java/world/bentobox/challenges/panel/user/ChallengesPanel.java b/src/main/java/world/bentobox/challenges/panel/user/ChallengesPanel.java index 4095dc57..7b8b7e1d 100644 --- a/src/main/java/world/bentobox/challenges/panel/user/ChallengesPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/user/ChallengesPanel.java @@ -7,17 +7,18 @@ package world.bentobox.challenges.panel.user; -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 java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; +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.panels.PanelItem; import world.bentobox.bentobox.api.panels.TemplatedPanel; import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; diff --git a/src/main/java/world/bentobox/challenges/panel/user/GameModePanel.java b/src/main/java/world/bentobox/challenges/panel/user/GameModePanel.java index c8ed4778..1b3b9b0b 100644 --- a/src/main/java/world/bentobox/challenges/panel/user/GameModePanel.java +++ b/src/main/java/world/bentobox/challenges/panel/user/GameModePanel.java @@ -7,15 +7,16 @@ package world.bentobox.challenges.panel.user; -import org.bukkit.World; -import org.bukkit.inventory.ItemStack; -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import org.bukkit.World; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + import world.bentobox.bentobox.api.addons.GameModeAddon; import world.bentobox.bentobox.api.panels.PanelItem; import world.bentobox.bentobox.api.panels.TemplatedPanel; diff --git a/src/main/java/world/bentobox/challenges/panel/user/MultiplePanel.java b/src/main/java/world/bentobox/challenges/panel/user/MultiplePanel.java index 011f66ae..c951cc09 100644 --- a/src/main/java/world/bentobox/challenges/panel/user/MultiplePanel.java +++ b/src/main/java/world/bentobox/challenges/panel/user/MultiplePanel.java @@ -7,15 +7,15 @@ package world.bentobox.challenges.panel.user; -import org.bukkit.inventory.ItemStack; -import org.eclipse.jdt.annotation.NonNull; - import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import java.util.stream.Collectors; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.NonNull; + import world.bentobox.bentobox.api.panels.PanelItem; import world.bentobox.bentobox.api.panels.TemplatedPanel; import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; diff --git a/src/main/java/world/bentobox/challenges/panel/util/ChallengeTypeSelector.java b/src/main/java/world/bentobox/challenges/panel/util/ChallengeTypeSelector.java index 024f4411..389c5fde 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/ChallengeTypeSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/ChallengeTypeSelector.java @@ -20,7 +20,11 @@ import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; import world.bentobox.bentobox.api.user.User; import world.bentobox.challenges.database.object.Challenge; -import world.bentobox.challenges.database.object.requirements.*; +import world.bentobox.challenges.database.object.requirements.InventoryRequirements; +import world.bentobox.challenges.database.object.requirements.IslandRequirements; +import world.bentobox.challenges.database.object.requirements.OtherRequirements; +import world.bentobox.challenges.database.object.requirements.Requirements; +import world.bentobox.challenges.database.object.requirements.StatisticRequirements; import world.bentobox.challenges.utils.Constants; diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiBlockSelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiBlockSelector.java index b5ce1240..0d6498b2 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiBlockSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiBlockSelector.java @@ -1,7 +1,13 @@ package world.bentobox.challenges.panel.util; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.function.BiConsumer; import java.util.stream.Collectors; diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java index fefe9927..d6d68f57 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java @@ -1,12 +1,19 @@ package world.bentobox.challenges.panel.util; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + import org.bukkit.Material; import org.bukkit.entity.EntityType; import org.bukkit.inventory.ItemStack; -import java.util.*; -import java.util.function.BiConsumer; -import java.util.stream.Collectors; import lv.id.bonne.panelutils.PanelUtils; import world.bentobox.bentobox.api.panels.PanelItem; diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java index f9931160..aacd4bc4 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java @@ -21,7 +21,6 @@ import org.eclipse.jdt.annotation.Nullable; import lv.id.bonne.panelutils.PanelUtils; -import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.panels.PanelItem; import world.bentobox.bentobox.api.panels.builders.PanelBuilder; import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; diff --git a/src/main/java/world/bentobox/challenges/panel/util/PagedSelector.java b/src/main/java/world/bentobox/challenges/panel/util/PagedSelector.java index 290fdf2c..04b3d9b8 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/PagedSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/PagedSelector.java @@ -7,12 +7,13 @@ package world.bentobox.challenges.panel.util; -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + import world.bentobox.bentobox.api.panels.PanelItem; import world.bentobox.bentobox.api.panels.builders.PanelBuilder; import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; diff --git a/src/main/java/world/bentobox/challenges/panel/util/SingleBlockSelector.java b/src/main/java/world/bentobox/challenges/panel/util/SingleBlockSelector.java index d9fae37d..03cde66f 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/SingleBlockSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/SingleBlockSelector.java @@ -1,12 +1,18 @@ package world.bentobox.challenges.panel.util; -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.function.BiConsumer; import java.util.stream.Collectors; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + import lv.id.bonne.panelutils.PanelUtils; import world.bentobox.bentobox.api.panels.PanelItem; import world.bentobox.bentobox.api.panels.builders.PanelBuilder; diff --git a/src/main/java/world/bentobox/challenges/panel/util/SingleEntitySelector.java b/src/main/java/world/bentobox/challenges/panel/util/SingleEntitySelector.java index b0219aa5..f22080fb 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/SingleEntitySelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/SingleEntitySelector.java @@ -1,7 +1,12 @@ package world.bentobox.challenges.panel.util; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; import java.util.function.BiConsumer; import java.util.stream.Collectors; diff --git a/src/main/java/world/bentobox/challenges/panel/util/StatisticSelector.java b/src/main/java/world/bentobox/challenges/panel/util/StatisticSelector.java index f6d2aedd..5d4a519e 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/StatisticSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/StatisticSelector.java @@ -1,12 +1,16 @@ package world.bentobox.challenges.panel.util; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + import org.bukkit.Material; import org.bukkit.Statistic; import org.bukkit.inventory.ItemStack; -import java.util.*; -import java.util.function.BiConsumer; -import java.util.stream.Collectors; import lv.id.bonne.panelutils.PanelUtils; import world.bentobox.bentobox.api.panels.PanelItem; diff --git a/src/test/java/world/bentobox/challenges/utils/UtilsTest.java b/src/test/java/world/bentobox/challenges/utils/UtilsTest.java index b8a3c99f..e355241e 100644 --- a/src/test/java/world/bentobox/challenges/utils/UtilsTest.java +++ b/src/test/java/world/bentobox/challenges/utils/UtilsTest.java @@ -10,7 +10,11 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.Set; import org.bukkit.Bukkit; import org.bukkit.Material; From 25dca4c469f9f894ff392e572ebb33d9e4c9eb6e Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 6 Feb 2025 12:54:00 -0800 Subject: [PATCH 09/34] Refactor to remove duplicate code --- .../util/MultiEntityTypeTagsSelector.java | 297 +++-------------- .../panel/util/MultiMaterialTagsSelector.java | 298 +++--------------- .../panel/util/MultiTagsSelector.java | 173 ++++++++++ 3 files changed, 258 insertions(+), 510 deletions(-) create mode 100644 src/main/java/world/bentobox/challenges/panel/util/MultiTagsSelector.java diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java index aacd4bc4..8131061b 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java @@ -1,85 +1,49 @@ package world.bentobox.challenges.panel.util; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; import java.util.HashSet; -import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.Set; -import java.util.function.BiConsumer; -import java.util.stream.Collectors; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.Registry; import org.bukkit.Tag; import org.bukkit.entity.EntityType; -import org.bukkit.inventory.ItemStack; -import org.eclipse.jdt.annotation.Nullable; -import lv.id.bonne.panelutils.PanelUtils; -import world.bentobox.bentobox.api.panels.PanelItem; -import world.bentobox.bentobox.api.panels.builders.PanelBuilder; -import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; import world.bentobox.bentobox.api.user.User; -import world.bentobox.challenges.utils.Constants; -import world.bentobox.challenges.utils.Utils; - - -/** - * This class contains all necessary things that allows to select single entitytype tag from all in game tags. Selected - * tag will be returned via BiConsumer. - */ -public class MultiEntityTypeTagsSelector extends PagedSelector> -{ - public static final Map, Material> ICONS = Map.of(); +public class MultiEntityTypeTagsSelector extends MultiTagsSelector { - /** - * Functional buttons in current GUI. - */ - private enum Button { - ACCEPT_SELECTED, CANCEL - } + // The ICONS map is empty here, but you could add mappings if needed. + public static final java.util.Map, Material> ICONS = java.util.Map.of(); public enum Mode { ENTITY_TYPE, ANY } - // --------------------------------------------------------------------- - // Section: Variables - // --------------------------------------------------------------------- - - private final List> elements = new ArrayList<>(); + private MultiEntityTypeTagsSelector(User user, Mode mode, Set> excluded, + java.util.function.BiConsumer>> consumer) { + super(user, excluded, consumer); + // Add mode-specific behavior here if needed. + } - /** - * Set that contains selected materials. - */ - private final Set> selectedElements; + public static void open(User user, Mode mode, Set> excluded, + java.util.function.BiConsumer>> consumer) { + new MultiEntityTypeTagsSelector(user, mode, excluded, consumer).build(); + } - /** - * This variable stores consumer. - */ - private final BiConsumer>> consumer; + public static void open(User user, + java.util.function.BiConsumer>> consumer) { + open(user, Mode.ANY, new HashSet<>(), consumer); + } - /** - * Stores filtered items. - */ - private List> filterElements; + @Override + protected Iterable> getTags() { + return Bukkit.getTags("entity_types", EntityType.class); + } - private MultiEntityTypeTagsSelector(User user, Mode mode, Set> excluded, - BiConsumer>> consumer) { - super(user); - this.consumer = consumer; - - this.selectedElements = new HashSet<>(); - Iterable> iterable = Bukkit.getTags("entity_types", EntityType.class); - iterable.forEach(elements::add); - elements.sort(Comparator.comparing(tag -> tag.getKey().getKey())); - // Remove irrelevant tags + @Override + protected void removeIrrelevantTags() { elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("AXOLOTL")); elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("IMMUNE")); elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("IGNORES")); @@ -97,212 +61,33 @@ private MultiEntityTypeTagsSelector(User user, Mode mode, Set> e elements.remove(Tag.ENTITY_TYPES_NON_CONTROLLING_RIDER); elements.remove(Tag.ENTITY_TYPES_NOT_SCARY_FOR_PUFFERFISH); elements.remove(Tag.ENTITY_TYPES_FROG_FOOD); - // Remove excluded tags - excluded.forEach(tag -> elements.removeIf(tag2 -> tag2.equals(tag))); - // Init without filters applied. - this.filterElements = this.elements; - } - - - /** - * This method opens GUI that allows to select challenge type. - * - * @param user User who opens GUI. - * @param consumer Consumer that allows to get clicked type. - */ - public static void open(User user, Mode mode, Set> excluded, - BiConsumer>> consumer) - { - new MultiEntityTypeTagsSelector(user, mode, excluded, consumer).build(); - } - - - /** - * This method opens GUI that allows to select challenge type. - * - * @param user User who opens GUI. - * @param consumer Consumer that allows to get clicked type. - */ - public static void open(User user, BiConsumer>> consumer) - { - new MultiEntityTypeTagsSelector(user, Mode.ANY, new HashSet<>(), consumer).build(); - } - - -// --------------------------------------------------------------------- -// Section: Methods -// --------------------------------------------------------------------- - - - /** - * This method builds all necessary elements in GUI panel. - */ - @Override - protected void build() - { - PanelBuilder panelBuilder = new PanelBuilder().user(this.user); - panelBuilder.name(this.user.getTranslation(Constants.TITLE + "entity-selector")); - - PanelUtils.fillBorder(panelBuilder, Material.BLUE_STAINED_GLASS_PANE); - - this.populateElements(panelBuilder, this.filterElements); - - panelBuilder.item(3, this.createButton(Button.ACCEPT_SELECTED)); - panelBuilder.item(5, this.createButton(Button.CANCEL)); - - panelBuilder.build(); - } - - - /** - * This method is called when filter value is updated. - */ - @Override - protected void updateFilters() - { - if (this.searchString == null || this.searchString.isBlank()) - { - this.filterElements = this.elements; - } - else - { - this.filterElements = this.elements.stream(). - filter(element -> { - // If element name is set and name contains search field, then do not filter out. - return element.getKey().getKey().toLowerCase(Locale.ENGLISH) - .contains(this.searchString.toLowerCase(Locale.ENGLISH)); - }). - distinct(). - collect(Collectors.toList()); - } - } - - - /** - * This method creates PanelItem button of requested type. - * @param button Button which must be created. - * @return new PanelItem with requested functionality. - */ - private PanelItem createButton(Button button) - { - final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; - - final String name = this.user.getTranslation(reference + "name"); - final List description = new ArrayList<>(3); - description.add(this.user.getTranslation(reference + "description")); - - ItemStack icon; - PanelItem.ClickHandler clickHandler; - - switch (button) - { - case ACCEPT_SELECTED -> { - if (!this.selectedElements.isEmpty()) - { - description.add(this.user.getTranslation(reference + "title")); - this.selectedElements.forEach(material -> - description.add(this.user.getTranslation(reference + "element", - "[element]", Utils.prettifyObject(material, this.user)))); - } - - icon = new ItemStack(Material.COMMAND_BLOCK); - clickHandler = (panel, user1, clickType, slot) -> - { - this.consumer.accept(true, this.selectedElements); - return true; - }; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-save")); - } - case CANCEL -> { - - icon = new ItemStack(Material.IRON_DOOR); - - clickHandler = (panel, user1, clickType, slot) -> - { - this.consumer.accept(false, null); - return true; - }; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-cancel")); - } - default -> { - icon = new ItemStack(Material.PAPER); - clickHandler = null; - } - } - - return new PanelItemBuilder(). - icon(icon). - name(name). - description(description). - clickHandler(clickHandler). - build(); - } - - - /** - * This method creates button for given material. - * @param materialTag material which button must be created. - * @return new Button for material. - */ - @Override - protected PanelItem createElementButton(Tag materialTag) - { - final String reference = Constants.BUTTON + "entity-group."; - - List description = new ArrayList<>(); - description.add(this.user.getTranslation(reference + "description", - "[id]", materialTag.getKey().getKey())); - - if (this.selectedElements.contains(materialTag)) - { - description.add(this.user.getTranslation(reference + "selected")); - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-deselect")); - } - else - { - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-select")); - } + // ... and so on + } - return new PanelItemBuilder(). - name(this.user.getTranslation(reference + "name", "[tag]", - Utils.prettifyObject(materialTag, this.user))). - icon(getIcon(materialTag)). - description(description). - clickHandler((panel, user1, clickType, slot) -> { - // On right click change which entities are selected for deletion. - if (!this.selectedElements.add(materialTag)) - { - // Remove material if it is already selected - this.selectedElements.remove(materialTag); - } + @Override + protected String getTitleKey() { + return "entity-selector"; + } - this.build(); - return true; - }). - glow(this.selectedElements.contains(materialTag)). - build(); - } + @Override + protected String getElementGroupKey() { + return "entity-group."; + } - - private @Nullable Material getIcon(Tag materialTag) { - if (materialTag.getKey().getKey().contains("boat")) { + @Override + protected Material getIconForTag(Tag tag) { + if (tag.getKey().getKey().contains("boat")) { return Material.OAK_BOAT; } - EntityType entType = Registry.ENTITY_TYPE.stream().filter(materialTag::isTagged).findAny().orElse(null); + EntityType entType = Registry.ENTITY_TYPE.stream().filter(tag::isTagged).findAny().orElse(null); + if (entType == null) { + return Material.PAPER; + } String eggName = entType.getKey().getKey().toUpperCase(Locale.ENGLISH) + "_SPAWN_EGG"; - Material result; try { - result = Material.valueOf(eggName); + return Material.valueOf(eggName); } catch (Exception e) { - result = Material.PAPER; + return Material.PAPER; } - return result; } - } diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java index a793e93e..4bd9f69d 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java @@ -1,86 +1,57 @@ package world.bentobox.challenges.panel.util; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; import java.util.HashSet; -import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; -import java.util.function.BiConsumer; -import java.util.stream.Collectors; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.Registry; import org.bukkit.Tag; -import org.bukkit.inventory.ItemStack; -import org.eclipse.jdt.annotation.Nullable; -import lv.id.bonne.panelutils.PanelUtils; -import world.bentobox.bentobox.api.panels.PanelItem; -import world.bentobox.bentobox.api.panels.builders.PanelBuilder; -import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; import world.bentobox.bentobox.api.user.User; -import world.bentobox.challenges.utils.Constants; -import world.bentobox.challenges.utils.Utils; - - -/** - * This class contains all necessary things that allows to select single material tag from all in game tags. Selected - * tag will be returned via BiConsumer. - */ -public class MultiMaterialTagsSelector extends PagedSelector> -{ - public static final Map, Material> ICONS = Map.of(Tag.AIR, Material.BARRIER, Tag.FIRE, - Material.TORCH, Tag.CANDLE_CAKES, Material.CAKE, Tag.PORTALS, Material.MAGENTA_STAINED_GLASS_PANE, - Tag.WALL_HANGING_SIGNS, Material.ACACIA_SIGN, Tag.WALL_SIGNS, Material.OAK_SIGN, Tag.WALL_CORALS, - Material.BUBBLE_CORAL_FAN); +public class MultiMaterialTagsSelector extends MultiTagsSelector { - /** - * Functional buttons in current GUI. - */ - private enum Button { - ACCEPT_SELECTED, CANCEL - } + public static final Map, Material> ICONS = Map.of( + Tag.AIR, Material.BARRIER, + Tag.FIRE, Material.TORCH, + Tag.CANDLE_CAKES, Material.CAKE, + Tag.PORTALS, Material.MAGENTA_STAINED_GLASS_PANE, + Tag.WALL_HANGING_SIGNS, Material.ACACIA_SIGN, + Tag.WALL_SIGNS, Material.OAK_SIGN, + Tag.WALL_CORALS, Material.BUBBLE_CORAL_FAN + // ... add other mappings as needed + ); public enum Mode { BLOCKS, ITEMS, ANY } - // --------------------------------------------------------------------- - // Section: Variables - // --------------------------------------------------------------------- - - private final List> elements = new ArrayList<>(); + private MultiMaterialTagsSelector(User user, Mode mode, Set> excluded, + java.util.function.BiConsumer>> consumer) { + super(user, excluded, consumer); + // Future, use the mode to perform additional filtering. + } - /** - * Set that contains selected materials. - */ - private final Set> selectedElements; + public static void open(User user, Mode mode, Set> excluded, + java.util.function.BiConsumer>> consumer) { + new MultiMaterialTagsSelector(user, mode, excluded, consumer).build(); + } - /** - * This variable stores consumer. - */ - private final BiConsumer>> consumer; + public static void open(User user, + java.util.function.BiConsumer>> consumer) { + open(user, Mode.ANY, new HashSet<>(), consumer); + } - /** - * Stores filtered items. - */ - private List> filterElements; + @Override + protected Iterable> getTags() { + return Bukkit.getTags("blocks", Material.class); + } - private MultiMaterialTagsSelector(User user, Mode mode, Set> excluded, - BiConsumer>> consumer) { - super(user); - this.consumer = consumer; - - this.selectedElements = new HashSet<>(); - Iterable> iterable = Bukkit.getTags("blocks", Material.class); - iterable.forEach(elements::add); - elements.sort(Comparator.comparing(tag -> tag.getKey().getKey())); + @Override + protected void removeIrrelevantTags() { // Remove irrelevant tags elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("SPAWNABLE")); elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("PLACE")); @@ -125,203 +96,22 @@ private MultiMaterialTagsSelector(User user, Mode mode, Set> exclu elements.remove(Tag.PREVENT_MOB_SPAWNING_INSIDE); elements.remove(Tag.SMELTS_TO_GLASS); elements.remove(Tag.WITHER_IMMUNE); - // Remove excluded tags - excluded.forEach(tag -> elements.removeIf(tag2 -> tag2.equals(tag))); - // Init without filters applied. - this.filterElements = this.elements; - } - - - /** - * This method opens GUI that allows to select challenge type. - * - * @param user User who opens GUI. - * @param consumer Consumer that allows to get clicked type. - */ - public static void open(User user, Mode mode, Set> excluded, - BiConsumer>> consumer) - { - new MultiMaterialTagsSelector(user, mode, excluded, consumer).build(); - } - - - /** - * This method opens GUI that allows to select challenge type. - * - * @param user User who opens GUI. - * @param consumer Consumer that allows to get clicked type. - */ - public static void open(User user, BiConsumer>> consumer) - { - new MultiMaterialTagsSelector(user, Mode.ANY, new HashSet<>(), consumer).build(); - } - - -// --------------------------------------------------------------------- -// Section: Methods -// --------------------------------------------------------------------- - - - /** - * This method builds all necessary elements in GUI panel. - */ - @Override - protected void build() - { - PanelBuilder panelBuilder = new PanelBuilder().user(this.user); - panelBuilder.name(this.user.getTranslation(Constants.TITLE + "block-selector")); - - PanelUtils.fillBorder(panelBuilder, Material.BLUE_STAINED_GLASS_PANE); - - this.populateElements(panelBuilder, this.filterElements); - - panelBuilder.item(3, this.createButton(Button.ACCEPT_SELECTED)); - panelBuilder.item(5, this.createButton(Button.CANCEL)); - - panelBuilder.build(); - } - - - /** - * This method is called when filter value is updated. - */ - @Override - protected void updateFilters() - { - if (this.searchString == null || this.searchString.isBlank()) - { - this.filterElements = this.elements; - } - else - { - this.filterElements = this.elements.stream(). - filter(element -> { - // If element name is set and name contains search field, then do not filter out. - return element.getKey().getKey().toLowerCase(Locale.ENGLISH) - .contains(this.searchString.toLowerCase(Locale.ENGLISH)); - }). - distinct(). - collect(Collectors.toList()); - } - } - - - /** - * This method creates PanelItem button of requested type. - * @param button Button which must be created. - * @return new PanelItem with requested functionality. - */ - private PanelItem createButton(Button button) - { - final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; - - final String name = this.user.getTranslation(reference + "name"); - final List description = new ArrayList<>(3); - description.add(this.user.getTranslation(reference + "description")); - - ItemStack icon; - PanelItem.ClickHandler clickHandler; - - switch (button) - { - case ACCEPT_SELECTED -> { - if (!this.selectedElements.isEmpty()) - { - description.add(this.user.getTranslation(reference + "title")); - this.selectedElements.forEach(material -> - description.add(this.user.getTranslation(reference + "element", - "[element]", Utils.prettifyObject(material, this.user)))); - } - - icon = new ItemStack(Material.COMMAND_BLOCK); - clickHandler = (panel, user1, clickType, slot) -> - { - this.consumer.accept(true, this.selectedElements); - return true; - }; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-save")); - } - case CANCEL -> { - - icon = new ItemStack(Material.IRON_DOOR); - - clickHandler = (panel, user1, clickType, slot) -> - { - this.consumer.accept(false, null); - return true; - }; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-cancel")); - } - default -> { - icon = new ItemStack(Material.PAPER); - clickHandler = null; - } - } - - return new PanelItemBuilder(). - icon(icon). - name(name). - description(description). - clickHandler(clickHandler). - build(); - } - - - /** - * This method creates button for given material. - * @param materialTag material which button must be created. - * @return new Button for material. - */ - @Override - protected PanelItem createElementButton(Tag materialTag) - { - final String reference = Constants.BUTTON + "block-group."; - - List description = new ArrayList<>(); - description.add(this.user.getTranslation(reference + "description", - "[id]", materialTag.getKey().getKey())); - - if (this.selectedElements.contains(materialTag)) - { - description.add(this.user.getTranslation(reference + "selected")); - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-deselect")); - } - else - { - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-select")); - } - - return new PanelItemBuilder(). - name(this.user.getTranslation(reference + "name", "[tag]", - Utils.prettifyObject(materialTag, this.user))). - icon(getIcon(materialTag)). - description(description). - clickHandler((panel, user1, clickType, slot) -> { - // On right click change which entities are selected for deletion. - if (!this.selectedElements.add(materialTag)) - { - // Remove material if it is already selected - this.selectedElements.remove(materialTag); - } + } - this.build(); - return true; - }). - glow(this.selectedElements.contains(materialTag)). - build(); - } + @Override + protected String getTitleKey() { + return "block-selector"; + } - - private @Nullable Material getIcon(Tag materialTag) { - return ICONS.getOrDefault(materialTag, Registry.MATERIAL.stream().filter(materialTag::isTagged) - .filter(Material::isItem).findAny() - .orElse(Material.PAPER)); + @Override + protected String getElementGroupKey() { + return "block-group."; } + @Override + protected Material getIconForTag(Tag tag) { + return ICONS.getOrDefault(tag, Registry.MATERIAL.stream().filter(tag::isTagged) + .filter(Material::isItem).findAny().orElse(Material.PAPER)); + } } + diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiTagsSelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiTagsSelector.java new file mode 100644 index 00000000..31e85fc9 --- /dev/null +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiTagsSelector.java @@ -0,0 +1,173 @@ +package world.bentobox.challenges.panel.util; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +import org.bukkit.Keyed; +import org.bukkit.Material; +import org.bukkit.Tag; +import org.bukkit.inventory.ItemStack; + +import lv.id.bonne.panelutils.PanelUtils; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.builders.PanelBuilder; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.challenges.utils.Constants; +import world.bentobox.challenges.utils.Utils; + +/** + * @author tastybento + */ +public abstract class MultiTagsSelector extends PagedSelector> { + + // Buttons common to both selectors. + protected enum Button { + ACCEPT_SELECTED, CANCEL + } + + // Common fields. + protected final List> elements = new ArrayList<>(); + protected final Set> selectedElements; + protected final BiConsumer>> consumer; + protected List> filterElements; + + protected MultiTagsSelector(User user, Set> excluded, BiConsumer>> consumer) { + super(user); + this.consumer = consumer; + this.selectedElements = new HashSet<>(); + // Fill elements using the type‐specific method. + for (Tag tag : getTags()) { + elements.add(tag); + } + elements.sort(Comparator.comparing(tag -> tag.getKey().getKey())); + // Remove irrelevant tags (type‐specific) + removeIrrelevantTags(); + // Remove any tags passed in as excluded. + excluded.forEach(excludedTag -> elements.removeIf(tag -> tag.equals(excludedTag))); + // Initially no filter is applied. + this.filterElements = elements; + } + + // ABSTRACT METHODS TO BE IMPLEMENTED BY SUBCLASSES: + + /** Return the tags from Bukkit (for example, Bukkit.getTags("blocks", Material.class)). */ + protected abstract Iterable> getTags(); + + /** Remove tags that are not needed (e.g. by checking the key string). */ + protected abstract void removeIrrelevantTags(); + + /** Return the translation key used for the panel title. For example, "block-selector" or "entity-selector". */ + protected abstract String getTitleKey(); + + /** Return the translation key prefix for element buttons (e.g. "block-group." or "entity-group."). */ + protected abstract String getElementGroupKey(); + + /** Return the icon for the given tag. */ + protected abstract Material getIconForTag(Tag tag); + + // COMMON METHODS: + + @Override + protected void build() { + PanelBuilder panelBuilder = new PanelBuilder().user(this.user); + panelBuilder.name(this.user.getTranslation(Constants.TITLE + getTitleKey())); + + PanelUtils.fillBorder(panelBuilder, Material.BLUE_STAINED_GLASS_PANE); + this.populateElements(panelBuilder, this.filterElements); + + panelBuilder.item(3, createButton(Button.ACCEPT_SELECTED)); + panelBuilder.item(5, createButton(Button.CANCEL)); + + panelBuilder.build(); + } + + @Override + protected void updateFilters() { + if (this.searchString == null || this.searchString.isBlank()) { + this.filterElements = this.elements; + } else { + this.filterElements = this.elements.stream() + .filter(tag -> tag.getKey().getKey().toLowerCase(Locale.ENGLISH) + .contains(this.searchString.toLowerCase(Locale.ENGLISH))) + .distinct().collect(Collectors.toList()); + } + } + + private PanelItem createButton(Button button) { + final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; + final String name = this.user.getTranslation(reference + "name"); + final List description = new ArrayList<>(); + description.add(this.user.getTranslation(reference + "description")); + + ItemStack icon; + PanelItem.ClickHandler clickHandler; + + switch (button) { + case ACCEPT_SELECTED -> { + if (!this.selectedElements.isEmpty()) { + description.add(this.user.getTranslation(reference + "title")); + this.selectedElements.forEach(tag -> description.add(this.user.getTranslation(reference + "element", + "[element]", Utils.prettifyObject(tag, this.user)))); + } + icon = new ItemStack(Material.COMMAND_BLOCK); + clickHandler = (panel, user1, clickType, slot) -> { + this.consumer.accept(true, this.selectedElements); + return true; + }; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-save")); + } + case CANCEL -> { + icon = new ItemStack(Material.IRON_DOOR); + clickHandler = (panel, user1, clickType, slot) -> { + this.consumer.accept(false, null); + return true; + }; + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-cancel")); + } + default -> { + icon = new ItemStack(Material.PAPER); + clickHandler = null; + } + } + + return new PanelItemBuilder().icon(icon).name(name).description(description).clickHandler(clickHandler).build(); + } + + @Override + protected PanelItem createElementButton(Tag tag) { + final String reference = Constants.BUTTON + getElementGroupKey(); + List description = new ArrayList<>(); + description.add(this.user.getTranslation(reference + "description", "[id]", tag.getKey().getKey())); + + if (this.selectedElements.contains(tag)) { + description.add(this.user.getTranslation(reference + "selected")); + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-deselect")); + } else { + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-select")); + } + + return new PanelItemBuilder() + .name(this.user.getTranslation(reference + "name", "[tag]", Utils.prettifyObject(tag, this.user))) + .icon(getIconForTag(tag)).description(description).clickHandler((panel, user1, clickType, slot) -> { + // Toggle selection. + if (!this.selectedElements.add(tag)) { + this.selectedElements.remove(tag); + } + this.build(); + return true; + }).glow(this.selectedElements.contains(tag)).build(); + } +} From 8cddb628eb7bd8b823aedfb2976cc367aa760c89 Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 6 Feb 2025 20:41:16 -0800 Subject: [PATCH 10/34] Bug fixes --- .../panel/admin/ManageBlockGroupsPanel.java | 3 +- .../panel/util/MultiBlockSelector.java | 378 +++++------------- .../panel/util/MultiEntitySelector.java | 378 +++++------------- .../util/MultiEntityTypeTagsSelector.java | 201 +++++++--- .../panel/util/MultiMaterialTagsSelector.java | 250 ++++++++---- ...elector.java => UnifiedMultiSelector.java} | 131 +++--- src/main/resources/locales/en-US.yml | 6 +- 7 files changed, 623 insertions(+), 724 deletions(-) rename src/main/java/world/bentobox/challenges/panel/util/{MultiTagsSelector.java => UnifiedMultiSelector.java} (55%) diff --git a/src/main/java/world/bentobox/challenges/panel/admin/ManageBlockGroupsPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/ManageBlockGroupsPanel.java index 56d20a5b..8bef831f 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/ManageBlockGroupsPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/ManageBlockGroupsPanel.java @@ -18,6 +18,7 @@ import org.eclipse.jdt.annotation.Nullable; import lv.id.bonne.panelutils.PanelUtils; +import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.panels.PanelItem; import world.bentobox.bentobox.api.panels.builders.PanelBuilder; import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; @@ -200,7 +201,7 @@ private PanelItem createButton(Button button) description.add(this.user.getTranslation(reference + "title")); this.selectedTags.forEach(material -> description.add(this.user.getTranslation(reference + "material", - "[material]", Utils.prettifyObject(material, this.user)))); + "[material]", Utils.prettifyObject(material, this.user)))); } icon = new ItemStack(Material.LAVA_BUCKET); diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiBlockSelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiBlockSelector.java index 0d6498b2..380b96ea 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiBlockSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiBlockSelector.java @@ -1,7 +1,5 @@ package world.bentobox.challenges.panel.util; - -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; @@ -15,286 +13,108 @@ import org.bukkit.inventory.ItemStack; import lv.id.bonne.panelutils.PanelUtils; -import world.bentobox.bentobox.api.panels.PanelItem; -import world.bentobox.bentobox.api.panels.builders.PanelBuilder; -import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; import world.bentobox.bentobox.api.user.User; -import world.bentobox.challenges.utils.Constants; import world.bentobox.challenges.utils.Utils; - /** - * This class contains all necessary things that allows to select single block from all in game blocks. Selected - * block will be returned via BiConsumer. + * This class provides a multi-selector GUI for selecting blocks (i.e. Materials). + * It extends the unified multi-selector base class and provides the type-specific + * implementations required for block selection. */ -public class MultiBlockSelector extends PagedSelector -{ - private MultiBlockSelector(User user, Mode mode, Set excluded, BiConsumer> consumer) - { - super(user); - this.consumer = consumer; - - // Current GUI cannot display air blocks. It crashes with null-pointer - excluded.add(Material.AIR); - excluded.add(Material.CAVE_AIR); - excluded.add(Material.VOID_AIR); - - // Piston head and moving piston is not necessary. useless. - excluded.add(Material.PISTON_HEAD); - excluded.add(Material.MOVING_PISTON); - - // Barrier cannot be accessible to user. - excluded.add(Material.BARRIER); - - this.selectedElements = new HashSet<>(); - - this.elements = Arrays.stream(Material.values()). - filter(material -> !excluded.contains(material)). - filter(material -> { - switch (mode) - { - case BLOCKS -> { - return material.isBlock(); - } - case ITEMS -> { - return material.isItem(); - } - default -> { - return true; - } - } - }). - // Sort by name - sorted(Comparator.comparing(Material::name)). - collect(Collectors.toList()); - // Init without filters applied. - this.filterElements = this.elements; - } - - - /** - * This method opens GUI that allows to select challenge type. - * - * @param user User who opens GUI. - * @param consumer Consumer that allows to get clicked type. - */ - public static void open(User user, Mode mode, Set excluded, BiConsumer> consumer) - { - new MultiBlockSelector(user, mode, excluded, consumer).build(); - } - - - /** - * This method opens GUI that allows to select challenge type. - * - * @param user User who opens GUI. - * @param consumer Consumer that allows to get clicked type. - */ - public static void open(User user, BiConsumer> consumer) - { - new MultiBlockSelector(user, Mode.ANY, new HashSet<>(), consumer).build(); - } - - -// --------------------------------------------------------------------- -// Section: Methods -// --------------------------------------------------------------------- - - - /** - * This method builds all necessary elements in GUI panel. - */ - @Override - protected void build() - { - PanelBuilder panelBuilder = new PanelBuilder().user(this.user); - panelBuilder.name(this.user.getTranslation(Constants.TITLE + "block-selector")); - - PanelUtils.fillBorder(panelBuilder, Material.BLUE_STAINED_GLASS_PANE); - - this.populateElements(panelBuilder, this.filterElements); - - panelBuilder.item(3, this.createButton(Button.ACCEPT_SELECTED)); - panelBuilder.item(5, this.createButton(Button.CANCEL)); - - panelBuilder.build(); - } - - - /** - * This method is called when filter value is updated. - */ - @Override - protected void updateFilters() - { - if (this.searchString == null || this.searchString.isBlank()) - { - this.filterElements = this.elements; - } - else - { - this.filterElements = this.elements.stream(). - filter(element -> { - // If element name is set and name contains search field, then do not filter out. - return element.name().toLowerCase().contains(this.searchString.toLowerCase()); - }). - distinct(). - collect(Collectors.toList()); - } - } - - - /** - * This method creates PanelItem button of requested type. - * @param button Button which must be created. - * @return new PanelItem with requested functionality. - */ - private PanelItem createButton(Button button) - { - final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; - - final String name = this.user.getTranslation(reference + "name"); - final List description = new ArrayList<>(3); - description.add(this.user.getTranslation(reference + "description")); - - ItemStack icon; - PanelItem.ClickHandler clickHandler; - - switch (button) - { - case ACCEPT_SELECTED -> { - if (!this.selectedElements.isEmpty()) - { - description.add(this.user.getTranslation(reference + "title")); - this.selectedElements.forEach(material -> - description.add(this.user.getTranslation(reference + "element", - "[element]", Utils.prettifyObject(material, this.user)))); - } - - icon = new ItemStack(Material.COMMAND_BLOCK); - clickHandler = (panel, user1, clickType, slot) -> - { - this.consumer.accept(true, this.selectedElements); - return true; - }; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-save")); - } - case CANCEL -> { - - icon = new ItemStack(Material.IRON_DOOR); - - clickHandler = (panel, user1, clickType, slot) -> - { - this.consumer.accept(false, null); - return true; - }; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-cancel")); - } - default -> { - icon = new ItemStack(Material.PAPER); - clickHandler = null; - } - } - - return new PanelItemBuilder(). - icon(icon). - name(name). - description(description). - clickHandler(clickHandler). - build(); - } - - - /** - * This method creates button for given material. - * @param material material which button must be created. - * @return new Button for material. - */ - @Override - protected PanelItem createElementButton(Material material) - { - final String reference = Constants.BUTTON + "material."; - - List description = new ArrayList<>(); - description.add(this.user.getTranslation(reference + "description", - "[id]", material.name())); - - if (this.selectedElements.contains(material)) - { - description.add(this.user.getTranslation(reference + "selected")); - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-deselect")); - } - else - { - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-select")); - } - - return new PanelItemBuilder(). - name(this.user.getTranslation(reference + "name", "[material]", - Utils.prettifyObject(material, this.user))). - icon(PanelUtils.getMaterialItem(material)). - description(description). - clickHandler((panel, user1, clickType, slot) -> { - // On right click change which entities are selected for deletion. - if (!this.selectedElements.add(material)) - { - // Remove material if it is already selected - this.selectedElements.remove(material); - } - - this.build(); - return true; - }). - glow(this.selectedElements.contains(material)). - build(); - } - - - /** - * Functional buttons in current GUI. - */ - private enum Button - { - ACCEPT_SELECTED, - CANCEL - } - - - public enum Mode - { - BLOCKS, - ITEMS, - ANY - } - - -// --------------------------------------------------------------------- -// Section: Variables -// --------------------------------------------------------------------- - - /** - * List with elements that will be displayed in current GUI. - */ - private final List elements; - - /** - * Set that contains selected materials. - */ - private final Set selectedElements; - - /** - * This variable stores consumer. - */ - private final BiConsumer> consumer; - - /** - * Stores filtered items. - */ - private List filterElements; +public class MultiBlockSelector extends UnifiedMultiSelector { + + private final Mode mode; + private final Set excluded; + + public enum Mode { + BLOCKS, ITEMS, ANY + } + + /** + * Private constructor. + * + * @param user the user opening the selector + * @param mode the mode indicating whether to show only blocks, only items, or any + * @param excluded a set of Materials to exclude from the list + * @param consumer the callback to be invoked when the user confirms or cancels + */ + private MultiBlockSelector(User user, Mode mode, Set excluded, + BiConsumer> consumer) { + super(user, consumer); + this.mode = mode; + if (excluded == null) { + excluded = new HashSet<>(); + } + this.excluded = excluded; + // Add default exclusions + this.excluded.add(Material.AIR); + this.excluded.add(Material.CAVE_AIR); + this.excluded.add(Material.VOID_AIR); + this.excluded.add(Material.PISTON_HEAD); + this.excluded.add(Material.MOVING_PISTON); + this.excluded.add(Material.BARRIER); + } + + /** + * Opens the MultiBlockSelector GUI with a specified mode and exclusions. + * + * @param user the user who opens the GUI + * @param mode the mode for filtering (BLOCKS, ITEMS, or ANY) + * @param excluded a set of Materials to exclude + * @param consumer a callback to receive the result + */ + public static void open(User user, Mode mode, Set excluded, + BiConsumer> consumer) { + new MultiBlockSelector(user, mode, excluded, consumer).build(); + } + + /** + * Opens the MultiBlockSelector GUI with default mode (ANY) and no exclusions. + * + * @param user the user who opens the GUI + * @param consumer a callback to receive the result + */ + public static void open(User user, BiConsumer> consumer) { + new MultiBlockSelector(user, Mode.ANY, new HashSet<>(), consumer).build(); + } + + @Override + protected List getElements() { + return Arrays.stream(Material.values()).filter(material -> excluded == null || !excluded.contains(material)) + .filter(material -> { + switch (mode) { + case BLOCKS: + return material.isBlock(); + case ITEMS: + return material.isItem(); + default: + return true; + } + }).sorted(Comparator.comparing(Material::name)).collect(Collectors.toList()); + } + + @Override + protected String getTitleKey() { + return "block-selector"; + } + + @Override + protected String getElementKeyPrefix() { + return "material."; + } + + @Override + protected ItemStack getIcon(Material element) { + return PanelUtils.getMaterialItem(element); + } + + @Override + protected String getElementDisplayName(Material element) { + return Utils.prettifyObject(element, this.user); + } + + @Override + protected String elementToString(Material element) { + return element.name(); + } } diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java index d6d68f57..4e8d9029 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java @@ -1,289 +1,129 @@ package world.bentobox.challenges.panel.util; - -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.function.BiConsumer; import java.util.stream.Collectors; -import org.bukkit.Material; import org.bukkit.entity.EntityType; import org.bukkit.inventory.ItemStack; import lv.id.bonne.panelutils.PanelUtils; -import world.bentobox.bentobox.api.panels.PanelItem; -import world.bentobox.bentobox.api.panels.builders.PanelBuilder; -import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; import world.bentobox.bentobox.api.user.User; -import world.bentobox.challenges.utils.Constants; import world.bentobox.challenges.utils.Utils; - /** - * This class contains all necessary things that allows to select single block from all ingame blocks. Selected - * block will be returned via BiConsumer. + * This class provides a multi-selector GUI for selecting entities. + * It extends the unified multi-selector base class and supplies the + * type-specific implementations required for entity selection. */ -public class MultiEntitySelector extends PagedSelector -{ - private MultiEntitySelector(User user, boolean asEgg, Mode mode, Set excluded, BiConsumer> consumer) - { - super(user); - - this.consumer = consumer; - this.asEgg = asEgg; - this.selectedElements = new HashSet<>(); - - this.elements = Arrays.stream(EntityType.values()). - filter(entity -> !excluded.contains(entity)). - filter(entity -> { - if (mode == Mode.ALIVE) - { - return entity.isAlive(); - } - else - { - return true; - } - }). - // Sort by name - sorted(Comparator.comparing(EntityType::name)). - collect(Collectors.toList()); - // Init without filters applied. - this.filterElements = this.elements; - } - - - /** - * This method opens GUI that allows to select challenge type. - * - * @param user User who opens GUI. - * @param consumer Consumer that allows to get clicked type. - */ - public static void open(User user, boolean asEgg, Mode mode, Set excluded, BiConsumer> consumer) - { - new MultiEntitySelector(user, asEgg, mode, excluded, consumer).build(); - } - - - /** - * This method opens GUI that allows to select challenge type. - * - * @param user User who opens GUI. - * @param consumer Consumer that allows to get clicked type. - */ - public static void open(User user, boolean asEgg, BiConsumer> consumer) - { - new MultiEntitySelector(user, asEgg, Mode.ANY, new HashSet<>(), consumer).build(); - } - - -// --------------------------------------------------------------------- -// Section: Methods -// --------------------------------------------------------------------- - - - /** - * This method builds all necessary elements in GUI panel. - */ - protected void build() - { - PanelBuilder panelBuilder = new PanelBuilder().user(this.user); - panelBuilder.name(this.user.getTranslation(Constants.TITLE + "entity-selector")); - - PanelUtils.fillBorder(panelBuilder, Material.BLUE_STAINED_GLASS_PANE); - - this.populateElements(panelBuilder, this.filterElements); - - panelBuilder.item(3, this.createButton(Button.ACCEPT_SELECTED)); - panelBuilder.item(5, this.createButton(Button.CANCEL)); - - panelBuilder.build(); - } - - - /** - * This method is called when filter value is updated. - */ - @Override - protected void updateFilters() - { - if (this.searchString == null || this.searchString.isBlank()) - { - this.filterElements = this.elements; - } - else - { - this.filterElements = this.elements.stream(). - filter(element -> { - // If element name is set and name contains search field, then do not filter out. - return element.name().toLowerCase().contains(this.searchString.toLowerCase()); - }). - distinct(). - collect(Collectors.toList()); - } - } - - - /** - * This method creates PanelItem button of requested type. - * @param button Button which must be created. - * @return new PanelItem with requested functionality. - */ - private PanelItem createButton(Button button) - { - final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; - - final String name = this.user.getTranslation(reference + "name"); - final List description = new ArrayList<>(3); - description.add(this.user.getTranslation(reference + "description")); - - ItemStack icon; - PanelItem.ClickHandler clickHandler; - - switch (button) - { - case ACCEPT_SELECTED -> { - if (!this.selectedElements.isEmpty()) - { - description.add(this.user.getTranslation(reference + "title")); - this.selectedElements.forEach(material -> - description.add(this.user.getTranslation(reference + "element", - "[element]", Utils.prettifyObject(material, this.user)))); - } - - icon = new ItemStack(Material.COMMAND_BLOCK); - clickHandler = (panel, user1, clickType, slot) -> - { - this.consumer.accept(true, this.selectedElements); - return true; - }; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-save")); - } - case CANCEL -> { - - icon = new ItemStack(Material.IRON_DOOR); - - clickHandler = (panel, user1, clickType, slot) -> - { - this.consumer.accept(false, null); - return true; - }; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-cancel")); - } - default -> { - icon = new ItemStack(Material.PAPER); - clickHandler = null; - } - } - - return new PanelItemBuilder(). - icon(icon). - name(name). - description(description). - clickHandler(clickHandler). - build(); - } - - - /** - * This method creates button for given entity. - * @param entity entity which button must be created. - * @return new Button for entity. - */ - @Override - protected PanelItem createElementButton(EntityType entity) - { - final String reference = Constants.BUTTON + "entity."; - - List description = new ArrayList<>(); - description.add(this.user.getTranslation(reference + "description", - "[id]", entity.name())); - - if (this.selectedElements.contains(entity)) - { - description.add(this.user.getTranslation(reference + "selected")); - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-deselect")); - } - else - { - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-select")); - } - - return new PanelItemBuilder(). - name(this.user.getTranslation(reference + "name", "[entity]", - Utils.prettifyObject(entity, this.user))). - icon(this.asEgg ? PanelUtils.getEntityEgg(entity) : PanelUtils.getEntityHead(entity)). - description(description). - clickHandler((panel, user1, clickType, slot) -> { - // On right click change which entities are selected for deletion. - if (!this.selectedElements.add(entity)) - { - // Remove entity if it is already selected - this.selectedElements.remove(entity); - } - - this.build(); - return true; - }). - glow(this.selectedElements.contains(entity)). - build(); - } - - - /** - * Functional buttons in current GUI. - */ - private enum Button - { - ACCEPT_SELECTED, - CANCEL - } - - - public enum Mode - { - ALIVE, - ANY - } - - -// --------------------------------------------------------------------- -// Section: Variables -// --------------------------------------------------------------------- - - /** - * List with elements that will be displayed in current GUI. - */ - private final List elements; - - /** - * Set that contains selected materials. - */ - private final Set selectedElements; - - /** - * This variable stores consumer. - */ - private final BiConsumer> consumer; - - /** - * Indicates that entity must be displayed as egg. - */ - private final boolean asEgg; - - /** - * Stores filtered items. - */ - private List filterElements; +public class MultiEntitySelector extends UnifiedMultiSelector { + + private final boolean asEgg; + private final Mode mode; + private final Set excluded; + + /** + * Specifies which entities to display. + */ + public enum Mode { + ALIVE, ANY + } + + /** + * Private constructor. + * + * @param user the user opening the selector + * @param asEgg if true, display entities using their spawn egg icon; otherwise, use the entity head + * @param mode determines whether to show only living entities (ALIVE) or all (ANY) + * @param excluded a set of EntityType values to exclude + * @param consumer the callback to be invoked when the user confirms or cancels + */ + private MultiEntitySelector(User user, boolean asEgg, Mode mode, Set excluded, + java.util.function.BiConsumer> consumer) { + super(user, consumer); + this.asEgg = asEgg; + this.mode = mode; + this.excluded = excluded; + } + + /** + * Opens the MultiEntitySelector GUI with the specified parameters. + * + * @param user the user who opens the GUI + * @param asEgg if true, show the entity spawn egg icon; otherwise, show the entity head + * @param mode the filtering mode (ALIVE or ANY) + * @param excluded a set of EntityType values to exclude from the list + * @param consumer a callback to receive the result + */ + public static void open(User user, boolean asEgg, Mode mode, Set excluded, + java.util.function.BiConsumer> consumer) { + new MultiEntitySelector(user, asEgg, mode, excluded, consumer).build(); + } + + /** + * Opens the MultiEntitySelector GUI with default parameters (mode ANY and no exclusions). + * + * @param user the user who opens the GUI + * @param asEgg if true, show the entity spawn egg icon; otherwise, show the entity head + * @param consumer a callback to receive the result + */ + public static void open(User user, boolean asEgg, + java.util.function.BiConsumer> consumer) { + new MultiEntitySelector(user, asEgg, Mode.ANY, new HashSet<>(), consumer).build(); + } + + /** + * Returns the list of EntityType values to display, applying the specified exclusions and mode. + */ + @Override + protected List getElements() { + return Arrays.stream(EntityType.values()).filter(entity -> excluded == null || !excluded.contains(entity)) + .filter(entity -> mode == Mode.ALIVE ? entity.isAlive() : true) + .sorted(Comparator.comparing(EntityType::name)).collect(Collectors.toList()); + } + + /** + * Returns the title key used to form the GUI title. + */ + @Override + protected String getTitleKey() { + return "entity-selector"; + } + + /** + * Returns the translation key prefix for element buttons. + */ + @Override + protected String getElementKeyPrefix() { + return "entity."; + } + + /** + * Returns the icon for the given EntityType. + * If asEgg is true, an entity spawn egg is returned; otherwise, the entity head is returned. + */ + @Override + protected ItemStack getIcon(EntityType element) { + return asEgg ? PanelUtils.getEntityEgg(element) : PanelUtils.getEntityHead(element); + } + + /** + * Returns the display name for the given EntityType. + */ + @Override + protected String getElementDisplayName(EntityType element) { + return Utils.prettifyObject(element, this.user); + } + + /** + * Returns a string representation of the given EntityType used for filtering. + */ + @Override + protected String elementToString(EntityType element) { + return element.name(); + } } diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java index 8131061b..ab4d728a 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java @@ -1,93 +1,200 @@ package world.bentobox.challenges.panel.util; -import java.util.HashSet; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; import java.util.Locale; import java.util.Set; +import java.util.HashSet; +import java.util.function.BiConsumer; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.Registry; import org.bukkit.Tag; import org.bukkit.entity.EntityType; +import org.bukkit.inventory.ItemStack; import world.bentobox.bentobox.api.user.User; +import world.bentobox.challenges.utils.Utils; -public class MultiEntityTypeTagsSelector extends MultiTagsSelector { +/** + * This class provides a multi-selector GUI for selecting entity-type tags. + * It extends the unified multi-selector base class (UnifiedMultiSelector) and supplies + * all tag-specific implementations such as retrieving the tag list, filtering unwanted tags, + * and choosing an appropriate icon. + * + * @see UnifiedMultiSelector + */ +public class MultiEntityTypeTagsSelector extends UnifiedMultiSelector> { - // The ICONS map is empty here, but you could add mappings if needed. - public static final java.util.Map, Material> ICONS = java.util.Map.of(); + private final Mode mode; + private final Set> excluded; + /** + * Defines filtering modes. + */ public enum Mode { ENTITY_TYPE, ANY } + /** + * Private constructor. + * + * @param user the user opening the selector + * @param mode the mode (ENTITY_TYPE or ANY) that might influence filtering behavior + * @param excluded a set of tags to be excluded from display + * @param consumer a callback to receive the selected tags (or cancellation) + */ private MultiEntityTypeTagsSelector(User user, Mode mode, Set> excluded, - java.util.function.BiConsumer>> consumer) { - super(user, excluded, consumer); - // Add mode-specific behavior here if needed. + BiConsumer>> consumer) { + super(user, consumer); + this.mode = mode; // This is not currently used + this.excluded = excluded; } + /** + * Opens the entity-type tag selector GUI with the specified mode and exclusions. + * + * @param user the user who opens the GUI + * @param mode filtering mode (ENTITY_TYPE or ANY) + * @param excluded a set of tags to exclude + * @param consumer a callback to receive the result + */ public static void open(User user, Mode mode, Set> excluded, - java.util.function.BiConsumer>> consumer) { + BiConsumer>> consumer) { new MultiEntityTypeTagsSelector(user, mode, excluded, consumer).build(); } + /** + * Opens the entity-type tag selector GUI with default parameters (mode ANY and no exclusions). + * + * @param user the user who opens the GUI + * @param consumer a callback to receive the result + */ public static void open(User user, - java.util.function.BiConsumer>> consumer) { - open(user, Mode.ANY, new HashSet<>(), consumer); + BiConsumer>> consumer) { + new MultiEntityTypeTagsSelector(user, Mode.ANY, new HashSet<>(), consumer).build(); } + /** + * Retrieves the list of available entity-type tags. + *

+ * This method uses Bukkit’s tag API to get all tags for "entity_types" (of type EntityType), + * sorts them by their key, then removes any that are deemed irrelevant based on their key name + * (for example, tags containing "AXOLOTL", "IMMUNE", etc.) and any tags specified in the excluded set. + *

+ * + * @return a sorted and filtered list of Tag<EntityType> + */ @Override - protected Iterable> getTags() { - return Bukkit.getTags("entity_types", EntityType.class); - } + protected List> getElements() { + List> tagList = new ArrayList<>(); + Iterable> iterable = Bukkit.getTags("entity_types", EntityType.class); + iterable.forEach(tagList::add); + tagList.sort(Comparator.comparing(tag -> tag.getKey().getKey())); - @Override - protected void removeIrrelevantTags() { - elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("AXOLOTL")); - elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("IMMUNE")); - elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("IGNORES")); - elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("FRIEND")); - elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("SENSITIVE")); - elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("PROJECTILE")); - elements.remove(Tag.ENTITY_TYPES_ARROWS); - elements.remove(Tag.ENTITY_TYPES_BEEHIVE_INHABITORS); - elements.remove(Tag.ENTITY_TYPES_CAN_TURN_IN_BOATS); - elements.remove(Tag.ENTITY_TYPES_DISMOUNTS_UNDERWATER); - elements.remove(Tag.ENTITY_TYPES_FALL_DAMAGE_IMMUNE); - elements.remove(Tag.ENTITY_TYPES_FREEZE_HURTS_EXTRA_TYPES); - elements.remove(Tag.ENTITY_TYPES_INVERTED_HEALING_AND_HARM); - elements.remove(Tag.ENTITY_TYPES_NO_ANGER_FROM_WIND_CHARGE); - elements.remove(Tag.ENTITY_TYPES_NON_CONTROLLING_RIDER); - elements.remove(Tag.ENTITY_TYPES_NOT_SCARY_FOR_PUFFERFISH); - elements.remove(Tag.ENTITY_TYPES_FROG_FOOD); - // ... and so on + // Remove irrelevant tags based on key contents. + tagList.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("AXOLOTL")); + tagList.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("IMMUNE")); + tagList.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("IGNORES")); + tagList.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("FRIEND")); + tagList.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("SENSITIVE")); + tagList.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("PROJECTILE")); + + // Remove specific known tags. + tagList.remove(org.bukkit.Tag.ENTITY_TYPES_ARROWS); + tagList.remove(org.bukkit.Tag.ENTITY_TYPES_BEEHIVE_INHABITORS); + tagList.remove(org.bukkit.Tag.ENTITY_TYPES_CAN_TURN_IN_BOATS); + tagList.remove(org.bukkit.Tag.ENTITY_TYPES_DISMOUNTS_UNDERWATER); + tagList.remove(org.bukkit.Tag.ENTITY_TYPES_FALL_DAMAGE_IMMUNE); + tagList.remove(org.bukkit.Tag.ENTITY_TYPES_FREEZE_HURTS_EXTRA_TYPES); + tagList.remove(org.bukkit.Tag.ENTITY_TYPES_INVERTED_HEALING_AND_HARM); + tagList.remove(org.bukkit.Tag.ENTITY_TYPES_NO_ANGER_FROM_WIND_CHARGE); + tagList.remove(org.bukkit.Tag.ENTITY_TYPES_NON_CONTROLLING_RIDER); + tagList.remove(org.bukkit.Tag.ENTITY_TYPES_NOT_SCARY_FOR_PUFFERFISH); + tagList.remove(org.bukkit.Tag.ENTITY_TYPES_FROG_FOOD); + + // Remove any tags specified in the excluded set. + if (excluded != null) { + for (Tag ex : excluded) { + tagList.removeIf(tag -> tag.equals(ex)); + } + } + return tagList; } + /** + * Returns the title key used to build the GUI title. + * + * @return "entity-selector" + */ @Override protected String getTitleKey() { return "entity-selector"; } + /** + * Returns the translation key prefix for individual element buttons. + * + * @return "entity-group." + */ @Override - protected String getElementGroupKey() { + protected String getElementKeyPrefix() { return "entity-group."; } + /** + * Returns the icon for the given entity-type tag. + *

+ * If the tag’s key contains "boat", an oak boat icon is used. Otherwise, the method attempts + * to find any EntityType that is tagged by this tag and constructs a spawn egg material name + * (e.g. "CREEPER_SPAWN_EGG"). If no matching material is found, a PAPER icon is returned. + *

+ * + * @param element the Tag<EntityType> for which to determine the icon + * @return an ItemStack representing the icon + */ @Override - protected Material getIconForTag(Tag tag) { - if (tag.getKey().getKey().contains("boat")) { - return Material.OAK_BOAT; - } - EntityType entType = Registry.ENTITY_TYPE.stream().filter(tag::isTagged).findAny().orElse(null); - if (entType == null) { - return Material.PAPER; - } - String eggName = entType.getKey().getKey().toUpperCase(Locale.ENGLISH) + "_SPAWN_EGG"; - try { - return Material.valueOf(eggName); - } catch (Exception e) { - return Material.PAPER; + protected ItemStack getIcon(Tag element) { + Material iconMaterial; + if (element.getKey().getKey().contains("boat")) { + iconMaterial = Material.OAK_BOAT; + } else { + EntityType entType = Registry.ENTITY_TYPE.stream().filter(element::isTagged).findAny().orElse(null); + if (entType != null) { + String eggName = entType.getKey().getKey().toUpperCase(Locale.ENGLISH) + "_SPAWN_EGG"; + try { + iconMaterial = Material.valueOf(eggName); + } catch (Exception e) { + iconMaterial = Material.PAPER; + } + } else { + iconMaterial = Material.PAPER; + } } + return new ItemStack(iconMaterial); + } + + /** + * Returns the display name for the given tag. + * + * @param element the Tag<EntityType> + * @return a pretty-printed string for display + */ + @Override + protected String getElementDisplayName(Tag element) { + return Utils.prettifyObject(element, this.user); + } + + /** + * Returns a string representation of the tag used for filtering. + * + * @param element the Tag<EntityType> + * @return the tag’s key (as a string) + */ + @Override + protected String elementToString(Tag element) { + return element.getKey().getKey(); } } diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java index 4bd9f69d..bef2e58f 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java @@ -1,117 +1,229 @@ package world.bentobox.challenges.panel.util; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.function.BiConsumer; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.Registry; import org.bukkit.Tag; +import org.bukkit.inventory.ItemStack; import world.bentobox.bentobox.api.user.User; +import world.bentobox.challenges.utils.Utils; -public class MultiMaterialTagsSelector extends MultiTagsSelector { +/** + * This class provides a multi-selector GUI for selecting material tags. + * It extends the unified multi-selector base class and supplies the tag‑specific + * implementations such as retrieving the tag list, filtering unwanted tags, + * selecting an appropriate icon, and generating display names. + * + *

+ * Two static open methods are provided: one that accepts a filtering mode and an excluded set, + * and one that uses default parameters. + *

+ * + * @see UnifiedMultiSelector + */ +public class MultiMaterialTagsSelector extends UnifiedMultiSelector> { + /** + * A map of specific tags to custom icon materials. + */ public static final Map, Material> ICONS = Map.of( - Tag.AIR, Material.BARRIER, - Tag.FIRE, Material.TORCH, - Tag.CANDLE_CAKES, Material.CAKE, - Tag.PORTALS, Material.MAGENTA_STAINED_GLASS_PANE, - Tag.WALL_HANGING_SIGNS, Material.ACACIA_SIGN, - Tag.WALL_SIGNS, Material.OAK_SIGN, - Tag.WALL_CORALS, Material.BUBBLE_CORAL_FAN - // ... add other mappings as needed + Tag.AIR, Material.BARRIER, Tag.FIRE, Material.TORCH, Tag.CANDLE_CAKES, Material.CAKE, Tag.PORTALS, + Material.MAGENTA_STAINED_GLASS_PANE, Tag.WALL_HANGING_SIGNS, Material.ACACIA_SIGN, Tag.WALL_SIGNS, + Material.OAK_SIGN, + Tag.WALL_CORALS, Material.BUBBLE_CORAL_FAN, Tag.CAVE_VINES, Material.VINE ); + private final Mode mode; + private final Set> excluded; + + /** + * Modes for filtering material tags. + */ public enum Mode { BLOCKS, ITEMS, ANY } + /** + * Private constructor. + * + * @param user the user opening the selector + * @param mode filtering mode (BLOCKS, ITEMS, or ANY) + * @param excluded a set of tags to exclude from display + * @param consumer the callback to receive the selected tags or cancellation + */ private MultiMaterialTagsSelector(User user, Mode mode, Set> excluded, - java.util.function.BiConsumer>> consumer) { - super(user, excluded, consumer); - // Future, use the mode to perform additional filtering. + BiConsumer>> consumer) { + super(user, consumer); + this.mode = mode; // Not currently used + this.excluded = excluded; } + /** + * Opens the material tag selector GUI with a specified mode and exclusions. + * + * @param user the user who opens the GUI + * @param mode the filtering mode (BLOCKS, ITEMS, or ANY) + * @param excluded a set of tags to exclude + * @param consumer a callback to receive the result + */ public static void open(User user, Mode mode, Set> excluded, - java.util.function.BiConsumer>> consumer) { + BiConsumer>> consumer) { new MultiMaterialTagsSelector(user, mode, excluded, consumer).build(); } - public static void open(User user, - java.util.function.BiConsumer>> consumer) { - open(user, Mode.ANY, new HashSet<>(), consumer); + /** + * Opens the material tag selector GUI with default parameters (mode ANY and no exclusions). + * + * @param user the user who opens the GUI + * @param consumer a callback to receive the result + */ + public static void open(User user, BiConsumer>> consumer) { + new MultiMaterialTagsSelector(user, Mode.ANY, new HashSet<>(), consumer).build(); } + /** + * Retrieves the list of available material tags. + * + *

+ * This method obtains tags using Bukkit’s tag API for the "blocks" category, + * sorts them by their key name, applies several removeIf filters to eliminate irrelevant tags, + * and then removes any tags provided in the excluded set. + *

+ * + * @return a sorted and filtered list of Tag<Material> + */ @Override - protected Iterable> getTags() { - return Bukkit.getTags("blocks", Material.class); - } + protected List> getElements() { + List> list = new ArrayList<>(); + Iterable> iterable = Bukkit.getTags("blocks", Material.class); + iterable.forEach(list::add); + list.sort(Comparator.comparing(tag -> tag.getKey().getKey())); - @Override - protected void removeIrrelevantTags() { - // Remove irrelevant tags - elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("SPAWNABLE")); - elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("PLACE")); - elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("TEMPT")); - elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("_ON")); - elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("BASE")); - elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("SOUND_BLOCKS")); - elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("DRAGON")); - elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("VALID")); - elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("INCORRECT")); - elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("INFINIBURN")); - elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("MINEABLE")); - elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("TOOL")); - elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("SNIFFER")); - elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("OVERRIDE")); - elements.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("OVERWORLD")); - elements.remove(Tag.BLOCKS_WIND_CHARGE_EXPLOSIONS); - elements.remove(Tag.CONVERTABLE_TO_MUD); - elements.remove(Tag.DAMPENS_VIBRATIONS); - elements.remove(Tag.DOES_NOT_BLOCK_HOPPERS); - elements.remove(Tag.ENCHANTMENT_POWER_PROVIDER); - elements.remove(Tag.ENCHANTMENT_POWER_TRANSMITTER); - elements.remove(Tag.ENDERMAN_HOLDABLE); - elements.remove(Tag.FEATURES_CANNOT_REPLACE); - elements.remove(Tag.FALL_DAMAGE_RESETTING); - elements.remove(Tag.FROG_PREFER_JUMP_TO); - elements.remove(Tag.MAINTAINS_FARMLAND); - elements.remove(Tag.MANGROVE_LOGS_CAN_GROW_THROUGH); - elements.remove(Tag.MANGROVE_ROOTS_CAN_GROW_THROUGH); - elements.remove(Tag.BEE_GROWABLES); - elements.remove(Tag.MOB_INTERACTABLE_DOORS); - elements.remove(Tag.HOGLIN_REPELLENTS); - elements.remove(Tag.PIGLIN_REPELLENTS); - elements.remove(Tag.SNAPS_GOAT_HORN); - elements.remove(Tag.SOUL_SPEED_BLOCKS); - elements.remove(Tag.STRIDER_WARM_BLOCKS); - elements.remove(Tag.SWORD_EFFICIENT); - elements.remove(Tag.UNSTABLE_BOTTOM_CENTER); - elements.remove(Tag.COMPLETES_FIND_TREE_TUTORIAL); - elements.remove(Tag.GUARDED_BY_PIGLINS); - elements.remove(Tag.IMPERMEABLE); - elements.remove(Tag.PREVENT_MOB_SPAWNING_INSIDE); - elements.remove(Tag.SMELTS_TO_GLASS); - elements.remove(Tag.WITHER_IMMUNE); + // Remove irrelevant tags based on their key. + list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("SPAWNABLE")); + list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("PLACE")); + list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("TEMPT")); + list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("_ON")); + list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("BASE")); + list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("SOUND_BLOCKS")); + list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("DRAGON")); + list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("VALID")); + list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("INCORRECT")); + list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("INFINIBURN")); + list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("MINEABLE")); + list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("TOOL")); + list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("SNIFFER")); + list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("OVERRIDE")); + list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("OVERWORLD")); + + // Remove specific known tags. + list.remove(Tag.BLOCKS_WIND_CHARGE_EXPLOSIONS); + list.remove(Tag.CONVERTABLE_TO_MUD); + list.remove(Tag.DAMPENS_VIBRATIONS); + list.remove(Tag.DOES_NOT_BLOCK_HOPPERS); + list.remove(Tag.ENCHANTMENT_POWER_PROVIDER); + list.remove(Tag.ENCHANTMENT_POWER_TRANSMITTER); + list.remove(Tag.ENDERMAN_HOLDABLE); + list.remove(Tag.FEATURES_CANNOT_REPLACE); + list.remove(Tag.FALL_DAMAGE_RESETTING); + list.remove(Tag.FROG_PREFER_JUMP_TO); + list.remove(Tag.MAINTAINS_FARMLAND); + list.remove(Tag.MANGROVE_LOGS_CAN_GROW_THROUGH); + list.remove(Tag.MANGROVE_ROOTS_CAN_GROW_THROUGH); + list.remove(Tag.BEE_GROWABLES); + list.remove(Tag.MOB_INTERACTABLE_DOORS); + list.remove(Tag.HOGLIN_REPELLENTS); + list.remove(Tag.PIGLIN_REPELLENTS); + list.remove(Tag.SNAPS_GOAT_HORN); + list.remove(Tag.SOUL_SPEED_BLOCKS); + list.remove(Tag.STRIDER_WARM_BLOCKS); + list.remove(Tag.SWORD_EFFICIENT); + list.remove(Tag.UNSTABLE_BOTTOM_CENTER); + list.remove(Tag.COMPLETES_FIND_TREE_TUTORIAL); + list.remove(Tag.GUARDED_BY_PIGLINS); + list.remove(Tag.IMPERMEABLE); + list.remove(Tag.PREVENT_MOB_SPAWNING_INSIDE); + list.remove(Tag.SMELTS_TO_GLASS); + list.remove(Tag.WITHER_IMMUNE); + + // Remove any tags specified in the excluded set. + if (excluded != null) { + for (Tag ex : excluded) { + list.removeIf(tag -> tag.equals(ex)); + } + } + return list; } + /** + * Returns the title key used for the GUI. + * + * @return "block-selector" + */ @Override protected String getTitleKey() { return "block-selector"; } + /** + * Returns the translation key prefix for individual element buttons. + * + * @return "block-group." + */ @Override - protected String getElementGroupKey() { + protected String getElementKeyPrefix() { return "block-group."; } + /** + * Returns the icon for the given material tag. + * + *

+ * This method first checks the ICONS map; if a mapping exists for the tag, that material is used. + * Otherwise, it searches through the Bukkit material registry for any material tagged by the given tag + * that is also an item. If none is found, it falls back to PAPER. + *

+ * + * @param element the Tag<Material> for which to determine the icon + * @return an ItemStack representing the icon + */ @Override - protected Material getIconForTag(Tag tag) { - return ICONS.getOrDefault(tag, Registry.MATERIAL.stream().filter(tag::isTagged) + protected ItemStack getIcon(Tag element) { + Material iconMaterial = ICONS.getOrDefault(element, Registry.MATERIAL.stream().filter(element::isTagged) .filter(Material::isItem).findAny().orElse(Material.PAPER)); + return new ItemStack(iconMaterial); } -} + /** + * Returns the display name for the given material tag. + * + * @param element the Tag<Material> + * @return a pretty-printed string for display + */ + @Override + protected String getElementDisplayName(Tag element) { + return Utils.prettifyObject(element, this.user); + } + + /** + * Returns a string representation of the tag used for filtering. + * + * @param element the Tag<Material> + * @return the tag's key (as a string) + */ + @Override + protected String elementToString(Tag element) { + return element.getKey().getKey(); + } +} diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiTagsSelector.java b/src/main/java/world/bentobox/challenges/panel/util/UnifiedMultiSelector.java similarity index 55% rename from src/main/java/world/bentobox/challenges/panel/util/MultiTagsSelector.java rename to src/main/java/world/bentobox/challenges/panel/util/UnifiedMultiSelector.java index 31e85fc9..855020d3 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiTagsSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/UnifiedMultiSelector.java @@ -10,70 +10,80 @@ import java.util.function.BiConsumer; import java.util.stream.Collectors; -import org.bukkit.Keyed; import org.bukkit.Material; -import org.bukkit.Tag; import org.bukkit.inventory.ItemStack; import lv.id.bonne.panelutils.PanelUtils; +import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.panels.PanelItem; import world.bentobox.bentobox.api.panels.builders.PanelBuilder; import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; import world.bentobox.bentobox.api.user.User; import world.bentobox.challenges.utils.Constants; -import world.bentobox.challenges.utils.Utils; /** - * @author tastybento + * Unified abstract class for multi‐selector GUIs. + *

+ * This class provides the common logic for building the GUI, filtering the list, + * and creating the functional buttons. Subclasses must supply the list of available elements + * and type‐specific details such as how to obtain an element’s display name, icon, and + * string representation for filtering. + *

+ * + * @param The type of element shown in the GUI (e.g. Material, EntityType, or Tag<Material>, etc.) */ -public abstract class MultiTagsSelector extends PagedSelector> { +public abstract class UnifiedMultiSelector extends PagedSelector { - // Buttons common to both selectors. - protected enum Button { - ACCEPT_SELECTED, CANCEL - } - - // Common fields. - protected final List> elements = new ArrayList<>(); - protected final Set> selectedElements; - protected final BiConsumer>> consumer; - protected List> filterElements; + protected final List elements; + protected final Set selectedElements; + protected final BiConsumer> consumer; + protected List filterElements; - protected MultiTagsSelector(User user, Set> excluded, BiConsumer>> consumer) { + protected UnifiedMultiSelector(User user, BiConsumer> consumer) { super(user); this.consumer = consumer; this.selectedElements = new HashSet<>(); - // Fill elements using the type‐specific method. - for (Tag tag : getTags()) { - elements.add(tag); - } - elements.sort(Comparator.comparing(tag -> tag.getKey().getKey())); - // Remove irrelevant tags (type‐specific) - removeIrrelevantTags(); - // Remove any tags passed in as excluded. - excluded.forEach(excludedTag -> elements.removeIf(tag -> tag.equals(excludedTag))); - // Initially no filter is applied. - this.filterElements = elements; + // Obtain the complete list of elements from the subclass. + this.elements = getElements(); + // Sort elements using the provided string representation. + this.elements.sort(Comparator.comparing(this::elementToString)); + // Start with the full list as the filtered list. + this.filterElements = this.elements; } - // ABSTRACT METHODS TO BE IMPLEMENTED BY SUBCLASSES: - - /** Return the tags from Bukkit (for example, Bukkit.getTags("blocks", Material.class)). */ - protected abstract Iterable> getTags(); + /** + * Subclasses must return the complete list of available elements. + */ + protected abstract List getElements(); - /** Remove tags that are not needed (e.g. by checking the key string). */ - protected abstract void removeIrrelevantTags(); - - /** Return the translation key used for the panel title. For example, "block-selector" or "entity-selector". */ + /** + * Returns the title key (to be appended to Constants.TITLE) + * for this selector (for example, "entity-selector" or "block-selector"). + */ protected abstract String getTitleKey(); - /** Return the translation key prefix for element buttons (e.g. "block-group." or "entity-group."). */ - protected abstract String getElementGroupKey(); - - /** Return the icon for the given tag. */ - protected abstract Material getIconForTag(Tag tag); - - // COMMON METHODS: + /** + * Returns the translation key prefix used for element buttons + * (for example, "entity." or "material."). + */ + protected abstract String getElementKeyPrefix(); + + /** + * Returns the icon for the given element. + */ + protected abstract ItemStack getIcon(T element); + + /** + * Returns the display name for the given element. + * (For instance, by calling Utils.prettifyObject(element, user)). + */ + protected abstract String getElementDisplayName(T element); + + /** + * Returns a string representation of the element used for filtering. + * (For enums you might simply return element.name().) + */ + protected abstract String elementToString(T element); @Override protected void build() { @@ -81,8 +91,11 @@ protected void build() { panelBuilder.name(this.user.getTranslation(Constants.TITLE + getTitleKey())); PanelUtils.fillBorder(panelBuilder, Material.BLUE_STAINED_GLASS_PANE); + + // Populate the GUI with the filtered list. this.populateElements(panelBuilder, this.filterElements); + // Add functional buttons. panelBuilder.item(3, createButton(Button.ACCEPT_SELECTED)); panelBuilder.item(5, createButton(Button.CANCEL)); @@ -95,7 +108,7 @@ protected void updateFilters() { this.filterElements = this.elements; } else { this.filterElements = this.elements.stream() - .filter(tag -> tag.getKey().getKey().toLowerCase(Locale.ENGLISH) + .filter(element -> elementToString(element).toLowerCase(Locale.ENGLISH) .contains(this.searchString.toLowerCase(Locale.ENGLISH))) .distinct().collect(Collectors.toList()); } @@ -114,15 +127,16 @@ private PanelItem createButton(Button button) { case ACCEPT_SELECTED -> { if (!this.selectedElements.isEmpty()) { description.add(this.user.getTranslation(reference + "title")); - this.selectedElements.forEach(tag -> description.add(this.user.getTranslation(reference + "element", - "[element]", Utils.prettifyObject(tag, this.user)))); + for (T element : this.selectedElements) { + description.add(this.user.getTranslation(reference + "element", "[element]", + getElementDisplayName(element))); + } } icon = new ItemStack(Material.COMMAND_BLOCK); clickHandler = (panel, user1, clickType, slot) -> { this.consumer.accept(true, this.selectedElements); return true; }; - description.add(""); description.add(this.user.getTranslation(Constants.TIPS + "click-to-save")); } @@ -145,12 +159,12 @@ private PanelItem createButton(Button button) { } @Override - protected PanelItem createElementButton(Tag tag) { - final String reference = Constants.BUTTON + getElementGroupKey(); + protected PanelItem createElementButton(T element) { + final String reference = Constants.BUTTON + getElementKeyPrefix(); List description = new ArrayList<>(); - description.add(this.user.getTranslation(reference + "description", "[id]", tag.getKey().getKey())); + description.add(this.user.getTranslation(reference + "description", "[id]", elementToString(element))); - if (this.selectedElements.contains(tag)) { + if (this.selectedElements.contains(element)) { description.add(this.user.getTranslation(reference + "selected")); description.add(""); description.add(this.user.getTranslation(Constants.TIPS + "click-to-deselect")); @@ -160,14 +174,19 @@ protected PanelItem createElementButton(Tag tag) { } return new PanelItemBuilder() - .name(this.user.getTranslation(reference + "name", "[tag]", Utils.prettifyObject(tag, this.user))) - .icon(getIconForTag(tag)).description(description).clickHandler((panel, user1, clickType, slot) -> { - // Toggle selection. - if (!this.selectedElements.add(tag)) { - this.selectedElements.remove(tag); + .name(this.user.getTranslation(reference + "name", "[id]", + getElementDisplayName(element))) + .icon(getIcon(element)).description(description).clickHandler((panel, user1, clickType, slot) -> { + // Toggle the selection state. + if (!this.selectedElements.add(element)) { + this.selectedElements.remove(element); } this.build(); return true; - }).glow(this.selectedElements.contains(tag)).build(); + }).glow(this.selectedElements.contains(element)).build(); + } + + protected enum Button { + ACCEPT_SELECTED, CANCEL } } diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index dae87d9b..1d2803d8 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -675,14 +675,14 @@ challenges: &7 selected block groups &7 from the list. title: "&7 Selected Block Groups:" - material: "&8 - [tag]" + material: "&8 - [material]" material: name: "&f&l [material]" description: |- &7 Material ID: [id] selected: "&2 Selected" block-group: - name: "&f&l [tag]" + name: "&f&l [id]" description: "" selected: "&2 Selected" add_entity: @@ -722,7 +722,7 @@ challenges: &7 Entity ID: [id] selected: "&2 Selected" entity-group: - name: "&f&l [tag]" + name: "&f&l [id]" description: "" selected: "&2 Selected" inventory_type: From 435764a61559d74dcd8429a703ce6a14637eef4a Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 6 Feb 2025 21:44:23 -0800 Subject: [PATCH 11/34] Add NPE protection --- .../challenges/panel/admin/ManageEntityGroupsPanel.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java index 2274012b..e607ed11 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java @@ -316,6 +316,9 @@ protected PanelItem createElementButton(Tag tag) return new ItemStack(Material.OAK_PLANKS, quantity); // Boats cannot be stacked } EntityType entType = Registry.ENTITY_TYPE.stream().filter(entityTag::isTagged).findAny().orElse(null); + if (entType == null) { + return new ItemStack(Material.PAPER, quantity); + } String eggName = entType.getKey().getKey().toUpperCase(Locale.ENGLISH) + "_SPAWN_EGG"; Material result; try { From c3397a1d26e08ffc8e506ed298dd5ef62e6ebe0a Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 6 Feb 2025 21:47:40 -0800 Subject: [PATCH 12/34] Tighten up minimum dependencies --- pom.xml | 2 +- src/main/resources/addon.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pom.xml b/pom.xml index 5308ae8f..e3f596c3 100644 --- a/pom.xml +++ b/pom.xml @@ -44,7 +44,7 @@ 1.21.3-R0.1-SNAPSHOT 1.2.3-SNAPSHOT - 3.0.0-SNAPSHOT + 3.2.4-SNAPSHOT 2.6.3 1.7 1.2.0 diff --git a/src/main/resources/addon.yml b/src/main/resources/addon.yml index 8cd6b891..8c70bcde 100755 --- a/src/main/resources/addon.yml +++ b/src/main/resources/addon.yml @@ -1,7 +1,7 @@ name: Challenges main: world.bentobox.challenges.ChallengesAddon version: ${version}${build.number} -api-version: 2.7.1 +api-version: 3.2.4 repository: 'BentoBoxWorld/Challenges' metrics: true @@ -9,7 +9,7 @@ authors: - tastybento - BONNe -softdepend: AcidIsland, BSkyBlock, CaveBlock, SkyGrid, Level +softdepend: AcidIsland, BSkyBlock, CaveBlock, SkyGrid, Level, Poseidon, Boxed permissions: addon.admin.challenges: From 93f7fd15616d975936f597efc3e6943ed50f364d Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 6 Feb 2025 21:54:24 -0800 Subject: [PATCH 13/34] Version 1.5.0 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index e3f596c3..ebe3f7cd 100644 --- a/pom.xml +++ b/pom.xml @@ -51,7 +51,7 @@ ${build.version}-SNAPSHOT - 1.4.0 + 1.5.0 -LOCAL BentoBoxWorld_Challenges From b5a9296cfa3e6ca4971a5a217a860473928e1ec7 Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 7 Feb 2025 08:28:27 -0800 Subject: [PATCH 14/34] Reduce code duplication by abstracting classes --- .../panel/admin/AbstractManageEnumPanel.java | 162 ++++++ .../panel/admin/ManageBlocksPanel.java | 459 ++++++---------- .../panel/admin/ManageEntitiesPanel.java | 498 +++++++----------- .../bentobox/challenges/utils/Utils.java | 20 + 4 files changed, 542 insertions(+), 597 deletions(-) create mode 100644 src/main/java/world/bentobox/challenges/panel/admin/AbstractManageEnumPanel.java diff --git a/src/main/java/world/bentobox/challenges/panel/admin/AbstractManageEnumPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/AbstractManageEnumPanel.java new file mode 100644 index 00000000..02b0d43b --- /dev/null +++ b/src/main/java/world/bentobox/challenges/panel/admin/AbstractManageEnumPanel.java @@ -0,0 +1,162 @@ +package world.bentobox.challenges.panel.admin; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import org.bukkit.inventory.ItemStack; + +import lv.id.bonne.panelutils.PanelUtils; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.builders.PanelBuilder; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.challenges.panel.CommonPagedPanel; +import world.bentobox.challenges.panel.CommonPanel; +import world.bentobox.challenges.panel.ConversationUtils; +import world.bentobox.challenges.utils.Constants; +import world.bentobox.challenges.utils.Utils; + +/** + * Abstract class that works with any enum type. + * This class contains all the common logic: filtering, building the panel, creating the element buttons, etc. + * @author tastybento + */ +public abstract class AbstractManageEnumPanel> extends CommonPagedPanel { + + protected final Map itemsMap; + protected final List itemList; + protected final Set selectedItems; + protected List filterElements; + + protected AbstractManageEnumPanel(CommonPanel parentGUI, Map itemsMap) { + super(parentGUI); + this.itemsMap = itemsMap; + this.itemList = new ArrayList<>(itemsMap.keySet()); + // Sort by the enum name (alphabetical order) + this.itemList.sort(Comparator.comparing(Enum::name)); + this.selectedItems = new HashSet<>(); + this.filterElements = this.itemList; + } + + /** + * Update the filter list based on the search string. + */ + @Override + protected void updateFilters() { + if (this.searchString == null || this.searchString.isBlank()) { + this.filterElements = this.itemList; + } else { + this.filterElements = this.itemList.stream() + .filter(element -> element.name().toLowerCase().contains(this.searchString.toLowerCase())) + .distinct().collect(Collectors.toList()); + } + } + + /** + * Creates a button for an element. + */ + @Override + protected PanelItem createElementButton(T element) { + final String reference = getElementTranslationPrefix(); + List description = new ArrayList<>(); + + if (selectedItems.contains(element)) { + description.add(this.user.getTranslation(reference + "selected")); + } + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "left-click-to-choose")); + + if (selectedItems.contains(element)) { + description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-deselect")); + } else { + description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-select")); + } + + return new PanelItemBuilder() + .name(this.user.getTranslation(reference + "name", getElementPlaceholder(), + Utils.prettifyObject(element, this.user))) + .icon(getElementIcon(element, itemsMap.get(element))).description(description) + .clickHandler((panel, user1, clickType, slot) -> { + // On right click, toggle selection. + if (clickType.isRightClick()) { + if (!selectedItems.add(element)) { + selectedItems.remove(element); + } + this.build(); + } else { + // On left click, open a numeric input conversation. + Consumer numberConsumer = number -> { + if (number != null) { + itemsMap.put(element, number.intValue()); + } + this.build(); + }; + + ConversationUtils.createNumericInput(numberConsumer, this.user, + this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 1, + Integer.MAX_VALUE); + } + return true; + }).glow(selectedItems.contains(element)).build(); + } + + /** + * Build the panel. + */ + @Override + protected void build() { + PanelBuilder panelBuilder = new PanelBuilder().user(this.user) + .name(this.user.getTranslation(getPanelTitleKey())); + + // Create a border. + PanelUtils.fillBorder(panelBuilder); + + // Add the functional buttons (buttons like add, remove, etc.) + addFunctionalButtons(panelBuilder); + + // Populate the panel with the filtered items. + populateElements(panelBuilder, this.filterElements); + + // Add the return button. + panelBuilder.item(getReturnButtonSlot(), this.returnButton); + + panelBuilder.build(); + } + + protected int getReturnButtonSlot() { + return 44; + } + + // --- Abstract methods that concrete subclasses must implement --- + + /** + * Returns the ItemStack icon for a given element. + */ + protected abstract ItemStack getElementIcon(T element, int count); + + /** + * Returns the translation prefix for element buttons (e.g. "button.material." or "button.entity."). + */ + protected abstract String getElementTranslationPrefix(); + + /** + * Returns the placeholder used in translation for the element (e.g. "[material]" or "[entity]"). + */ + protected abstract String getElementPlaceholder(); + + /** + * Returns the translation key for the panel title. + */ + protected abstract String getPanelTitleKey(); + + /** + * Adds all the functional (non-element) buttons to the panel. + */ + protected abstract void addFunctionalButtons(PanelBuilder panelBuilder); +} diff --git a/src/main/java/world/bentobox/challenges/panel/admin/ManageBlocksPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/ManageBlocksPanel.java index 109c4516..f0ebbeb6 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/ManageBlocksPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/ManageBlocksPanel.java @@ -1,14 +1,9 @@ package world.bentobox.challenges.panel.admin; - import java.util.ArrayList; -import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.function.Consumer; -import java.util.stream.Collectors; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; @@ -17,296 +12,182 @@ import world.bentobox.bentobox.api.panels.PanelItem; import world.bentobox.bentobox.api.panels.builders.PanelBuilder; import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; -import world.bentobox.challenges.panel.CommonPagedPanel; import world.bentobox.challenges.panel.CommonPanel; -import world.bentobox.challenges.panel.ConversationUtils; import world.bentobox.challenges.panel.util.MultiBlockSelector; import world.bentobox.challenges.utils.Constants; import world.bentobox.challenges.utils.Utils; - /** - * This class allows to edit material that are in required material map. + * The ManageBlocksPanel class provides a graphical interface for administrators + * to manage a collection of materials along with their required counts. + *

+ * It extends the abstract generic class {@code AbstractManageEnumPanel}, + * which provides common functionality for panels that handle paginated elements. + * This panel allows adding new materials using a multi-selection tool and + * removing selected materials. When a material is clicked (left or right), a + * numeric input conversation can be started to update the count associated with + * that material. + *

+ * + *

Usage: To display this panel, call the static {@link #open(CommonPanel, Map)} + * method with the parent panel and the map of materials to their counts.

*/ -public class ManageBlocksPanel extends CommonPagedPanel -{ - private ManageBlocksPanel(CommonPanel parentGUI, Map materialMap) - { - super(parentGUI); - this.materialMap = materialMap; - this.materialList = new ArrayList<>(this.materialMap.keySet()); - - // Sort materials by their ordinal value. - this.materialList.sort(Comparator.comparing(Enum::name)); - - this.selectedMaterials = new HashSet<>(); - - // Init without filters applied. - this.filterElements = this.materialList; - } - - - /** - * Open the Challenges Admin GUI. - */ - public static void open(CommonPanel parentGUI, Map materialMap) - { - new ManageBlocksPanel(parentGUI, materialMap).build(); - } - - -// --------------------------------------------------------------------- -// Section: Methods -// --------------------------------------------------------------------- - - - /** - * This method is called when filter value is updated. - */ - @Override - protected void updateFilters() - { - if (this.searchString == null || this.searchString.isBlank()) - { - this.filterElements = this.materialList; - } - else - { - this.filterElements = this.materialList.stream(). - filter(element -> { - // If element name is set and name contains search field, then do not filter out. - return element.name().toLowerCase().contains(this.searchString.toLowerCase()); - }). - distinct(). - collect(Collectors.toList()); - } - } - - - /** - * This method builds all necessary elements in GUI panel. - */ - @Override - protected void build() - { - PanelBuilder panelBuilder = new PanelBuilder().user(this.user). - name(this.user.getTranslation(Constants.TITLE + "manage-blocks")); - - // Create nice border. - PanelUtils.fillBorder(panelBuilder); - - panelBuilder.item(3, this.createButton(Button.ADD_BLOCK)); - panelBuilder.item(5, this.createButton(Button.REMOVE_BLOCK)); - - this.populateElements(panelBuilder, this.filterElements); - - // Add return button. - panelBuilder.item(44, this.returnButton); - - panelBuilder.build(); - } - - - /** - * This method creates PanelItem button of requested type. - * @param button Button which must be created. - * @return new PanelItem with requested functionality. - */ - private PanelItem createButton(Button button) - { - final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; - - final String name = this.user.getTranslation(reference + "name"); - final List description = new ArrayList<>(3); - description.add(this.user.getTranslation(reference + "description")); - - ItemStack icon; - PanelItem.ClickHandler clickHandler; - boolean glow; - - switch (button) - { - case ADD_BLOCK -> { - icon = new ItemStack(Material.BUCKET); - clickHandler = (panel, user1, clickType, slot) -> - { - MultiBlockSelector.open(this.user, - MultiBlockSelector.Mode.BLOCKS, - new HashSet<>(this.materialList), - (status, materials) -> - { - if (status) - { - materials.forEach(material -> - { - this.materialMap.put(material, 1); - this.materialList.add(material); - }); - } - - this.build(); - }); - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-add")); - } - case REMOVE_BLOCK -> { - - if (!this.selectedMaterials.isEmpty()) - { - description.add(this.user.getTranslation(reference + "title")); - this.selectedMaterials.forEach(material -> - description.add(this.user.getTranslation(reference + "material", - "[material]", Utils.prettifyObject(material, this.user)))); - } - - icon = new ItemStack(Material.LAVA_BUCKET); - - clickHandler = (panel, user1, clickType, slot) -> - { - if (!this.selectedMaterials.isEmpty()) - { - this.materialMap.keySet().removeAll(this.selectedMaterials); - this.materialList.removeAll(this.selectedMaterials); - this.selectedMaterials.clear(); - this.build(); - } - - return true; - }; - - glow = !this.selectedMaterials.isEmpty(); - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-remove")); - } - default -> { - icon = new ItemStack(Material.PAPER); - clickHandler = null; - glow = false; - } - } - - return new PanelItemBuilder(). - icon(icon). - name(name). - description(description). - clickHandler(clickHandler). - glow(glow). - build(); - } - - - /** - * This method creates button for given material. - * @param material material which button must be created. - * @return new Button for material. - */ - @Override - protected PanelItem createElementButton(Material material) - { - final String reference = Constants.BUTTON + "material."; - - List description = new ArrayList<>(); - - if (this.selectedMaterials.contains(material)) - { - description.add(this.user.getTranslation(reference + "selected")); - } - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "left-click-to-choose")); - - if (this.selectedMaterials.contains(material)) - { - description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-deselect")); - } - else - { - description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-select")); - } - - return new PanelItemBuilder(). - name(this.user.getTranslation(reference + "name", "[material]", - Utils.prettifyObject(material, this.user))). - icon(PanelUtils.getMaterialItem(material, this.materialMap.get(material))). - description(description). - clickHandler((panel, user1, clickType, slot) -> { - // On right click change which entities are selected for deletion. - if (clickType.isRightClick()) - { - if (!this.selectedMaterials.add(material)) - { - // Remove material if it is already selected - this.selectedMaterials.remove(material); - } - - this.build(); - } - else - { - Consumer numberConsumer = number -> { - if (number != null) - { - this.materialMap.put(material, number.intValue()); - } - - // reopen panel - this.build(); - }; - - ConversationUtils.createNumericInput(numberConsumer, - this.user, - this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), - 1, - Integer.MAX_VALUE); - } - return true; - }). - glow(this.selectedMaterials.contains(material)). - build(); - } - - -// --------------------------------------------------------------------- -// Section: Enums -// --------------------------------------------------------------------- - - - /** - * Functional buttons in current GUI. - */ - private enum Button - { - ADD_BLOCK, +public class ManageBlocksPanel extends AbstractManageEnumPanel { + + /** + * Private constructor that initializes the ManageBlocksPanel with the provided + * material map. + * + * @param parentGUI The parent panel that spawns this panel. + * @param materialMap A map of Material objects to their required counts. + */ + private ManageBlocksPanel(CommonPanel parentGUI, Map materialMap) { + super(parentGUI, materialMap); + } + + /** + * Opens the Manage Blocks panel. + * + * @param parentGUI The parent panel that spawns this panel. + * @param materialMap A map of Material objects to their required counts. + */ + public static void open(CommonPanel parentGUI, Map materialMap) { + new ManageBlocksPanel(parentGUI, materialMap).build(); + } + + /** + * Provides the icon for a material element. + * + * @param material The material for which the icon is required. + * @param count The count to be displayed on the icon. + * @return An ItemStack representing the material icon. + */ + @Override + protected ItemStack getElementIcon(Material material, int count) { + return PanelUtils.getMaterialItem(material, count); + } + + /** + * Returns the translation prefix used for buttons related to materials. + * + * @return A string containing the translation prefix for material buttons. + */ + @Override + protected String getElementTranslationPrefix() { + return Constants.BUTTON + "material."; + } + + /** + * Returns the placeholder key used in translations for a material. + * + * @return The placeholder key for materials. + */ + @Override + protected String getElementPlaceholder() { + return "[material]"; + } + + /** + * Returns the translation key for the title of the panel. + * + * @return A string containing the translation key for the panel title. + */ + @Override + protected String getPanelTitleKey() { + return Constants.TITLE + "manage-blocks"; + } + + /** + * Adds functional buttons (e.g., Add and Remove) to the panel. + * + * @param panelBuilder The PanelBuilder used to construct the panel. + */ + @Override + protected void addFunctionalButtons(PanelBuilder panelBuilder) { + // Position 3: Button for adding new materials. + panelBuilder.item(3, createButton(Button.ADD_BLOCK)); + // Position 5: Button for removing selected materials. + panelBuilder.item(5, createButton(Button.REMOVE_BLOCK)); + } + + /** + * Creates a functional button based on the specified type. + * + * @param button The button type to create. + * @return A PanelItem representing the functional button. + */ + private PanelItem createButton(Button button) { + final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; + final String name = this.user.getTranslation(reference + "name"); + final List description = new ArrayList<>(3); + description.add(this.user.getTranslation(reference + "description")); + + ItemStack icon; + PanelItem.ClickHandler clickHandler; + boolean glow; + + switch (button) { + case ADD_BLOCK -> { + icon = new ItemStack(Material.BUCKET); + clickHandler = (panel, user, clickType, slot) -> { + // Open a multi-selection tool to add new materials. + MultiBlockSelector.open(this.user, MultiBlockSelector.Mode.BLOCKS, new HashSet<>(this.itemList), + (status, materials) -> { + if (status) { + // For each selected material, add it to the map with a default count. + materials.forEach(material -> { + this.itemsMap.put(material, 1); + this.itemList.add(material); + }); + } + // Rebuild the panel to reflect changes. + this.build(); + }); + return true; + }; + glow = false; + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-add")); + } + case REMOVE_BLOCK -> { + if (!this.selectedItems.isEmpty()) { + // If any materials are selected, list them in the description. + description.add(this.user.getTranslation(reference + "title")); + this.selectedItems.forEach(material -> description.add(this.user.getTranslation(reference + "material", + "[material]", Utils.prettifyObject(material, this.user)))); + } + icon = new ItemStack(Material.LAVA_BUCKET); + clickHandler = (panel, user, clickType, slot) -> { + if (!this.selectedItems.isEmpty()) { + // Remove all selected materials from the map and list. + this.itemsMap.keySet().removeAll(this.selectedItems); + this.itemList.removeAll(this.selectedItems); + this.selectedItems.clear(); + // Rebuild the panel after removal. + this.build(); + } + return true; + }; + glow = !this.selectedItems.isEmpty(); + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-remove")); + } + default -> { + icon = new ItemStack(Material.PAPER); + clickHandler = null; + glow = false; + } + } + + return new PanelItemBuilder().icon(icon).name(name).description(description).clickHandler(clickHandler) + .glow(glow).build(); + } + + /** + * Enumeration of functional buttons in the Manage Blocks panel. + */ + private enum Button { + ADD_BLOCK, REMOVE_BLOCK - } - - -// --------------------------------------------------------------------- -// Section: Variables -// --------------------------------------------------------------------- - - /** - * Contains selected materials. - */ - private final Set selectedMaterials; - - /** - * List of materials to avoid order issues. - */ - private final List materialList; - - /** - * List of required materials. - */ - private final Map materialMap; - - /** - * Stores filtered items. - */ - private List filterElements; + } } diff --git a/src/main/java/world/bentobox/challenges/panel/admin/ManageEntitiesPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/ManageEntitiesPanel.java index 951467b1..6df39307 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/ManageEntitiesPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/ManageEntitiesPanel.java @@ -1,14 +1,8 @@ package world.bentobox.challenges.panel.admin; - import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.function.Consumer; -import java.util.stream.Collectors; import org.bukkit.Material; import org.bukkit.entity.EntityType; @@ -18,313 +12,201 @@ import world.bentobox.bentobox.api.panels.PanelItem; import world.bentobox.bentobox.api.panels.builders.PanelBuilder; import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; -import world.bentobox.challenges.panel.CommonPagedPanel; import world.bentobox.challenges.panel.CommonPanel; -import world.bentobox.challenges.panel.ConversationUtils; import world.bentobox.challenges.panel.util.MultiEntitySelector; import world.bentobox.challenges.utils.Constants; import world.bentobox.challenges.utils.Utils; - /** - * This class allows to edit entities that are in required entities map. + * The ManageEntitiesPanel class provides a graphical interface for administrators + * to manage a collection of entities (of type {@link EntityType}) along with their required counts. + *

+ * This panel enables adding new entities via a multi-selection tool, removing selected entities, + * and toggling the display between entity eggs and mob heads. It extends the generic + * {@code AbstractManageEnumPanel} to share common functionality with other paginated panels. + *

+ * + *

Usage: To display this panel, call the static {@link #open(CommonPanel, Map)} + * method with the parent panel and the map of entities to their counts.

*/ -public class ManageEntitiesPanel extends CommonPagedPanel -{ - private ManageEntitiesPanel(CommonPanel parentGUI, Map requiredEntities) - { - super(parentGUI); - this.requiredEntities = requiredEntities; - - this.entityList = new ArrayList<>(this.requiredEntities.keySet()); - this.entityList.sort(Comparator.comparing(Enum::name)); - - this.selectedEntities = new HashSet<>(EntityType.values().length); - this.filterElements = this.entityList; - } - - - /** - * Open the Challenges Admin GUI. - */ - public static void open(CommonPanel parentGUI, Map requiredEntities) - { - new ManageEntitiesPanel(parentGUI, requiredEntities).build(); - } - - -// --------------------------------------------------------------------- -// Section: Methods -// --------------------------------------------------------------------- - - - /** - * This method is called when filter value is updated. - */ - @Override - protected void updateFilters() - { - if (this.searchString == null || this.searchString.isBlank()) - { - this.filterElements = this.entityList; - } - else - { - this.filterElements = this.entityList.stream(). - filter(element -> { - // If element name is set and name contains search field, then do not filter out. - return element.name().toLowerCase().contains(this.searchString.toLowerCase()); - }). - distinct(). - collect(Collectors.toList()); - } - } - - - /** - * This method builds all necessary elements in GUI panel. - */ - @Override - protected void build() - { - PanelBuilder panelBuilder = new PanelBuilder().user(this.user). - name(this.user.getTranslation(Constants.TITLE + "manage-entities")); - - // create border - PanelUtils.fillBorder(panelBuilder); - - panelBuilder.item(3, this.createButton(Button.ADD_ENTITY)); - panelBuilder.item(5, this.createButton(Button.REMOVE_ENTITY)); - panelBuilder.item(8, this.createButton(Button.SWITCH_ENTITY)); - - this.populateElements(panelBuilder, this.filterElements); - - // Add return button. - panelBuilder.item(44, this.returnButton); - - panelBuilder.build(); - } - - - /** - * This method creates PanelItem button of requested type. - * @param button Button which must be created. - * @return new PanelItem with requested functionality. - */ - private PanelItem createButton(Button button) - { - final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; - - final String name = this.user.getTranslation(reference + "name"); - final List description = new ArrayList<>(3); - description.add(this.user.getTranslation(reference + "description")); - - ItemStack icon; - PanelItem.ClickHandler clickHandler; - boolean glow; - - switch (button) - { - case ADD_ENTITY -> { - icon = new ItemStack(Material.BUCKET); - clickHandler = (panel, user1, clickType, slot) -> { - MultiEntitySelector.open(this.user, - this.asEggs, - MultiEntitySelector.Mode.ALIVE, - this.requiredEntities.keySet(), - (status, entities) -> { - if (status) - { - entities.forEach(entity -> { - this.requiredEntities.put(entity, 1); - this.entityList.add(entity); - }); - } - - this.build(); - }); - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-add")); - } - case REMOVE_ENTITY -> { - - if (!this.selectedEntities.isEmpty()) - { - description.add(this.user.getTranslation(reference + "title")); - this.selectedEntities.forEach(entity -> - description.add(this.user.getTranslation(reference + "entity", - "[entity]", Utils.prettifyObject(entity, this.user)))); - } - - icon = new ItemStack(Material.LAVA_BUCKET); - - clickHandler = (panel, user1, clickType, slot) -> - { - if (!this.selectedEntities.isEmpty()) - { - this.requiredEntities.keySet().removeAll(this.selectedEntities); - this.entityList.removeAll(this.selectedEntities); - this.selectedEntities.clear(); - this.build(); - } - - return true; - }; - - glow = !this.entityList.isEmpty(); - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-remove")); - } - case SWITCH_ENTITY -> { - icon = new ItemStack(this.asEggs ? Material.EGG : Material.PLAYER_HEAD); - - clickHandler = (panel, user1, clickType, slot) -> { - this.asEggs = !this.asEggs; - this.build(); - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); - } - default -> { - icon = new ItemStack(Material.PAPER); - clickHandler = null; - glow = false; - } - } - - return new PanelItemBuilder(). - icon(icon). - name(name). - description(description). - clickHandler(clickHandler). - glow(glow). - build(); - } - - - /** - * This method creates button for given entity. - * @param entity Entity which button must be created. - * @return new Button for entity. - */ - @Override - protected PanelItem createElementButton(EntityType entity) - { - final String reference = Constants.BUTTON + "entity."; - - List description = new ArrayList<>(); - - if (this.selectedEntities.contains(entity)) - { - description.add(this.user.getTranslation(reference + "selected")); - } - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "left-click-to-choose")); - - if (this.selectedEntities.contains(entity)) - { - description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-deselect")); - } - else - { - description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-select")); - } - - return new PanelItemBuilder(). - name(this.user.getTranslation(reference + "name", "[entity]", - Utils.prettifyObject(entity, this.user))). - icon(this.asEggs ? - PanelUtils.getEntityEgg(entity, this.requiredEntities.get(entity)) : - PanelUtils.getEntityHead(entity, this.requiredEntities.get(entity))). - description(description). - clickHandler((panel, user1, clickType, slot) -> { - // On right click change which entities are selected for deletion. - if (clickType.isRightClick()) - { - if (!this.selectedEntities.add(entity)) - { - // Remove entity if it is already selected - this.selectedEntities.remove(entity); - } - - this.build(); - } - else - { - Consumer numberConsumer = number -> { - if (number != null) - { - this.requiredEntities.put(entity, number.intValue()); - } - - // reopen panel - this.build(); - }; - - ConversationUtils.createNumericInput(numberConsumer, - this.user, - this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), - 1, - Integer.MAX_VALUE); - } - return true; - }). - glow(this.selectedEntities.contains(entity)). - build(); - } - - -// --------------------------------------------------------------------- -// Section: Enums -// --------------------------------------------------------------------- - - - /** - * Functional buttons in current GUI. - */ - private enum Button - { - ADD_ENTITY, - REMOVE_ENTITY, - SWITCH_ENTITY - } - - -// --------------------------------------------------------------------- -// Section: Variables -// --------------------------------------------------------------------- - - /** - * List with entities to avoid list irregularities. - */ - private final List entityList; - - /** - * Set with entities that are selected. - */ - private final Set selectedEntities; - - /** - * Map that contains all entities and their cound. - */ - private final Map requiredEntities; - - /** - * Boolean indicate if entities should be displayed as eggs or mob heads. - */ - private boolean asEggs; - - /** - * Stores filtered items. - */ - private List filterElements; +public class ManageEntitiesPanel extends AbstractManageEnumPanel { + + /** + * Flag to indicate whether entities should be displayed as eggs (true) or mob heads (false). + */ + private boolean asEggs = true; + + /** + * Private constructor that initializes the ManageEntitiesPanel with the provided + * entities map. + * + * @param parentGUI The parent panel that spawns this panel. + * @param requiredEntities A map of EntityType objects to their required counts. + */ + private ManageEntitiesPanel(CommonPanel parentGUI, Map requiredEntities) { + super(parentGUI, requiredEntities); + } + + /** + * Opens the Manage Entities panel. + * + * @param parentGUI The parent panel that spawns this panel. + * @param requiredEntities A map of EntityType objects to their required counts. + */ + public static void open(CommonPanel parentGUI, Map requiredEntities) { + new ManageEntitiesPanel(parentGUI, requiredEntities).build(); + } + + /** + * Provides the icon for an entity element. + *

+ * Depending on the {@code asEggs} flag, this method returns either the egg icon or the mob head icon. + *

+ * + * @param entity The entity for which the icon is required. + * @param count The count to be displayed on the icon. + * @return An ItemStack representing the entity icon. + */ + @Override + protected ItemStack getElementIcon(EntityType entity, int count) { + return asEggs ? PanelUtils.getEntityEgg(entity, count) : PanelUtils.getEntityHead(entity, count); + } + + /** + * Returns the translation prefix used for buttons related to entities. + * + * @return A string containing the translation prefix for entity buttons. + */ + @Override + protected String getElementTranslationPrefix() { + return Constants.BUTTON + "entity."; + } + + /** + * Returns the placeholder key used in translations for an entity. + * + * @return The placeholder key for entities. + */ + @Override + protected String getElementPlaceholder() { + return "[entity]"; + } + + /** + * Returns the translation key for the title of the panel. + * + * @return A string containing the translation key for the panel title. + */ + @Override + protected String getPanelTitleKey() { + return Constants.TITLE + "manage-entities"; + } + + /** + * Adds functional buttons (e.g., Add, Remove, and Switch Display Mode) to the panel. + * + * @param panelBuilder The PanelBuilder used to construct the panel. + */ + @Override + protected void addFunctionalButtons(PanelBuilder panelBuilder) { + // Position 3: Button for adding new entities. + panelBuilder.item(3, createButton(Button.ADD_ENTITY)); + // Position 5: Button for removing selected entities. + panelBuilder.item(5, createButton(Button.REMOVE_ENTITY)); + // Position 8: Button to switch between displaying entity eggs and mob heads. + panelBuilder.item(8, createButton(Button.SWITCH_ENTITY)); + } + + /** + * Creates a functional button based on the specified action. + * + * @param button The button type to create. + * @return A PanelItem representing the functional button. + */ + private PanelItem createButton(Button button) { + final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; + final String name = this.user.getTranslation(reference + "name"); + final List description = new ArrayList<>(3); + description.add(this.user.getTranslation(reference + "description")); + + ItemStack icon; + PanelItem.ClickHandler clickHandler; + boolean glow; + + switch (button) { + case ADD_ENTITY -> { + icon = new ItemStack(Material.BUCKET); + clickHandler = (panel, user, clickType, slot) -> { + // Open a multi-selection tool to add new entities. + MultiEntitySelector.open(this.user, this.asEggs, MultiEntitySelector.Mode.ALIVE, this.itemsMap.keySet(), + (status, entities) -> { + if (status) { + // For each selected entity, add it to the map with a default count. + entities.forEach(entity -> { + this.itemsMap.put(entity, 1); + this.itemList.add(entity); + }); + } + // Rebuild the panel to reflect changes. + this.build(); + }); + return true; + }; + glow = false; + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-add")); + } + case REMOVE_ENTITY -> { + if (!this.selectedItems.isEmpty()) { + // If any entities are selected, list them in the description. + description.add(this.user.getTranslation(reference + "title")); + this.selectedItems.forEach(entity -> description.add(this.user.getTranslation(reference + "entity", + "[entity]", Utils.prettifyObject(entity, this.user)))); + } + icon = new ItemStack(Material.LAVA_BUCKET); + clickHandler = (panel, user, clickType, slot) -> { + if (!this.selectedItems.isEmpty()) { + // Remove all selected entities from the map and list. + this.itemsMap.keySet().removeAll(this.selectedItems); + this.itemList.removeAll(this.selectedItems); + this.selectedItems.clear(); + // Rebuild the panel after removal. + this.build(); + } + return true; + }; + glow = !this.selectedItems.isEmpty(); + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-remove")); + } + case SWITCH_ENTITY -> { + // Button to toggle the display mode between entity eggs and mob heads. + icon = new ItemStack(asEggs ? Material.EGG : Material.PLAYER_HEAD); + clickHandler = (panel, user, clickType, slot) -> { + // Toggle the display mode flag and rebuild the panel. + this.asEggs = !this.asEggs; + this.build(); + return true; + }; + glow = false; + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); + } + default -> { + icon = new ItemStack(Material.PAPER); + clickHandler = null; + glow = false; + } + } + + return new PanelItemBuilder().icon(icon).name(name).description(description).clickHandler(clickHandler) + .glow(glow).build(); + } + + /** + * Enumeration of functional buttons in the Manage Entities panel. + */ + private enum Button { + ADD_ENTITY, REMOVE_ENTITY, SWITCH_ENTITY + } } diff --git a/src/main/java/world/bentobox/challenges/utils/Utils.java b/src/main/java/world/bentobox/challenges/utils/Utils.java index 6a3e8780..8d2b6b01 100644 --- a/src/main/java/world/bentobox/challenges/utils/Utils.java +++ b/src/main/java/world/bentobox/challenges/utils/Utils.java @@ -305,6 +305,26 @@ public static String prettifyObject(@Nullable Tag object, User user) { return translation.isEmpty() ? tag : translation; } + /** + * Prettify object + * @param class that extends Enum + * @param object that extends Enum + * @param user use who will see the text + * @return string of pretty text for user + */ + public static > String prettifyObject(@Nullable T object, User user) { + if (object == null) { + return ""; + } + // Build a translation key using the enum name. + String translation = user + .getTranslationOrNothing(Constants.MATERIALS + object.name().toLowerCase(Locale.ENGLISH) + ".name"); + String any = user.getTranslationOrNothing(Constants.MATERIALS + "any"); + // Use the enum's name and prettify it (for example, convert ALL_HANGING_SIGNS to "All Hanging Sign") + String tag = any + Util.prettifyText(object.name()).replaceAll("s$", ""); + return translation.isEmpty() ? tag : translation; + } + /** * Prettify Material object for user. * @param object Object that must be pretty. From f88d5a80e46f30bfea1b039f4c6f9f44d2f042b3 Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 7 Feb 2025 10:50:06 -0800 Subject: [PATCH 15/34] Fix error with Mode - now the enum is correctly shared --- .../bentobox/challenges/panel/CommonPagedPanel.java | 2 +- .../panel/admin/AbstractManageEnumPanel.java | 1 - .../challenges/panel/admin/EditChallengePanel.java | 7 ------- .../panel/admin/ManageBlockGroupsPanel.java | 3 +-- .../panel/admin/ManageEntityGroupsPanel.java | 5 +++-- .../challenges/panel/util/MultiBlockSelector.java | 11 ++++------- .../challenges/panel/util/MultiEntitySelector.java | 11 +---------- .../panel/util/MultiEntityTypeTagsSelector.java | 13 ++----------- .../panel/util/MultiMaterialTagsSelector.java | 11 +---------- .../challenges/panel/util/UnifiedMultiSelector.java | 11 ++++++++--- 10 files changed, 21 insertions(+), 54 deletions(-) diff --git a/src/main/java/world/bentobox/challenges/panel/CommonPagedPanel.java b/src/main/java/world/bentobox/challenges/panel/CommonPagedPanel.java index 84de54fa..dec98c8a 100644 --- a/src/main/java/world/bentobox/challenges/panel/CommonPagedPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/CommonPagedPanel.java @@ -103,7 +103,7 @@ else if (this.pageIndex > (size / MAX_ELEMENTS)) { if (!panelBuilder.slotOccupied(index)) { - // Show a challenge + // Show an item panelBuilder.item(index, this.createElementButton(objectList.get(objectIndex++))); } diff --git a/src/main/java/world/bentobox/challenges/panel/admin/AbstractManageEnumPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/AbstractManageEnumPanel.java index 02b0d43b..da5a6ffd 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/AbstractManageEnumPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/AbstractManageEnumPanel.java @@ -77,7 +77,6 @@ protected PanelItem createElementButton(T element) { } else { description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-select")); } - return new PanelItemBuilder() .name(this.user.getTranslation(reference + "name", getElementPlaceholder(), Utils.prettifyObject(element, this.user))) diff --git a/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java index 7c73d7cb..83fc3cf0 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java @@ -345,7 +345,6 @@ private PanelItem createMenuButton(MenuType menuType) { glow = false; } } - return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) .clickHandler(clickHandler).build(); } @@ -612,7 +611,6 @@ private PanelItem createRequirementButton(RequirementButton button) { if (!this.challenge.getRequirements().getRequiredPermissions().isEmpty()) { description.add(this.user.getTranslation(Constants.TIPS + "shift-click-to-reset")); } - return new PanelItemBuilder().icon(icon).name(name).description(description).clickHandler(clickHandler) .build(); } @@ -809,7 +807,6 @@ private PanelItem createIslandRequirementButton(RequirementButton button) { glow = false; } } - return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) .clickHandler(clickHandler).build(); } @@ -955,7 +952,6 @@ private PanelItem createInventoryRequirementButton(RequirementButton button) { glow = false; } } - return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) .clickHandler(clickHandler).build(); } @@ -1085,7 +1081,6 @@ private PanelItem createOtherRequirementButton(RequirementButton button) { glow = false; } } - return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) .clickHandler(clickHandler).build(); } @@ -1243,7 +1238,6 @@ private PanelItem createStatisticRequirementButton(RequirementButton button) { glow = false; } } - return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) .clickHandler(clickHandler).build(); } @@ -1708,7 +1702,6 @@ private PanelItem createRewardButton(RewardButton button) { glow = false; } } - return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) .clickHandler(clickHandler).build(); } diff --git a/src/main/java/world/bentobox/challenges/panel/admin/ManageBlockGroupsPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/ManageBlockGroupsPanel.java index 8bef831f..025b2060 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/ManageBlockGroupsPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/ManageBlockGroupsPanel.java @@ -18,7 +18,6 @@ import org.eclipse.jdt.annotation.Nullable; import lv.id.bonne.panelutils.PanelUtils; -import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.panels.PanelItem; import world.bentobox.bentobox.api.panels.builders.PanelBuilder; import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; @@ -271,7 +270,7 @@ protected PanelItem createElementButton(Tag tag) } return new PanelItemBuilder(). - name(this.user.getTranslation(reference + "name", "[tag]", + name(this.user.getTranslation(reference + "name", "[id]", Utils.prettifyObject(tag, this.user))). icon(getIcon(tag, this.tagMap.get(tag))). description(description). diff --git a/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java index e607ed11..5bac9640 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java @@ -26,6 +26,7 @@ import world.bentobox.challenges.panel.CommonPanel; import world.bentobox.challenges.panel.ConversationUtils; import world.bentobox.challenges.panel.util.MultiEntityTypeTagsSelector; +import world.bentobox.challenges.panel.util.UnifiedMultiSelector.Mode; import world.bentobox.challenges.utils.Constants; import world.bentobox.challenges.utils.Utils; @@ -172,7 +173,7 @@ private PanelItem createButton(Button button) icon = new ItemStack(Material.BUCKET); clickHandler = (panel, user1, clickType, slot) -> { - MultiEntityTypeTagsSelector.open(this.user, MultiEntityTypeTagsSelector.Mode.ENTITY_TYPE, + MultiEntityTypeTagsSelector.open(this.user, Mode.ENTITY_TYPE, new HashSet<>(this.materialList), (status, materials) -> { @@ -271,7 +272,7 @@ protected PanelItem createElementButton(Tag tag) } return new PanelItemBuilder(). - name(this.user.getTranslation(reference + "name", "[tag]", + name(this.user.getTranslation(reference + "name", "[id]", Utils.prettifyObject(tag, this.user))). icon(getIcon(tag, this.tagMap.get(tag))). description(description). diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiBlockSelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiBlockSelector.java index 380b96ea..37a4b31f 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiBlockSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiBlockSelector.java @@ -13,6 +13,7 @@ import org.bukkit.inventory.ItemStack; import lv.id.bonne.panelutils.PanelUtils; +import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.user.User; import world.bentobox.challenges.utils.Utils; @@ -23,12 +24,8 @@ */ public class MultiBlockSelector extends UnifiedMultiSelector { - private final Mode mode; - private final Set excluded; - public enum Mode { - BLOCKS, ITEMS, ANY - } + private final Set excluded; /** * Private constructor. @@ -40,8 +37,8 @@ public enum Mode { */ private MultiBlockSelector(User user, Mode mode, Set excluded, BiConsumer> consumer) { - super(user, consumer); - this.mode = mode; + super(user, mode, consumer); + if (excluded == null) { excluded = new HashSet<>(); } diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java index 4e8d9029..7f53c7fa 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java @@ -23,16 +23,8 @@ public class MultiEntitySelector extends UnifiedMultiSelector { private final boolean asEgg; - private final Mode mode; private final Set excluded; - /** - * Specifies which entities to display. - */ - public enum Mode { - ALIVE, ANY - } - /** * Private constructor. * @@ -44,9 +36,8 @@ public enum Mode { */ private MultiEntitySelector(User user, boolean asEgg, Mode mode, Set excluded, java.util.function.BiConsumer> consumer) { - super(user, consumer); + super(user, mode, consumer); this.asEgg = asEgg; - this.mode = mode; this.excluded = excluded; } diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java index ab4d728a..90c06030 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java @@ -2,10 +2,10 @@ import java.util.ArrayList; import java.util.Comparator; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Set; -import java.util.HashSet; import java.util.function.BiConsumer; import org.bukkit.Bukkit; @@ -28,16 +28,8 @@ */ public class MultiEntityTypeTagsSelector extends UnifiedMultiSelector> { - private final Mode mode; private final Set> excluded; - /** - * Defines filtering modes. - */ - public enum Mode { - ENTITY_TYPE, ANY - } - /** * Private constructor. * @@ -48,8 +40,7 @@ public enum Mode { */ private MultiEntityTypeTagsSelector(User user, Mode mode, Set> excluded, BiConsumer>> consumer) { - super(user, consumer); - this.mode = mode; // This is not currently used + super(user, mode, consumer); this.excluded = excluded; } diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java index bef2e58f..1e039eb6 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java @@ -44,16 +44,8 @@ public class MultiMaterialTagsSelector extends UnifiedMultiSelector> excluded; - /** - * Modes for filtering material tags. - */ - public enum Mode { - BLOCKS, ITEMS, ANY - } - /** * Private constructor. * @@ -64,8 +56,7 @@ public enum Mode { */ private MultiMaterialTagsSelector(User user, Mode mode, Set> excluded, BiConsumer>> consumer) { - super(user, consumer); - this.mode = mode; // Not currently used + super(user, Mode.ANY, consumer); this.excluded = excluded; } diff --git a/src/main/java/world/bentobox/challenges/panel/util/UnifiedMultiSelector.java b/src/main/java/world/bentobox/challenges/panel/util/UnifiedMultiSelector.java index 855020d3..90d3e8f0 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/UnifiedMultiSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/UnifiedMultiSelector.java @@ -14,7 +14,6 @@ import org.bukkit.inventory.ItemStack; import lv.id.bonne.panelutils.PanelUtils; -import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.panels.PanelItem; import world.bentobox.bentobox.api.panels.builders.PanelBuilder; import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; @@ -34,13 +33,20 @@ */ public abstract class UnifiedMultiSelector extends PagedSelector { + protected final Mode mode; + + public enum Mode { + ALIVE, BLOCKS, ITEMS, ANY, ENTITY_TYPE + } + protected final List elements; protected final Set selectedElements; protected final BiConsumer> consumer; protected List filterElements; - protected UnifiedMultiSelector(User user, BiConsumer> consumer) { + protected UnifiedMultiSelector(User user, Mode mode, BiConsumer> consumer) { super(user); + this.mode = mode; this.consumer = consumer; this.selectedElements = new HashSet<>(); // Obtain the complete list of elements from the subclass. @@ -172,7 +178,6 @@ protected PanelItem createElementButton(T element) { description.add(""); description.add(this.user.getTranslation(Constants.TIPS + "click-to-select")); } - return new PanelItemBuilder() .name(this.user.getTranslation(reference + "name", "[id]", getElementDisplayName(element))) From fb690588ff4f9e42a897c193d8b5befd78e6a863 Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 7 Feb 2025 11:17:52 -0800 Subject: [PATCH 16/34] Fix placeholders --- .../challenges/panel/util/MultiBlockSelector.java | 6 +++++- .../challenges/panel/util/MultiEntitySelector.java | 5 +++++ .../panel/util/MultiEntityTypeTagsSelector.java | 5 +++++ .../challenges/panel/util/MultiMaterialTagsSelector.java | 5 +++++ .../challenges/panel/util/UnifiedMultiSelector.java | 8 +++++++- 5 files changed, 27 insertions(+), 2 deletions(-) diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiBlockSelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiBlockSelector.java index 37a4b31f..00bb833d 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiBlockSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiBlockSelector.java @@ -13,7 +13,6 @@ import org.bukkit.inventory.ItemStack; import lv.id.bonne.panelutils.PanelUtils; -import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.user.User; import world.bentobox.challenges.utils.Utils; @@ -114,4 +113,9 @@ protected String getElementDisplayName(Material element) { protected String elementToString(Material element) { return element.name(); } + + @Override + protected String getElementPlaceholder() { + return "[material]"; + } } diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java index 7f53c7fa..c5211abf 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java @@ -117,4 +117,9 @@ protected String getElementDisplayName(EntityType element) { protected String elementToString(EntityType element) { return element.name(); } + + @Override + protected String getElementPlaceholder() { + return "[entity]"; + } } diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java index 90c06030..e9191870 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java @@ -188,4 +188,9 @@ protected String getElementDisplayName(Tag element) { protected String elementToString(Tag element) { return element.getKey().getKey(); } + + @Override + protected String getElementPlaceholder() { + return "[id]"; + } } diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java index 1e039eb6..335921b1 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiMaterialTagsSelector.java @@ -217,4 +217,9 @@ protected String getElementDisplayName(Tag element) { protected String elementToString(Tag element) { return element.getKey().getKey(); } + + @Override + protected String getElementPlaceholder() { + return "[id]"; + } } diff --git a/src/main/java/world/bentobox/challenges/panel/util/UnifiedMultiSelector.java b/src/main/java/world/bentobox/challenges/panel/util/UnifiedMultiSelector.java index 90d3e8f0..2a344d0c 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/UnifiedMultiSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/UnifiedMultiSelector.java @@ -74,6 +74,12 @@ protected UnifiedMultiSelector(User user, Mode mode, BiConsumer { // Toggle the selection state. From c5334acc8700c1ba635bfe4247932f92a8edeea1 Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 10 Feb 2025 02:47:06 -0800 Subject: [PATCH 17/34] Enable challenges to have multiple statistic requirements This enables things like a challenge to kill 10 creepers, 10 zombies, and 10 skeletons. --- .../requirements/StatisticRequirements.java | 231 ++----- .../managers/ChallengesImportManager.java | 14 +- .../challenges/panel/CommonPanel.java | 61 +- .../panel/admin/EditChallengePanel.java | 258 ++------ .../panel/admin/ManageStatisticsPanel.java | 616 ++++++++++++++++++ .../panel/util/StatisticSelector.java | 13 +- .../panel/util/UnifiedMultiSelector.java | 14 + .../challenges/tasks/TryToComplete.java | 198 +++--- src/main/resources/locales/en-US.yml | 204 ++++-- 9 files changed, 1052 insertions(+), 557 deletions(-) create mode 100644 src/main/java/world/bentobox/challenges/panel/admin/ManageStatisticsPanel.java diff --git a/src/main/java/world/bentobox/challenges/database/object/requirements/StatisticRequirements.java b/src/main/java/world/bentobox/challenges/database/object/requirements/StatisticRequirements.java index 2951bbfb..3df672b2 100644 --- a/src/main/java/world/bentobox/challenges/database/object/requirements/StatisticRequirements.java +++ b/src/main/java/world/bentobox/challenges/database/object/requirements/StatisticRequirements.java @@ -2,11 +2,16 @@ // Created by BONNe // Copyright - 2021 // +// Enhanced by tastybento package world.bentobox.challenges.database.object.requirements; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + import org.bukkit.Material; import org.bukkit.Statistic; import org.bukkit.entity.EntityType; @@ -15,215 +20,121 @@ import com.google.gson.annotations.Expose; +/** + * Requirements for statistics based challenges + */ public class StatisticRequirements extends Requirements { /** - * Constructor Requirements creates a new Requirements instance. + * Record for this requirement + * @param Statistic statistic + * @param EntityType entity + * @param Material material + * @param Integer amount + * @param Boolean reduceStatistic */ - public StatisticRequirements() - { - // Empty constructor + public record StatisticRec(@Expose Statistic statistic, @Expose EntityType entity, @Expose Material material, + @Expose Integer amount, @Expose Boolean reduceStatistic) { } - /** - * This method copies given statistic object. - * @return Copy of this object. + * Type of the statistic field. + * @deprecated Shifting to a list */ - @Override - public Requirements copy() - { - StatisticRequirements requirements = new StatisticRequirements(); - requirements.setStatistic(this.statistic); - requirements.setEntity(this.entity); - requirements.setMaterial(this.material); - requirements.setAmount(this.amount); - requirements.setReduceStatistic(this.reduceStatistic); - - return requirements; - } - - - @Override - public boolean isValid() - { - if (!super.isValid()) - { - return false; - } - - if (this.statistic == null) - { - return false; - } - - return switch (this.statistic.getType()) - { - case ITEM -> this.material != null && this.material.isItem(); - - case BLOCK -> this.material != null && this.material.isBlock(); - - case ENTITY -> this.entity != null; - - case UNTYPED -> true; - - }; - - } - - - // --------------------------------------------------------------------- -// Section: Getters and setters -// --------------------------------------------------------------------- - + @Expose + @Nullable + private Statistic statistic; /** - * Gets statistic. - * - * @return the statistic + * Type of entity for entity related statistics. + * @deprecated Shifting to a list */ + @Expose @Nullable - public Statistic getStatistic() - { - return statistic; - } - + private EntityType entity; /** - * Sets statistic. - * - * @param statistic the statistic + * Type of material for block and item related statistics. + * @deprecated Shifting to a list */ - public void setStatistic(@Nullable Statistic statistic) - { - this.statistic = statistic; - } - + @Expose + @Nullable + private Material material; /** - * Gets entity. - * - * @return the entity + * Amount of the stats. + * @deprecated Shifting to a list */ - @Nullable - public EntityType getEntity() - { - return entity; - } - + @Expose + private Integer amount; /** - * Sets entity. - * - * @param entity the entity + * Indicate that player statistic fields must be adjusted after completing challenges. + * @deprecated Shifting to a list */ - public void setEntity(@Nullable EntityType entity) - { - this.entity = entity; - } - + @Expose + private Boolean reduceStatistic; /** - * Gets material. - * - * @return the material + * List of statistics that must be done for this challenge */ + @Expose @Nullable - public Material getMaterial() - { - return material; - } + private List statisticList; /** - * Sets material. - * - * @param material the material + * Constructor Requirements creates a new Requirements instance. */ - public void setMaterial(@Nullable Material material) + public StatisticRequirements() { - this.material = material; + // Empty constructor } /** - * Gets amount. - * - * @return the amount + * This method copies given statistic object. + * @return Copy of this object. */ - public int getAmount() + @Override + public Requirements copy() { - return amount; + StatisticRequirements requirements = new StatisticRequirements(); + requirements.setStatisticList(this.getRequiredStatistics()); + return requirements; } - /** - * Sets amount. - * - * @param amount the amount - */ - public void setAmount(int amount) + @Override + public boolean isValid() { - this.amount = amount; + // TODO - do something here? + return super.isValid(); } - /** - * Is reduce statistic boolean. - * - * @return the boolean + * @return the statisticList */ - public boolean isReduceStatistic() - { - return reduceStatistic; + public List getRequiredStatistics() { + if (statisticList == null) { + statisticList = new ArrayList<>(); + // Convert old single statistic entries to new list of records + if (statistic != null) { + StatisticRec rec = new StatisticRec(this.statistic, this.entity, this.material, this.amount, + this.reduceStatistic); + statisticList.add(rec); + } + } + return statisticList; } - /** - * Sets reduce statistic. - * - * @param reduceStatistic the reduce statistic + * @param value the statisticList to set */ - public void setReduceStatistic(boolean reduceStatistic) - { - this.reduceStatistic = reduceStatistic; + public void setStatisticList(Collection value) { + // If value is null, assign null; otherwise, create a new ArrayList from value. + this.statisticList = (value == null) ? null : new ArrayList<>(value); } -// --------------------------------------------------------------------- -// Section: Variables -// --------------------------------------------------------------------- - - /** - * Type of the statistic field. - */ - @Expose - @Nullable - private Statistic statistic; - - /** - * Type of entity for entity related statistics. - */ - @Expose - @Nullable - private EntityType entity; - - /** - * Type of material for block and item related statistics. - */ - @Expose - @Nullable - private Material material; - - /** - * Amount of the stats. - */ - @Expose - private int amount; - - /** - * Indicate that player statistic fields must be adjusted after completing challenges. - */ - @Expose - private boolean reduceStatistic; } diff --git a/src/main/java/world/bentobox/challenges/managers/ChallengesImportManager.java b/src/main/java/world/bentobox/challenges/managers/ChallengesImportManager.java index ef99daa5..85abe79a 100644 --- a/src/main/java/world/bentobox/challenges/managers/ChallengesImportManager.java +++ b/src/main/java/world/bentobox/challenges/managers/ChallengesImportManager.java @@ -50,6 +50,7 @@ import world.bentobox.challenges.database.object.requirements.IslandRequirements; import world.bentobox.challenges.database.object.requirements.OtherRequirements; import world.bentobox.challenges.database.object.requirements.StatisticRequirements; +import world.bentobox.challenges.database.object.requirements.StatisticRequirements.StatisticRec; import world.bentobox.challenges.utils.Constants; import world.bentobox.challenges.utils.Utils; @@ -356,13 +357,12 @@ private void populateRequirements(Challenge challenge, ConfigurationSection sect case STATISTIC_TYPE -> { StatisticRequirements requirements = new StatisticRequirements(); challenge.setRequirements(requirements); - - requirements.setAmount(section.getInt("amount", 0)); - requirements.setReduceStatistic(section.getBoolean("reduce", false)); - - requirements.setStatistic(matchStatistic(section.getString("statistic"))); - requirements.setEntity(matchEntity(section.getString("entity"))); - requirements.setMaterial(matchMaterial(section.getString("material"))); + List list = new ArrayList<>(); + list.add(new StatisticRec(matchStatistic(section.getString("statistic")), + matchEntity(section.getString("entity")), matchMaterial(section.getString("material")), + section.getInt("amount", 0), section.getBoolean("reduce", false))); + // TODO: Add support for multiple stat challenge + requirements.setStatisticList(list); } } diff --git a/src/main/java/world/bentobox/challenges/panel/CommonPanel.java b/src/main/java/world/bentobox/challenges/panel/CommonPanel.java index 8cedb18e..5a5b95b4 100644 --- a/src/main/java/world/bentobox/challenges/panel/CommonPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/CommonPanel.java @@ -13,6 +13,7 @@ import java.util.stream.Collectors; import org.bukkit.Material; +import org.bukkit.Statistic; import org.bukkit.World; import org.bukkit.inventory.ItemStack; import org.eclipse.jdt.annotation.NonNull; @@ -29,6 +30,7 @@ import world.bentobox.challenges.database.object.requirements.IslandRequirements; import world.bentobox.challenges.database.object.requirements.OtherRequirements; import world.bentobox.challenges.database.object.requirements.StatisticRequirements; +import world.bentobox.challenges.database.object.requirements.StatisticRequirements.StatisticRec; import world.bentobox.challenges.managers.ChallengesManager; import world.bentobox.challenges.utils.Constants; import world.bentobox.challenges.utils.LevelStatus; @@ -467,7 +469,7 @@ private String generateOtherChallenge(OtherRequirements requirement) { } /** - * This method generates lore message for Statistic requirement. + * This method generates lore message for Statistic requirements. * * @param requirement Statistic Requirement. * @return Requirement lore message. @@ -475,48 +477,49 @@ private String generateOtherChallenge(OtherRequirements requirement) { private String generateStatisticChallenge(StatisticRequirements requirement) { final String reference = Constants.DESCRIPTIONS + "challenge.requirements.statistic."; - String statistic; - - if (requirement.getStatistic() == null) { + if (requirement.getRequiredStatistics().isEmpty()) { // Challenges by default comes with empty statistic field. return ""; } - switch (requirement.getStatistic().getType()) { - case UNTYPED -> statistic = this.user.getTranslationOrNothing(reference + "statistic", "[statistic]", - Utils.prettifyObject(requirement.getStatistic(), this.user), "[number]", - String.valueOf(requirement.getAmount())); + StringBuilder statistics = new StringBuilder(); + for (StatisticRec s : requirement.getRequiredStatistics()) { + String statistic = switch (s.statistic().getType()) { + case UNTYPED -> this.user.getTranslationOrNothing(reference + "statistic", "[statistic]", + Utils.prettifyObject(s.statistic(), this.user), "[number]", String.valueOf(s.amount())); case ITEM, BLOCK -> { - if (requirement.getAmount() > 1) { - statistic = this.user.getTranslationOrNothing(reference + "multiple-target", "[statistic]", - Utils.prettifyObject(requirement.getStatistic(), this.user), "[number]", - String.valueOf(requirement.getAmount()), "[target]", - Utils.prettifyObject(requirement.getMaterial(), this.user)); + if (s.amount() > 1) { + yield this.user.getTranslationOrNothing(reference + "multiple-target", "[statistic]", + Utils.prettifyObject(s.statistic(), this.user), "[number]", String.valueOf(s.amount()), + "[target]", Utils.prettifyObject(s.material(), this.user)); } else { - statistic = this.user.getTranslationOrNothing(reference + "single-target", "[statistic]", - Utils.prettifyObject(requirement.getStatistic(), this.user), "[target]", - Utils.prettifyObject(requirement.getMaterial(), this.user)); + yield this.user.getTranslationOrNothing(reference + "single-target", "[statistic]", + Utils.prettifyObject(s.statistic(), this.user), "[target]", + Utils.prettifyObject(s.material(), this.user)); } } case ENTITY -> { - if (requirement.getAmount() > 1) { - statistic = this.user.getTranslationOrNothing(reference + "multiple-target", "[statistic]", - Utils.prettifyObject(requirement.getStatistic(), this.user), "[number]", - String.valueOf(requirement.getAmount()), "[target]", - Utils.prettifyObject(requirement.getEntity(), this.user)); + if (s.amount() > 1) { + yield this.user.getTranslationOrNothing(reference + "multiple-target", "[statistic]", + Utils.prettifyObject(s.statistic(), this.user), "[number]", String.valueOf(s.amount()), + "[target]", Utils.prettifyObject(s.entity(), this.user)); } else { - statistic = this.user.getTranslationOrNothing(reference + "single-target", "[statistic]", - Utils.prettifyObject(requirement.getStatistic(), this.user), "[target]", - Utils.prettifyObject(requirement.getEntity(), this.user)); + yield this.user.getTranslationOrNothing(reference + "single-target", "[statistic]", + Utils.prettifyObject(s.statistic(), this.user), "[target]", + Utils.prettifyObject(s.entity(), this.user)); } } - default -> statistic = ""; - } + default -> ""; + }; - String warning = requirement.isReduceStatistic() ? this.user.getTranslationOrNothing(reference + "warning") - : ""; + String warning = s.reduceStatistic() ? this.user.getTranslationOrNothing(reference + "warning") + : ""; + statistics.append(this.user.getTranslationOrNothing(reference + "lore", "[statistic]", statistic, "[warning]", + warning)); + statistics.append("\n"); - return this.user.getTranslationOrNothing(reference + "lore", "[statistic]", statistic, "[warning]", warning); + } + return statistics.toString(); } /** diff --git a/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java index 83fc3cf0..b680271a 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java @@ -35,9 +35,6 @@ import world.bentobox.challenges.panel.util.EnvironmentSelector; import world.bentobox.challenges.panel.util.ItemSelector; import world.bentobox.challenges.panel.util.MultiBlockSelector; -import world.bentobox.challenges.panel.util.SingleBlockSelector; -import world.bentobox.challenges.panel.util.SingleEntitySelector; -import world.bentobox.challenges.panel.util.StatisticSelector; import world.bentobox.challenges.utils.Constants; import world.bentobox.challenges.utils.Utils; @@ -229,24 +226,8 @@ private void buildOtherRequirementsPanel(PanelBuilder panelBuilder) { * @param panelBuilder PanelBuilder where icons must be added. */ private void buildStatisticRequirementsPanel(PanelBuilder panelBuilder) { - panelBuilder.item(10, this.createRequirementButton(RequirementButton.STATISTIC)); - panelBuilder.item(19, this.createRequirementButton(RequirementButton.REMOVE_STATISTIC)); - - panelBuilder.item(11, this.createRequirementButton(RequirementButton.STATISTIC_AMOUNT)); - - StatisticRequirements requirements = this.challenge.getRequirements(); - - if (requirements.getStatistic() != null) { - switch (requirements.getStatistic().getType()) { - case ITEM -> panelBuilder.item(13, this.createRequirementButton(RequirementButton.STATISTIC_ITEMS)); - case BLOCK -> panelBuilder.item(13, this.createRequirementButton(RequirementButton.STATISTIC_BLOCKS)); - case ENTITY -> panelBuilder.item(13, this.createRequirementButton(RequirementButton.STATISTIC_ENTITIES)); - default -> { - } - } - } - - panelBuilder.item(25, this.createRequirementButton(RequirementButton.REQUIRED_PERMISSIONS)); + // Give user the ability to add or remove statistics + panelBuilder.item(10, this.createRequirementButton(RequirementButton.REQUIRED_STATISTICS)); } /** @@ -627,9 +608,9 @@ private PanelItem createRequirementButton(RequirementButton button) { case REQUIRED_EXPERIENCE, REMOVE_EXPERIENCE, REQUIRED_LEVEL, REQUIRED_MONEY, REMOVE_MONEY -> { return this.createOtherRequirementButton(button); } - // Buttons for Statistic Requirements - case STATISTIC, STATISTIC_BLOCKS, STATISTIC_ITEMS, STATISTIC_ENTITIES, STATISTIC_AMOUNT, REMOVE_STATISTIC -> { - return this.createStatisticRequirementButton(button); + // Statistics + case REQUIRED_STATISTICS -> { + return this.createStatisticsRequirementButton(button); } // Default behaviour. default -> { @@ -702,8 +683,8 @@ private PanelItem createIslandRequirementButton(RequirementButton button) { description.add(this.user.getTranslation(reference + "title")); // Add Material Tags only requirements.getRequiredMaterialTags() - .forEach((block, count) -> description.add(this.user.getTranslation(reference + "list", - "[tag]", Utils.prettifyObject(block, this.user), "[number]", String.valueOf(count)))); + .forEach((block, count) -> description.add(this.user.getTranslation(reference + "list", "[tag]", + Utils.prettifyObject(block, this.user), "[number]", String.valueOf(count)))); } icon = new ItemStack(Material.STONE_BRICKS); @@ -724,8 +705,8 @@ private PanelItem createIslandRequirementButton(RequirementButton button) { description.add(this.user.getTranslation(reference + "title")); // Add Material Tags only requirements.getRequiredEntityTypeTags() - .forEach((block, count) -> description.add(this.user.getTranslation(reference + "list", - "[tag]", Utils.prettifyObject(block, this.user), "[number]", String.valueOf(count)))); + .forEach((block, count) -> description.add(this.user.getTranslation(reference + "list", "[tag]", + Utils.prettifyObject(block, this.user), "[number]", String.valueOf(count)))); } icon = new ItemStack(Material.ZOMBIE_HEAD); @@ -811,6 +792,62 @@ private PanelItem createIslandRequirementButton(RequirementButton button) { .clickHandler(clickHandler).build(); } + /** + * This method creates buttons for inventory requirements menu. + * + * @param button Button which panel item must be created. + * @return PanelItem that represents given button. + */ + private PanelItem createStatisticsRequirementButton(RequirementButton button) { + final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; + + final String name = this.user.getTranslation(reference + "name"); + final List description = new ArrayList<>(3); + description.add(this.user.getTranslation(reference + "description")); + + ItemStack icon; + boolean glow; + PanelItem.ClickHandler clickHandler; + + final StatisticRequirements requirements = this.challenge.getRequirements(); + switch (button) { + // Just one special statistic button right now + case REQUIRED_STATISTICS -> { + // TODO rename getStaticicList() to getRequiredStatistics() + if (requirements.getRequiredStatistics().isEmpty()) { + description.add(this.user.getTranslation(reference + "none")); + } else { + description.add(this.user.getTranslation(reference + "title")); + + requirements.getRequiredStatistics().stream() + .sorted(Comparator.comparing(r -> r.statistic().getKey().getKey())) + // Just list the name of the statistic + .forEach(sr -> description.add(this.user.getTranslationOrNothing(reference + "list", "[name]", + Utils.prettifyObject(sr.statistic(), user)))); + } + + icon = new ItemStack(Material.CHEST); + clickHandler = (panel, user, clickType, slot) -> { + // Deal with adding and removing statistics in the MultiStatisticSelector class + ManageStatisticsPanel.open(this, requirements.getRequiredStatistics()); + return true; + }; + glow = false; + } + default -> { + // This should never need to be shown. Just for future expansion. + glow = false; + icon = new ItemStack(Material.PAPER); + clickHandler = null; + } + } + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) + .clickHandler(clickHandler).build(); + } + /** * This method creates buttons for inventory requirements menu. * @@ -1085,163 +1122,6 @@ private PanelItem createOtherRequirementButton(RequirementButton button) { .clickHandler(clickHandler).build(); } - /** - * Creates a button for statistic requirements. - * - * @param button Button that must be created. - * @return PanelItem button. - */ - private PanelItem createStatisticRequirementButton(RequirementButton button) { - final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; - - final String name = this.user.getTranslation(reference + "name"); - final List description = new ArrayList<>(3); - description.add(this.user.getTranslation(reference + "description")); - - ItemStack icon; - boolean glow; - PanelItem.ClickHandler clickHandler; - - final StatisticRequirements requirements = this.challenge.getRequirements(); - - switch (button) { - case STATISTIC -> { - description.add(this.user.getTranslation(reference + "value", "[statistic]", - Utils.prettifyObject(requirements.getStatistic(), this.user))); - - icon = new ItemStack(requirements.getStatistic() == null ? Material.BARRIER : Material.PAPER); - clickHandler = (panel, user, clickType, slot) -> { - StatisticSelector.open(this.user, (status, statistic) -> { - if (status) { - requirements.setStatistic(statistic); - requirements.setMaterial(null); - requirements.setEntity(null); - requirements.setAmount(0); - } - - this.build(); - }); - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case STATISTIC_AMOUNT -> { - description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, - String.valueOf(requirements.getAmount()))); - icon = new ItemStack(Material.CHEST); - clickHandler = (panel, user, clickType, i) -> { - Consumer numberConsumer = number -> { - if (number != null) { - requirements.setAmount(number.intValue()); - } - - // reopen panel - this.build(); - }; - ConversationUtils.createNumericInput(numberConsumer, this.user, - this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 0, Integer.MAX_VALUE); - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case REMOVE_STATISTIC -> { - description.add( - this.user.getTranslation(reference + (requirements.isReduceStatistic() ? "enabled" : "disabled"))); - - icon = new ItemStack(Material.LEVER); - clickHandler = (panel, user, clickType, slot) -> { - requirements.setReduceStatistic(!requirements.isReduceStatistic()); - this.build(); - return true; - }; - glow = requirements.isReduceStatistic(); - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); - } - case STATISTIC_BLOCKS -> { - description.add(this.user.getTranslation(reference + "value", "[block]", - Utils.prettifyObject(requirements.getMaterial(), this.user))); - - icon = requirements.getMaterial() == null ? new ItemStack(Material.BARRIER) - : new ItemStack(requirements.getMaterial()); - clickHandler = (panel, user, clickType, slot) -> { - SingleBlockSelector.open(this.user, SingleBlockSelector.Mode.BLOCKS, (status, block) -> { - if (status) { - requirements.setMaterial(block); - } - - this.build(); - }); - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case STATISTIC_ITEMS -> { - description.add(this.user.getTranslation(reference + "value", "[item]", - Utils.prettifyObject(requirements.getMaterial(), this.user))); - - icon = requirements.getMaterial() == null ? new ItemStack(Material.BARRIER) - : new ItemStack(requirements.getMaterial()); - clickHandler = (panel, user, clickType, slot) -> { - SingleBlockSelector.open(this.user, SingleBlockSelector.Mode.ITEMS, (status, block) -> { - if (status) { - requirements.setMaterial(block); - } - - this.build(); - }); - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - case STATISTIC_ENTITIES -> { - description.add(this.user.getTranslation(reference + "value", "[entity]", - Utils.prettifyObject(requirements.getEntity(), this.user))); - - icon = requirements.getEntity() == null ? new ItemStack(Material.BARRIER) - : new ItemStack(PanelUtils.getEntityEgg(requirements.getEntity())); - clickHandler = (panel, user, clickType, slot) -> { - SingleEntitySelector.open(this.user, true, (status, entity) -> { - if (status) { - requirements.setEntity(entity); - } - - this.build(); - }); - - return true; - }; - glow = false; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); - } - default -> { - icon = new ItemStack(Material.PAPER); - clickHandler = null; - glow = false; - } - } - return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) - .clickHandler(clickHandler).build(); - } - /** * This method creates buttons for rewards menu. * @@ -1791,11 +1671,13 @@ private enum RewardButton { /** * Represents different requirement buttons that are used in menus. */ - private enum RequirementButton { + public enum RequirementButton { REQUIRED_ENTITIES, REMOVE_ENTITIES, REQUIRED_BLOCKS, REMOVE_BLOCKS, SEARCH_RADIUS, REQUIRED_PERMISSIONS, REQUIRED_ITEMS, REMOVE_ITEMS, ADD_IGNORED_META, REMOVE_IGNORED_META, REQUIRED_EXPERIENCE, REMOVE_EXPERIENCE, - REQUIRED_LEVEL, REQUIRED_MONEY, REMOVE_MONEY, STATISTIC, STATISTIC_BLOCKS, STATISTIC_ITEMS, STATISTIC_ENTITIES, - STATISTIC_AMOUNT, REMOVE_STATISTIC, REQUIRED_MATERIALTAGS, REQUIRED_ENTITYTAGS, + REQUIRED_LEVEL, REQUIRED_MONEY, REMOVE_MONEY, STATISTIC_SHOW, STATISTIC, STATISTIC_BLOCKS, STATISTIC_ITEMS, + STATISTIC_ENTITIES, NEW_STATISTIC, + STATISTIC_AMOUNT, REMOVE_STATISTIC, REQUIRED_MATERIALTAGS, REQUIRED_ENTITYTAGS, REQUIRED_STATISTICS, + REMOVE_STATISTICS, } // --------------------------------------------------------------------- diff --git a/src/main/java/world/bentobox/challenges/panel/admin/ManageStatisticsPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/ManageStatisticsPanel.java new file mode 100644 index 00000000..199dbce2 --- /dev/null +++ b/src/main/java/world/bentobox/challenges/panel/admin/ManageStatisticsPanel.java @@ -0,0 +1,616 @@ +package world.bentobox.challenges.panel.admin; + + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import org.bukkit.Material; +import org.bukkit.Statistic; +import org.bukkit.inventory.ItemStack; +import org.eclipse.jdt.annotation.Nullable; + +import lv.id.bonne.panelutils.PanelUtils; +import world.bentobox.bentobox.BentoBox; +import world.bentobox.bentobox.api.panels.Panel; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.builders.PanelBuilder; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.challenges.database.object.requirements.StatisticRequirements.StatisticRec; +import world.bentobox.challenges.panel.CommonPagedPanel; +import world.bentobox.challenges.panel.CommonPanel; +import world.bentobox.challenges.panel.ConversationUtils; +import world.bentobox.challenges.panel.admin.EditChallengePanel.RequirementButton; +import world.bentobox.challenges.panel.util.SingleBlockSelector; +import world.bentobox.challenges.panel.util.SingleEntitySelector; +import world.bentobox.challenges.panel.util.StatisticSelector; +import world.bentobox.challenges.utils.Constants; +import world.bentobox.challenges.utils.Utils; + + +/** + * This class allows to edit material that are in required material map. + */ +public class ManageStatisticsPanel extends CommonPagedPanel +{ + + // --------------------------------------------------------------------- + // Section: Enums + // --------------------------------------------------------------------- + + /** + * Functional buttons in current GUI. + */ + private enum Button { + ADD_STATISTIC, REMOVE_STATISTIC + } + + // --------------------------------------------------------------------- + // Section: Variables + // --------------------------------------------------------------------- + + /** + * Contains selected stats. + */ + private final Set selectedStats; + + /** + * List of required statistics + */ + private final List statisticsList; + + /** + * Stores filtered items. + */ + private List filterElements; + + private ManageStatisticsPanel(CommonPanel parentGUI, List statisticsList) + { + super(parentGUI); + this.statisticsList = statisticsList; + + // Sort tags by their ordinal value. + this.statisticsList.sort(Comparator.comparing(tag -> tag.statistic().getKey().getKey())); + + this.selectedStats = new HashSet<>(); + + // Init without filters applied. + this.filterElements = this.statisticsList; + } + + + /** + * Open the Challenges Admin GUI. + */ + public static void open(CommonPanel parentGUI, List statisticsList) { + new ManageStatisticsPanel(parentGUI, statisticsList).build(); + } + + +// --------------------------------------------------------------------- +// Section: Methods +// --------------------------------------------------------------------- + + + /** + * This method is called when filter value is updated. + */ + @Override + protected void updateFilters() + { + if (this.searchString == null || this.searchString.isBlank()) + { + this.filterElements = this.statisticsList; + } + else + { + this.filterElements = this.statisticsList.stream(). + filter(element -> { + // If element name is set and name contains search field, then do not filter out. + return element.statistic().getKey().getKey().toLowerCase(Locale.ENGLISH) + .contains(this.searchString.toLowerCase(Locale.ENGLISH)); + }). + distinct(). + collect(Collectors.toList()); + } + } + + + /** + * This method builds all necessary elements in GUI panel. + */ + @Override + protected void build() + { + PanelBuilder panelBuilder = new PanelBuilder().user(this.user). + name(this.user.getTranslation(Constants.TITLE + "manage-statistics")); + + // Create nice border. + PanelUtils.fillBorder(panelBuilder); + + panelBuilder.item(3, this.createButton(Button.ADD_STATISTIC)); + panelBuilder.item(5, this.createButton(Button.REMOVE_STATISTIC)); + // Fill the box with what is selected + this.populateElements(panelBuilder, this.filterElements); + + // Add return button. + panelBuilder.item(44, this.returnButton); + + panelBuilder.build(); + } + + + /** + * This method creates PanelItem button of requested type. + * @param button Button which must be created. + * @return new PanelItem with requested functionality. + */ + private PanelItem createButton(Button button) + { + final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; + + final String name = this.user.getTranslation(reference + "name"); + final List description = new ArrayList<>(3); + description.add(this.user.getTranslation(reference + "description")); + + ItemStack icon; + PanelItem.ClickHandler clickHandler; + boolean glow; + + switch (button) + { + case ADD_STATISTIC -> { + icon = new ItemStack(Material.BUCKET); + clickHandler = (panel, user1, clickType, slot) -> + { + StatisticSelector.open(this.user, (status, statistic) -> + { + if (status) + { + StatisticRec newItem = new StatisticRec(statistic, null, null, 0, false); + this.statisticsList.add(newItem); + + } + + this.build(); + }); + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-add")); + } + case REMOVE_STATISTIC -> { + + if (!this.selectedStats.isEmpty()) + { + description.add(this.user.getTranslation(reference + "title")); + this.selectedStats.forEach(stat -> + description.add(this.user.getTranslation(reference + "statistic_element", "[statistic]", + Utils.prettifyObject(stat.statistic(), this.user)))); + } + + icon = new ItemStack(Material.LAVA_BUCKET); + + clickHandler = (panel, user1, clickType, slot) -> + { + if (!this.selectedStats.isEmpty()) + { + this.statisticsList.removeAll(this.selectedStats); + this.selectedStats.clear(); + this.build(); + } + + return true; + }; + + glow = !this.selectedStats.isEmpty(); + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-remove")); + } + default -> { + icon = new ItemStack(Material.PAPER); + clickHandler = null; + glow = false; + } + } + + return new PanelItemBuilder(). + icon(icon). + name(name). + description(description). + clickHandler(clickHandler). + glow(glow). + build(); + } + + + /** + * This method creates button for given stat. + * @param rec material which button must be created. + * @return new Button for material. + */ + @Override + protected PanelItem createElementButton(StatisticRec rec) + { + final String reference = Constants.BUTTON + "statistic_element."; + + List description = new ArrayList<>(); + + // Show everything about this statistic + // Type is not shown to the user, just used to decide what to show + switch (rec.statistic().getType()) { + case BLOCK: + description.add(this.user.getTranslation(reference + "block", "[block]", + Utils.prettifyObject(rec.material(), this.user))); + break; + case ENTITY: + description.add(this.user.getTranslation(reference + "entity", "[entity]", + Utils.prettifyObject(rec.entity(), this.user))); + break; + case ITEM: + description.add(this.user.getTranslation(reference + "item", "[item]", + Utils.prettifyObject(rec.material(), this.user))); + break; + default: + break; + } + // Amount + description.add(this.user.getTranslation(reference + "amount", Constants.PARAMETER_NUMBER, + String.valueOf(Objects.requireNonNullElse(rec.amount(), 0)))); + // Removal + description.add(this.user.getTranslation(reference + "remove.name", "[value]", + this.user.getTranslation(reference + "remove.value." + + (Objects.requireNonNullElse(rec.reduceStatistic(), false) ? "enabled" : "disabled")))); + + if (this.selectedStats.contains(rec)) + { + description.add(this.user.getTranslation(reference + "selected")); + } + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "left-click-to-choose")); + + if (this.selectedStats.contains(rec)) + { + description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-deselect")); + } + else + { + description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-select")); + } + + return new PanelItemBuilder(). + name(this.user.getTranslation(reference + "name", "[statistic]", + Utils.prettifyObject(rec.statistic(), this.user))) + .icon(getStatisticIcon(rec.statistic())). + description(description). + clickHandler((panel, user1, clickType, slot) -> { + // On right click change which entities are selected for deletion. + if (clickType.isRightClick()) + { + if (!this.selectedStats.add(rec)) + { + // Remove material if it is already selected + this.selectedStats.remove(rec); + } + + this.build(); + } + else + { + // Left click + this.buildStatisticPanel(rec); + } + return true; + }). + glow(this.selectedStats.contains(rec)). + build(); + } + + /** + * Get an icon for a Statistic. Hand selected! + * @param stat Statistic + * @return ItemStack icon + */ + public static ItemStack getStatisticIcon(Statistic stat) { + return switch (stat) { + case ANIMALS_BRED -> new ItemStack(Material.WHEAT); + case ARMOR_CLEANED -> new ItemStack(Material.LEATHER_CHESTPLATE); + case AVIATE_ONE_CM -> new ItemStack(Material.ELYTRA); + case BANNER_CLEANED -> new ItemStack(Material.RED_BANNER); + case BEACON_INTERACTION -> new ItemStack(Material.BEACON); + case BELL_RING -> new ItemStack(Material.BELL); + case BOAT_ONE_CM -> new ItemStack(Material.OAK_BOAT); + case BREAK_ITEM -> new ItemStack(Material.AMETHYST_SHARD); + case BREWINGSTAND_INTERACTION -> new ItemStack(Material.BREWING_STAND); + case CAKE_SLICES_EATEN -> new ItemStack(Material.CAKE); + case CAULDRON_FILLED -> new ItemStack(Material.CAULDRON); + case CAULDRON_USED -> new ItemStack(Material.CAULDRON); + case CHEST_OPENED -> new ItemStack(Material.CHEST); + case CLEAN_SHULKER_BOX -> new ItemStack(Material.SHULKER_BOX); + case CLIMB_ONE_CM -> new ItemStack(Material.LADDER); + case CRAFTING_TABLE_INTERACTION -> new ItemStack(Material.CRAFTING_TABLE); + case CRAFT_ITEM -> new ItemStack(Material.CRAFTING_TABLE); + case CROUCH_ONE_CM -> new ItemStack(Material.PAPER); + case DAMAGE_ABSORBED -> new ItemStack(Material.IRON_SWORD); + case DAMAGE_BLOCKED_BY_SHIELD -> new ItemStack(Material.SHIELD); + case DAMAGE_DEALT -> new ItemStack(Material.DIAMOND_SWORD); + case DAMAGE_DEALT_ABSORBED -> new ItemStack(Material.GOLDEN_SWORD); + case DAMAGE_DEALT_RESISTED -> new ItemStack(Material.WOODEN_SWORD); + case DAMAGE_RESISTED -> new ItemStack(Material.IRON_HELMET); + case DAMAGE_TAKEN -> new ItemStack(Material.IRON_LEGGINGS); + case DEATHS -> new ItemStack(Material.OBSIDIAN); + case DISPENSER_INSPECTED -> new ItemStack(Material.DISPENSER); + case DROP -> new ItemStack(Material.PAPER); + case DROPPER_INSPECTED -> new ItemStack(Material.DROPPER); + case DROP_COUNT -> new ItemStack(Material.PAPER); + case ENDERCHEST_OPENED -> new ItemStack(Material.ENDER_CHEST); + case ENTITY_KILLED_BY -> new ItemStack(Material.BOW); + case FALL_ONE_CM -> new ItemStack(Material.PAPER); + case FISH_CAUGHT -> new ItemStack(Material.FISHING_ROD); + case FLOWER_POTTED -> new ItemStack(Material.FLOWER_POT); + case FLY_ONE_CM -> new ItemStack(Material.ELYTRA); + case FURNACE_INTERACTION -> new ItemStack(Material.FURNACE); + case HOPPER_INSPECTED -> new ItemStack(Material.HOPPER); + case HORSE_ONE_CM -> new ItemStack(Material.IRON_HORSE_ARMOR); + case INTERACT_WITH_ANVIL -> new ItemStack(Material.ANVIL); + case INTERACT_WITH_BLAST_FURNACE -> new ItemStack(Material.BLAST_FURNACE); + case INTERACT_WITH_CAMPFIRE -> new ItemStack(Material.CAMPFIRE); + case INTERACT_WITH_CARTOGRAPHY_TABLE -> new ItemStack(Material.CARTOGRAPHY_TABLE); + case INTERACT_WITH_GRINDSTONE -> new ItemStack(Material.GRINDSTONE); + case INTERACT_WITH_LECTERN -> new ItemStack(Material.LECTERN); + case INTERACT_WITH_LOOM -> new ItemStack(Material.LOOM); + case INTERACT_WITH_SMITHING_TABLE -> new ItemStack(Material.SMITHING_TABLE); + case INTERACT_WITH_SMOKER -> new ItemStack(Material.SMOKER); + case INTERACT_WITH_STONECUTTER -> new ItemStack(Material.STONECUTTER); + case ITEM_ENCHANTED -> new ItemStack(Material.ENCHANTED_GOLDEN_APPLE); + case JUMP -> new ItemStack(Material.RABBIT_FOOT); + case KILL_ENTITY -> new ItemStack(Material.DIAMOND_SWORD); + case LEAVE_GAME -> new ItemStack(Material.PAPER); + case MINECART_ONE_CM -> new ItemStack(Material.MINECART); + case MINE_BLOCK -> new ItemStack(Material.STONE); + case MOB_KILLS -> new ItemStack(Material.PAPER); + case NOTEBLOCK_PLAYED -> new ItemStack(Material.NOTE_BLOCK); + case NOTEBLOCK_TUNED -> new ItemStack(Material.NOTE_BLOCK); + case OPEN_BARREL -> new ItemStack(Material.BARREL); + case PICKUP -> new ItemStack(Material.PAPER); + case PIG_ONE_CM -> new ItemStack(Material.PIG_SPAWN_EGG); + case PLAYER_KILLS -> new ItemStack(Material.PLAYER_HEAD); + case PLAY_ONE_MINUTE -> new ItemStack(Material.CLOCK); + case RAID_TRIGGER -> new ItemStack(Material.OMINOUS_BOTTLE); + case RAID_WIN -> new ItemStack(Material.GOLD_INGOT); + case RECORD_PLAYED -> new ItemStack(Material.MUSIC_DISC_BLOCKS); + case SHULKER_BOX_OPENED -> new ItemStack(Material.SHULKER_BOX); + case SLEEP_IN_BED -> new ItemStack(Material.RED_BED); + case SNEAK_TIME -> new ItemStack(Material.PAPER); + case SPRINT_ONE_CM -> new ItemStack(Material.PAPER); + case STRIDER_ONE_CM -> new ItemStack(Material.STRIDER_SPAWN_EGG); + case SWIM_ONE_CM -> new ItemStack(Material.WATER_BUCKET); + case TALKED_TO_VILLAGER -> new ItemStack(Material.EMERALD); + case TARGET_HIT -> new ItemStack(Material.TARGET); + case TIME_SINCE_DEATH -> new ItemStack(Material.CLOCK); + case TIME_SINCE_REST -> new ItemStack(Material.CLOCK); + case TOTAL_WORLD_TIME -> new ItemStack(Material.CLOCK); + case TRADED_WITH_VILLAGER -> new ItemStack(Material.EMERALD); + case TRAPPED_CHEST_TRIGGERED -> new ItemStack(Material.TRAPPED_CHEST); + case USE_ITEM -> new ItemStack(Material.STICK); + case WALK_ONE_CM -> new ItemStack(Material.LEATHER_BOOTS); + case WALK_ON_WATER_ONE_CM -> new ItemStack(Material.LILY_PAD); + case WALK_UNDER_WATER_ONE_CM -> new ItemStack(Material.WATER_BUCKET); + default -> new ItemStack(Material.PAPER); + + }; + } + + private Panel buildStatisticPanel(StatisticRec req) { + + PanelBuilder panelBuilder = new PanelBuilder().user(this.user); + + panelBuilder.name(this.user.getTranslation(Constants.TITLE + "statistic-selector")); + + PanelUtils.fillBorder(panelBuilder); + + panelBuilder.item(10, this.createStatisticRequirementButton(RequirementButton.STATISTIC, req)); + panelBuilder.item(19, this.createStatisticRequirementButton(RequirementButton.REMOVE_STATISTIC, req)); + + panelBuilder.item(11, this.createStatisticRequirementButton(RequirementButton.STATISTIC_AMOUNT, req)); + switch (req.statistic().getType()) { + case BLOCK: + panelBuilder.item(13, this.createStatisticRequirementButton(RequirementButton.STATISTIC_BLOCKS, req)); + break; + case ENTITY: + panelBuilder.item(13, this.createStatisticRequirementButton(RequirementButton.STATISTIC_ENTITIES, req)); + break; + case ITEM: + panelBuilder.item(13, this.createStatisticRequirementButton(RequirementButton.STATISTIC_ITEMS, req)); + break; + case UNTYPED: + break; + default: + break; + + } + panelBuilder.item(25, this.createStatisticRequirementButton(RequirementButton.REQUIRED_PERMISSIONS, req)); + panelBuilder.item(44, this.returnButton); + return panelBuilder.build(); + } + + /** + * Creates a button for statistic requirements. + * + * @param button Button that must be created. + * @param req + * @return PanelItem button. + */ + private PanelItem createStatisticRequirementButton(RequirementButton button, StatisticRec req) { + + final String reference = Constants.BUTTON + button.name().toLowerCase(Locale.ENGLISH) + "."; + final String name = this.user.getTranslation(reference + "name"); + final List description = new ArrayList<>(3); + description.add(this.user.getTranslation(reference + "description")); + + ItemStack icon; + boolean glow; + PanelItem.ClickHandler clickHandler; + + switch (button) { + case STATISTIC -> { + description.add(this.user.getTranslation(reference + "value", "[statistic]", + Utils.prettifyObject(req.statistic(), this.user))); + + icon = new ItemStack(req.statistic() == null ? Material.BARRIER : Material.PAPER); + clickHandler = (panel, user, clickType, slot) -> { + StatisticSelector.open(this.user, (status, statistic) -> { + if (status) { + // Replace the old with the new + statisticsList.removeIf(sr -> sr.equals(req)); + statisticsList.add(new StatisticRec(statistic, null, null, 0, false)); + } + this.build(); + }); + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case STATISTIC_AMOUNT -> { + description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, + String.valueOf(req.amount()))); + icon = new ItemStack(Material.CHEST); + clickHandler = (panel, user, clickType, i) -> { + Consumer numberConsumer = number -> { + if (number != null) { + // Replace the old with the new + statisticsList.removeIf(sr -> sr.equals(req)); + statisticsList.add(new StatisticRec(req.statistic(), req.entity(), req.material(), + number.intValue(), req.reduceStatistic())); + } + + // reopen panel + this.build(); + }; + ConversationUtils.createNumericInput(numberConsumer, this.user, + this.user.getTranslation(Constants.CONVERSATIONS + "input-number"), 0, Integer.MAX_VALUE); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case REMOVE_STATISTIC -> { + description.add(this.user.getTranslation(reference + (req.reduceStatistic() ? "enabled" : "disabled"))); + + icon = new ItemStack(Material.LEVER); + clickHandler = (panel, user, clickType, slot) -> { + // Replace the old with the new + statisticsList.removeIf(sr -> sr.equals(req)); + statisticsList.add(new StatisticRec(req.statistic(), req.entity(), req.material(), req.amount(), + !req.reduceStatistic())); + + this.build(); + return true; + }; + glow = req.reduceStatistic(); + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); + } + case STATISTIC_BLOCKS -> { + description.add(this.user.getTranslation(reference + "value", "[block]", + Utils.prettifyObject(req.material(), this.user))); + + icon = req.material() == null ? new ItemStack(Material.BARRIER) : new ItemStack(req.material()); + clickHandler = (panel, user, clickType, slot) -> { + SingleBlockSelector.open(this.user, SingleBlockSelector.Mode.BLOCKS, (status, block) -> { + if (status) { + // Replace the old with the new + statisticsList.removeIf(sr -> sr.equals(req)); + statisticsList.add(new StatisticRec(req.statistic(), req.entity(), block, req.amount(), + req.reduceStatistic())); + + } + + this.build(); + }); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case STATISTIC_ITEMS -> { + description.add(this.user.getTranslation(reference + "value", "[item]", + Utils.prettifyObject(req.material(), this.user))); + + icon = req.material() == null ? new ItemStack(Material.BARRIER) : new ItemStack(req.material()); + clickHandler = (panel, user, clickType, slot) -> { + SingleBlockSelector.open(this.user, SingleBlockSelector.Mode.ITEMS, (status, block) -> { + if (status) { + // Replace the old with the new + statisticsList.removeIf(sr -> sr.equals(req)); + statisticsList.add(new StatisticRec(req.statistic(), req.entity(), block, req.amount(), + req.reduceStatistic())); + } + + this.build(); + }); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + case STATISTIC_ENTITIES -> { + description.add(this.user.getTranslation(reference + "value", "[entity]", + Utils.prettifyObject(req.entity(), this.user))); + + icon = req.entity() == null ? new ItemStack(Material.BARRIER) + : new ItemStack(PanelUtils.getEntityEgg(req.entity())); + clickHandler = (panel, user, clickType, slot) -> { + SingleEntitySelector.open(this.user, true, (status, entity) -> { + if (status) { + // Replace the old with the new + statisticsList.removeIf(sr -> sr.equals(req)); + statisticsList.add(new StatisticRec(req.statistic(), entity, req.material(), req.amount(), + req.reduceStatistic())); + } + + this.build(); + }); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } + default -> { + icon = new ItemStack(Material.PAPER); + clickHandler = null; + glow = false; + } + } + return new PanelItemBuilder().icon(icon).name(name).description(description).glow(glow) + .clickHandler(clickHandler).build(); + } + + +} diff --git a/src/main/java/world/bentobox/challenges/panel/util/StatisticSelector.java b/src/main/java/world/bentobox/challenges/panel/util/StatisticSelector.java index 5d4a519e..2fcf957f 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/StatisticSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/StatisticSelector.java @@ -17,6 +17,7 @@ import world.bentobox.bentobox.api.panels.builders.PanelBuilder; import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; import world.bentobox.bentobox.api.user.User; +import world.bentobox.challenges.panel.admin.ManageStatisticsPanel; import world.bentobox.challenges.utils.Constants; import world.bentobox.challenges.utils.Utils; @@ -132,13 +133,11 @@ protected PanelItem createElementButton(Statistic statistic) return new PanelItemBuilder(). name(this.user.getTranslation(reference + "name", "[statistic]", Utils.prettifyObject(statistic, this.user))). - icon(Material.PAPER). - description(description). - clickHandler((panel, user1, clickType, slot) -> { - this.consumer.accept(true, statistic); - return true; - }). - build(); + icon(ManageStatisticsPanel.getStatisticIcon(statistic)).description(description) + .clickHandler((panel, user1, clickType, slot) -> { + this.consumer.accept(true, statistic); + return true; + }).build(); } diff --git a/src/main/java/world/bentobox/challenges/panel/util/UnifiedMultiSelector.java b/src/main/java/world/bentobox/challenges/panel/util/UnifiedMultiSelector.java index 2a344d0c..5f2e1f83 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/UnifiedMultiSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/UnifiedMultiSelector.java @@ -57,6 +57,20 @@ protected UnifiedMultiSelector(User user, Mode mode, BiConsumer elements, + BiConsumer> consumer) { + super(user); + this.mode = mode; + this.consumer = consumer; + this.selectedElements = new HashSet<>(); + //If the elements are passed to the subclass in the constructor + this.elements = elements; + // Sort elements using the provided string representation. + this.elements.sort(Comparator.comparing(this::elementToString)); + // Start with the full list as the filtered list. + this.filterElements = this.elements; + } + /** * Subclasses must return the complete list of available elements. */ diff --git a/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java b/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java index 244ed941..3b2824dd 100644 --- a/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java +++ b/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java @@ -3,6 +3,7 @@ import java.time.Duration; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -48,6 +49,7 @@ import world.bentobox.challenges.database.object.requirements.IslandRequirements; import world.bentobox.challenges.database.object.requirements.OtherRequirements; import world.bentobox.challenges.database.object.requirements.StatisticRequirements; +import world.bentobox.challenges.database.object.requirements.StatisticRequirements.StatisticRec; import world.bentobox.challenges.managers.ChallengesManager; import world.bentobox.challenges.utils.Constants; import world.bentobox.challenges.utils.Utils; @@ -475,63 +477,58 @@ private void fullFillRequirements(ChallengeResult result) } case STATISTIC_TYPE -> { StatisticRequirements requirements = this.challenge.getRequirements(); + for (StatisticRec s : requirements.getRequiredStatistics()) { + if (s.reduceStatistic() && s.statistic() != null) { + int removeAmount = result.getFactor() * s.amount(); - if (requirements.isReduceStatistic() && requirements.getStatistic() != null) { - int removeAmount = result.getFactor() * requirements.getAmount(); - - // Start to remove from player who called the completion. - switch (requirements.getStatistic().getType()) - { - case UNTYPED -> { - int statistic = this.user.getPlayer().getStatistic(requirements.getStatistic()); - - if (removeAmount >= statistic) { - this.user.getPlayer().setStatistic(requirements.getStatistic(), 0); - removeAmount -= statistic; - } else { - this.user.getPlayer().setStatistic(requirements.getStatistic(), statistic - removeAmount); - removeAmount = 0; - } - } - case ITEM, BLOCK -> { - if (requirements.getMaterial() == null) { - // Just a sanity check. Material cannot be null at this point of code. - removeAmount = 0; - } else { - int statistic = this.user.getPlayer().getStatistic(requirements.getStatistic(), - requirements.getMaterial()); + // Start to remove from player who called the completion. + switch (s.statistic().getType()) + { + case UNTYPED -> { + int statistic = this.user.getPlayer().getStatistic(s.statistic()); if (removeAmount >= statistic) { - this.user.getPlayer().setStatistic(requirements.getStatistic(), requirements.getMaterial(), - 0); + this.user.getPlayer().setStatistic(s.statistic(), 0); removeAmount -= statistic; } else { - this.user.getPlayer().setStatistic(requirements.getStatistic(), requirements.getMaterial(), - statistic - removeAmount); + this.user.getPlayer().setStatistic(s.statistic(), statistic - removeAmount); removeAmount = 0; } } - } - case ENTITY -> { - if (requirements.getEntity() == null) { - // Just a sanity check. Entity cannot be null at this point of code. - removeAmount = 0; - } else { - int statistic = this.user.getPlayer().getStatistic(requirements.getStatistic(), - requirements.getEntity()); - - if (removeAmount >= statistic) { - this.user.getPlayer().setStatistic(requirements.getStatistic(), requirements.getEntity(), - 0); - removeAmount -= statistic; + case ITEM, BLOCK -> { + if (s.material() == null) { + // Just a sanity check. Material cannot be null at this point of code. + removeAmount = 0; } else { - this.user.getPlayer().setStatistic(requirements.getStatistic(), requirements.getEntity(), - statistic - removeAmount); + int statistic = this.user.getPlayer().getStatistic(s.statistic(), s.material()); + + if (removeAmount >= statistic) { + this.user.getPlayer().setStatistic(s.statistic(), s.material(), 0); + removeAmount -= statistic; + } else { + this.user.getPlayer().setStatistic(s.statistic(), s.material(), + statistic - removeAmount); + removeAmount = 0; + } + } + } + case ENTITY -> { + if (s.entity() == null) { + // Just a sanity check. Entity cannot be null at this point of code. removeAmount = 0; + } else { + int statistic = this.user.getPlayer().getStatistic(s.statistic(), s.entity()); + + if (removeAmount >= statistic) { + this.user.getPlayer().setStatistic(s.statistic(), s.entity(), 0); + removeAmount -= statistic; + } else { + this.user.getPlayer().setStatistic(s.statistic(), s.entity(), statistic - removeAmount); + removeAmount = 0; + } } } - } - } + } // If challenges are in sync with all island members, then punish others too. if (this.addon.getChallengesSettings().isStoreAsIslandData()) @@ -553,64 +550,62 @@ private void fullFillRequirements(ChallengeResult result) continue; } - switch (Objects.requireNonNull(requirements.getStatistic()).getType()) { + switch (Objects.requireNonNull(s.statistic()).getType()) { case UNTYPED -> { - int statistic = player.getStatistic(requirements.getStatistic()); + int statistic = player.getStatistic(s.statistic()); if (removeAmount >= statistic) { removeAmount -= statistic; - player.setStatistic(requirements.getStatistic(), 0); + player.setStatistic(s.statistic(), 0); } else { - player.setStatistic(requirements.getStatistic(), statistic - removeAmount); + player.setStatistic(s.statistic(), statistic - removeAmount); removeAmount = 0; } } case ITEM, BLOCK -> { - if (requirements.getMaterial() == null) + if (s.material() == null) { // Just a sanity check. Entity cannot be null at this point of code. removeAmount = 0; } else { - int statistic = player.getStatistic(requirements.getStatistic(), - requirements.getMaterial()); + int statistic = player.getStatistic(s.statistic(), s.material()); if (removeAmount >= statistic) { removeAmount -= statistic; - player.setStatistic(requirements.getStatistic(), requirements.getMaterial(), 0); + player.setStatistic(s.statistic(), s.material(), 0); } else { - player.setStatistic(requirements.getStatistic(), requirements.getMaterial(), + player.setStatistic(s.statistic(), s.material(), statistic - removeAmount); removeAmount = 0; } } } case ENTITY -> { - if (requirements.getEntity() == null) + if (s.entity() == null) { // Just a sanity check. Entity cannot be null at this point of code. removeAmount = 0; } else { - int statistic = player.getStatistic(requirements.getStatistic(), - requirements.getEntity()); + int statistic = player.getStatistic(s.statistic(), s.entity()); if (removeAmount >= statistic) { removeAmount -= statistic; - player.setStatistic(requirements.getStatistic(), requirements.getEntity(), 0); + player.setStatistic(s.statistic(), s.entity(), 0); } else { - player.setStatistic(requirements.getStatistic(), requirements.getEntity(), + player.setStatistic(s.statistic(), s.entity(), statistic - removeAmount); removeAmount = 0; } @@ -619,6 +614,7 @@ private void fullFillRequirements(ChallengeResult result) } } } + } } } } @@ -1470,54 +1466,58 @@ private ChallengeResult checkStatistic(int factor) { int currentValue; - if (requirements.getStatistic() == null) { + if (requirements.getRequiredStatistics().isEmpty()) { // Sanity check. return EMPTY_RESULT; } + List cr = new ArrayList<>(); + // Check all requirements + for (StatisticRec s : requirements.getRequiredStatistics()) { - switch (Objects.requireNonNull(requirements.getStatistic()).getType()) - { - case UNTYPED -> - currentValue = this.manager.getStatisticData(this.user, this.world, requirements.getStatistic()); - case ITEM, BLOCK -> currentValue = this.manager.getStatisticData(this.user, this.world, - requirements.getStatistic(), requirements.getMaterial()); - case ENTITY -> currentValue = this.manager.getStatisticData(this.user, this.world, requirements.getStatistic(), - requirements.getEntity()); - default -> currentValue = 0; - } - - if (currentValue < requirements.getAmount()) { - switch (Objects.requireNonNull(requirements.getStatistic()).getType()) + switch (Objects.requireNonNull(s.statistic()).getType()) { - case ITEM, BLOCK -> { - Utils.sendMessage(this.user, this.world, Constants.ERRORS + "requirement-not-met-material", - TextVariables.NUMBER, String.valueOf(requirements.getAmount()), "[statistic]", - Utils.prettifyObject(requirements.getStatistic(), this.user), "[material]", - Utils.prettifyObject(requirements.getMaterial(), this.user), Constants.PARAMETER_VALUE, - String.valueOf(currentValue)); - } - case ENTITY -> { - Utils.sendMessage(this.user, - this.world, Constants.ERRORS + "requirement-not-met-entity", TextVariables.NUMBER, - String.valueOf(requirements.getAmount()), "[statistic]", - Utils.prettifyObject(requirements.getStatistic(), this.user), "[entity]", - Utils.prettifyObject(requirements.getEntity(), this.user), Constants.PARAMETER_VALUE, - String.valueOf(currentValue)); - } - default -> { - Utils.sendMessage(this.user, - this.world, Constants.ERRORS + "requirement-not-met", TextVariables.NUMBER, - String.valueOf(requirements.getAmount()), "[statistic]", - Utils.prettifyObject(requirements.getStatistic(), this.user), Constants.PARAMETER_VALUE, - String.valueOf(currentValue)); - } + case UNTYPED -> currentValue = this.manager.getStatisticData(this.user, this.world, s.statistic()); + case ITEM, BLOCK -> + currentValue = this.manager.getStatisticData(this.user, this.world, s.statistic(), s.material()); + case ENTITY -> + currentValue = this.manager.getStatisticData(this.user, this.world, s.statistic(), s.entity()); + default -> currentValue = 0; } - } else { - factor = requirements.getAmount() == 0 ? factor : Math.min(factor, currentValue / requirements.getAmount()); - return new ChallengeResult().setMeetsRequirements().setCompleteFactor(factor); + if (currentValue < s.amount()) { + switch (Objects.requireNonNull(s.statistic()).getType()) { + case ITEM, BLOCK -> { + Utils.sendMessage(this.user, this.world, Constants.ERRORS + "requirement-not-met-material", + TextVariables.NUMBER, String.valueOf(s.amount()), "[statistic]", + Utils.prettifyObject(s.statistic(), this.user), "[material]", + Utils.prettifyObject(s.material(), this.user), Constants.PARAMETER_VALUE, + String.valueOf(currentValue)); + } + case ENTITY -> { + Utils.sendMessage(this.user, this.world, Constants.ERRORS + "requirement-not-met-entity", + TextVariables.NUMBER, String.valueOf(s.amount()), "[statistic]", + Utils.prettifyObject(s.statistic(), this.user), "[entity]", + Utils.prettifyObject(s.entity(), this.user), Constants.PARAMETER_VALUE, + String.valueOf(currentValue)); + } + default -> { + Utils.sendMessage(this.user, this.world, Constants.ERRORS + "requirement-not-met", + TextVariables.NUMBER, String.valueOf(s.amount()), "[statistic]", + Utils.prettifyObject(s.statistic(), this.user), Constants.PARAMETER_VALUE, + String.valueOf(currentValue)); + } + } + } else { + factor = s.amount() == 0 ? factor : Math.min(factor, currentValue / s.amount()); + // Store result + cr.add(new ChallengeResult().setMeetsRequirements().setCompleteFactor(factor)); + } + } + // Check results -- all must pass + if (cr.stream().allMatch(result -> result.meetsRequirements)) { + // Return any of them, because they pass + return cr.getFirst(); } - return EMPTY_RESULT; } diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 1d2803d8..01eb9a14 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -63,6 +63,7 @@ challenges: manage-block-groups: "&0&l Manage Block Groups" manage-entity-groups: "&0&l Manage Entity Groups" manage-entities: "&0&l Manage Entities" + manage-statistics: "&0&l Manage Statistics" type-selector: "&0&l Challenge Type Selector" item-selector: "&0&l Item Selector" block-selector: "&0&l Block Selector" @@ -212,7 +213,8 @@ challenges: name: "&f&l Deployment" description: |- &7 Toggle whether the challenge is - &7 deployed and can be completed by users. + &7 deployed and can be completed + &7 by users. enabled: "&2 Enabled" disabled: "&c Disabled" name: @@ -265,7 +267,8 @@ challenges: name: "&f&l Required Permissions" description: |- &7 Allows you to change the required - &7 permissions for this challenge to be completed. + &7 permissions for this challenge to + &7 be completed. title: "&7 Permissions: " permission: " &8 - [permission]" none: "&7 No permissions are set." @@ -273,15 +276,16 @@ challenges: name: "&f&l Remove Entities" description: |- &7 Toggle whether required entities will - &7 be removed from the world after completing - &7 the challenge. + &7 be removed from the world after + &7 completing the challenge. enabled: "&2 Enabled" disabled: "&c Disabled" required_entities: name: "&f&l Required Entities" description: |- &7 Allows you to change the required - &7 entities for this challenge to be completed. + &7 entities for this challenge to be + &7 completed. title: "&7 Entities: " list: " &8 - [number] x [entity]" none: "&7 No entities have been added." @@ -289,18 +293,28 @@ challenges: name: "&f&l Remove Blocks" description: |- &7 Toggle whether required blocks will - &7 be removed from the world after completing - &7 the challenge. + &7 be removed from the world after + &7 completing the challenge. enabled: "&2 Enabled" disabled: "&c Disabled" required_blocks: name: "&f&l Required Blocks" description: |- &7 Allows you to change the required - &7 blocks for this challenge to be completed. + &7 blocks for this challenge to be + &7 completed. title: "&7 Blocks: " list: " &8 - [number] x [block]" none: "&7 No blocks have been added." + required_statistics: + name: "&f&l Required Statistics" + description: |- + &7 Allows you to change the required + &7 statistics for this challenge to be + &7 completed. + title: "&7 Statistics: " + list: " &8 - [name]" + none: "&7 No statistics have been added." search_radius: name: "&f&l Search Radius" description: |- @@ -312,8 +326,8 @@ challenges: name: "&f&l Remove Items" description: |- &7 Toggle whether required items will - &7 be removed from the inventory after completing - &7 the challenge. + &7 be removed from the inventory after + &7 completing the challenge. enabled: "&2 Enabled" disabled: "&c Disabled" required_items: @@ -327,8 +341,9 @@ challenges: add_ignored_meta: name: "&f&l Add Ignore Metadata" description: |- - &7 Allows you to specify which items should ignore - &7 any metadata assigned to them. + &7 Allows you to specify which items + &7 should ignore any metadata + &7 assigned to them. title: "&7 Items: " list: " &8 - [number] x [item]" none: "&7 No items have been added." @@ -340,28 +355,31 @@ challenges: remove_experience: name: "&f&l Remove Experience" description: |- - &7 Toggle whether the required experience will - &7 be removed from the player after completing + &7 Toggle whether the required + &7 experience will be removed + &7 from the player after completing &7 the challenge. enabled: "&2 Enabled" disabled: "&c Disabled" required_experience: name: "&f&l Required Experience" description: |- - &7 Allows you to change the required experience for - &7 the player. + &7 Allows you to change the required + &7 experience for the player. value: "&7 Current experience: &e [number]" required_level: name: "&f&l Required Island Level" description: |- - &7 Allows you to change the required island level + &7 Allows you to change the + &7 required island level &7 for the challenge. value: "&7 Current level: &e [number]" required_materialtags: name: "&f&l Required Block Groups" description: |- &7 Allows you to change the required - &7 block groups for this challenge to be completed. + &7 block groups for this challenge to + &7 be completed. title: "&7 Block group: " list: " &8 - [number] x [tag]" none: "&7 No block groups have been added." @@ -369,57 +387,71 @@ challenges: name: "&f&l Required Entity Groups" description: |- &7 Allows you to change the required - &7 entity groups for this challenge to be completed. + &7 entity groups for this challenge to + &7 be completed. title: "&7 Entity group: " list: " &8 - [number] x [tag]" none: "&7 No entity groups have been added." remove_money: name: "&f&l Remove Money" description: |- - &7 Toggle whether the required money will - &7 be removed from the player's account after completing - &7 the challenge. + &7 Toggle whether the required + &7 money will be removed from + &7 the player's account after + &7 completing the challenge. enabled: "&2 Enabled" disabled: "&c Disabled" required_money: name: "&f&l Required Money" description: |- - &7 Allows you to change the required money on the player's + &7 Allows you to change the required + &7 money on the player's &7 account for the challenge. value: "&7 Current value: &e [number]" statistic: name: "&f&l Statistic" description: |- - &7 Allows you to change the statistic type that is - &7 checked for this challenge. + &7 Allows you to change the statistic + &7 type that is checked for + &7 this challenge. value: "&7 Current value: &e [statistic]" statistic_amount: name: "&f&l Target Value" description: |- - &7 Allows you to change the target statistic value + &7 Allows you to change the target + &7 statistic value &7 that must be met. value: "&7 Current value: &e [number]" + add_statistic: + name: "&f&l Add Statistic" + description: |- + &7 Allows you to add a new + &7 statistic to the list. remove_statistic: - name: "&f&l Reduce Statistic" + name: "&f&l Remove Statistic" description: |- - &7 Toggle whether the statistic value will - &7 be reduced after completing the challenge. + &7 Allows you to remove + &7 selected statistics + &7 from the list. enabled: "&2 Enabled" disabled: "&c Disabled" statistic_blocks: name: "&f&l Target Block" description: |- - &7 Allows you to change the target block for the statistic. + &7 Allows you to change the target + &7 block for the statistic. value: "&7 Current block: &e [block]" statistic_items: name: "&f&l Target Item" description: |- - &7 Allows you to change the target item for the statistic. + &7 Allows you to change the target + &7 item for the statistic. value: "&7 Current item: &e [item]" statistic_entities: name: "&f&l Target Entity" description: |- - &7 Allows you to change the target entity for the statistic. + &7 Allows you to change the target + &7 entity for the statistic. value: "&7 Current entity: &e [entity]" reward_text: name: "&f&l Reward Text" @@ -430,7 +462,8 @@ challenges: repeat_reward_text: name: "&f&l Repeat Reward Text" description: |- - &7 Specifies the repeat reward text for the challenge. + &7 Specifies the repeat reward + &7 text for the challenge. &7 Color codes must be applied. value: "&7 Current text:" reward_items: @@ -443,21 +476,22 @@ challenges: repeat_reward_items: name: "&f&l Repeat Reward Items" description: |- - &7 Allows you to change the repeat reward items for this challenge. + &7 Allows you to change the repeat + &7 reward items for this challenge. title: "&7 Items: " list: " &8 - [number] x [item]" none: "&7 No items have been added." reward_experience: name: "&f&l Reward Experience" description: |- - &7 Allows you to change the reward experience for - &7 the player. + &7 Allows you to change the reward + & experience for the player. value: "&7 Reward experience: &e [number]" repeat_reward_experience: name: "&f&l Repeat Reward Experience" description: |- - &7 Allows you to change the repeat reward experience - &7 for the player. + &7 Allows you to change the repeat + &7 reward experience for the player. value: "&7 Reward experience: &e [number]" reward_money: name: "&f&l Reward Money" @@ -467,8 +501,8 @@ challenges: repeat_reward_money: name: "&f&l Repeat Reward Money" description: |- - &7 Allows you to change the repeat reward money - &7 for the challenge. + &7 Allows you to change the repeat + &7 reward money for the challenge. value: "&7 Current value: &e [number]" reward_commands: name: "&f&l Reward Commands" @@ -511,8 +545,9 @@ challenges: cool_down: name: "&f&l Cool Down" description: |- - &7 Allows you to change the cooldown period (in seconds) - &7 that must elapse between repeatable challenge completions. + &7 Allows you to change the cooldown + &7 period (in seconds) that must elapse + &7 between repeatable challenge completions. value: "&7 Current value: &e [time]" challenges: name: "&f&l Challenges" @@ -522,66 +557,79 @@ challenges: waiver_amount: name: "&f&l Waiver Amount" description: |- - &7 Allows you to set the number of challenges that can - &7 be left uncompleted to unlock the next level. + &7 Allows you to set the number of challenges + &7 that can be left uncompleted to unlock + &7 the next level. value: "&7 Current value: &e [number]" add_challenges: name: "&f&l Add Challenge(-s)" description: |- - &7 Allows you to select and add challenges to the level. + &7 Allows you to select and add + &7 challenges to the level. remove_challenges: name: "&f&l Remove Challenge(-s)" description: |- - &7 Allows you to select and remove challenges from the level. + &7 Allows you to select and remove + &7 challenges from the level. reset_on_new: name: "&f&l Reset On New" description: |- - &7 Toggle whether challenges should be reset when a user leaves + &7 Toggle whether challenges should + & be reset when a user leaves &7 their island or creates a new one. enabled: "&2 Enabled" disabled: "&c Disabled" broadcast: name: "&f&l Broadcast" description: |- - &7 Broadcasts the first-time completion of a challenge or level to everyone. + &7 Broadcasts the first-time completion + &7 of a challenge or level to everyone. enabled: "&2 Enabled" disabled: "&c Disabled" remove_completed: name: "&f&l Hide Completed" description: |- - &7 Hides completed challenges from the menu. + &7 Hides completed challenges + &7 from the menu. enabled: "&2 Enabled" disabled: "&c Disabled" glow_completed: name: "&f&l Glow Completed" description: |- - &7 Applies an enchantment glow to completed challenges. + &7 Applies an enchantment glow + &7 to completed challenges. enabled: "&2 Enabled" disabled: "&c Disabled" store_history: name: "&f&l Store History" description: |- - &7 Stores internal history each time a challenge is completed. - &7 Currently viewable only in the database. + &7 Stores internal history each time + &7 a challenge is completed. + &7 Currently viewable only in the + &7 database. enabled: "&2 Enabled" disabled: "&c Disabled" data_per_island: name: "&f&l Store Per Island" description: |- - &7 Stores completed challenges on a per-island basis. - &7 Progress will be shared with all team members. + &7 Stores completed challenges + &7 on a per-island basis. + &7 Progress will be shared + &7 with all team members. enabled: "&2 Enabled" disabled: "&c Disabled" show_title: name: "&f&l Show Title" description: |- - &7 Displays a title when a challenge or level is completed. + &7 Displays a title when a challenge + &7 or level is completed. enabled: "&2 Enabled" disabled: "&c Disabled" gamemode_gui: name: "&f&l GameMode Selection GUI" description: |- - &7 Enables a single GUI accessible via the /challenges command. + &7 Enables a single GUI accessible + &7 via the /challenges command. &7 (Requires server restart.) enabled: "&2 Enabled" disabled: "&c Disabled" @@ -593,20 +641,26 @@ challenges: purge_history: name: "&f&l History Lifetime" description: |- - &7 Specifies the number of days that history data is stored + &7 Specifies the number of days + &7 that history data is stored &7 in user data. - &7 A value of 0 means the data will not be removed. + &7 A value of 0 means the data will + &7 not be removed. value: "&7 Current value: &e [number]" title_showtime: name: "&f&l Title Showtime" description: |- - &7 Number of ticks the title will be displayed to the player. + &7 Number of ticks the title will + & be displayed to the player. value: "&7 Current value: &e [number]" active_world_list: name: "&f&l Show Only Active World" description: |- - &7 If the GameMode Selection GUI is enabled, this setting determines - &7 whether the GUI displays the GameMode selection or challenges for the current world. + &7 If the GameMode Selection GUI is + &7 enabled, this setting determines + &7 whether the GUI displays the + &7 GameMode selection or challenges + &7 for the current world. &7 (Requires server restart.) enabled: "&2 Enabled" disabled: "&c Disabled" @@ -622,13 +676,15 @@ challenges: include_undeployed: name: "&f&l Include Undeployed Challenges" description: |- - &7 Indicates whether undeployed challenges should be counted towards level completion. + &7 Indicates whether undeployed challenges + &7 should be counted towards level completion. enabled: "&2 Enabled" disabled: "&c Disabled" download: name: "&f&l Download Libraries" description: |- - &7 Manually updates the available challenges libraries. + &7 Manually updates the available + &7 challenges libraries. enabled: "&2 With cache clear" disabled: "&c Without cache clear" player: @@ -728,20 +784,24 @@ challenges: inventory_type: name: "&f&l Inventory Type" description: |- - &7 Challenge that checks items in the player's inventory. + &7 Challenge that checks items + &7 in the player's inventory. island_type: name: "&f&l Island Type" description: |- - &7 Challenge that checks blocks or entities around the player. + &7 Challenge that checks blocks + &7 or entities around the player. other_type: name: "&f&l Other Type" description: |- - &7 Challenge that utilizes plugins or add-on features, + &7 Challenge that utilizes plugins + &7 or add-on features, &7 such as level and money. statistic_type: name: "&f&l Statistic Type" description: |- - &7 Challenge that checks the player's statistic data. + &7 Challenge that checks the + &7 player's statistic data. save: name: "&f&l Save" description: |- @@ -761,7 +821,17 @@ challenges: element: "&8 - [element]" statistic_element: name: "&f&l [statistic]" + amount: "&7 Target Value: &e [number]" + remove: + name: "&7 Reduce Statistic: [value]" + value: + enabled: "&c Enabled" + disabled: "&2 Disabled" + block: "&7 Target Block: &e [block]" + item: "&7 Target Item: &e [item]" + entity: "&7 Target Entity: &e [entity]" description: "[description]" + selected: "&2 Selected" environment_element: name: "&f&l [environment]" description: "[description]" From b63838f4be7d06a6a0aef7b98fdfd31bfb2d4a18 Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 10 Feb 2025 03:02:04 -0800 Subject: [PATCH 18/34] Reduce duplication in the constructor --- .../panel/util/UnifiedMultiSelector.java | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/src/main/java/world/bentobox/challenges/panel/util/UnifiedMultiSelector.java b/src/main/java/world/bentobox/challenges/panel/util/UnifiedMultiSelector.java index 5f2e1f83..bd505f36 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/UnifiedMultiSelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/UnifiedMultiSelector.java @@ -45,16 +45,7 @@ public enum Mode { protected List filterElements; protected UnifiedMultiSelector(User user, Mode mode, BiConsumer> consumer) { - super(user); - this.mode = mode; - this.consumer = consumer; - this.selectedElements = new HashSet<>(); - // Obtain the complete list of elements from the subclass. - this.elements = getElements(); - // Sort elements using the provided string representation. - this.elements.sort(Comparator.comparing(this::elementToString)); - // Start with the full list as the filtered list. - this.filterElements = this.elements; + this(user, mode, null, consumer); } protected UnifiedMultiSelector(User user, Mode mode, List elements, @@ -63,14 +54,12 @@ protected UnifiedMultiSelector(User user, Mode mode, List elements, this.mode = mode; this.consumer = consumer; this.selectedElements = new HashSet<>(); - //If the elements are passed to the subclass in the constructor - this.elements = elements; - // Sort elements using the provided string representation. + this.elements = (elements != null) ? elements : getElements(); // Use provided elements or get them from subclass this.elements.sort(Comparator.comparing(this::elementToString)); - // Start with the full list as the filtered list. this.filterElements = this.elements; } + /** * Subclasses must return the complete list of available elements. */ From 183368bedbb9f38833b66ab3073a61ffd148c2b8 Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 10 Feb 2025 03:02:35 -0800 Subject: [PATCH 19/34] Remove unused enums and todo comment --- .../bentobox/challenges/panel/admin/EditChallengePanel.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java index b680271a..f886e134 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java @@ -813,7 +813,6 @@ private PanelItem createStatisticsRequirementButton(RequirementButton button) { switch (button) { // Just one special statistic button right now case REQUIRED_STATISTICS -> { - // TODO rename getStaticicList() to getRequiredStatistics() if (requirements.getRequiredStatistics().isEmpty()) { description.add(this.user.getTranslation(reference + "none")); } else { @@ -1674,8 +1673,8 @@ private enum RewardButton { public enum RequirementButton { REQUIRED_ENTITIES, REMOVE_ENTITIES, REQUIRED_BLOCKS, REMOVE_BLOCKS, SEARCH_RADIUS, REQUIRED_PERMISSIONS, REQUIRED_ITEMS, REMOVE_ITEMS, ADD_IGNORED_META, REMOVE_IGNORED_META, REQUIRED_EXPERIENCE, REMOVE_EXPERIENCE, - REQUIRED_LEVEL, REQUIRED_MONEY, REMOVE_MONEY, STATISTIC_SHOW, STATISTIC, STATISTIC_BLOCKS, STATISTIC_ITEMS, - STATISTIC_ENTITIES, NEW_STATISTIC, + REQUIRED_LEVEL, REQUIRED_MONEY, REMOVE_MONEY, STATISTIC, STATISTIC_BLOCKS, STATISTIC_ITEMS, + STATISTIC_ENTITIES, STATISTIC_AMOUNT, REMOVE_STATISTIC, REQUIRED_MATERIALTAGS, REQUIRED_ENTITYTAGS, REQUIRED_STATISTICS, REMOVE_STATISTICS, } From 9be26141c57f473a5ab04c8ff98cd18fb8df4a9f Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 10 Feb 2025 04:29:53 -0800 Subject: [PATCH 20/34] Improve the icons for picking entities --- .../panel/admin/ManageStatisticsPanel.java | 4 +- .../panel/util/SingleEntitySelector.java | 441 +++++++++--------- 2 files changed, 230 insertions(+), 215 deletions(-) diff --git a/src/main/java/world/bentobox/challenges/panel/admin/ManageStatisticsPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/ManageStatisticsPanel.java index 199dbce2..2a8f68ab 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/ManageStatisticsPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/ManageStatisticsPanel.java @@ -14,10 +14,8 @@ import org.bukkit.Material; import org.bukkit.Statistic; import org.bukkit.inventory.ItemStack; -import org.eclipse.jdt.annotation.Nullable; import lv.id.bonne.panelutils.PanelUtils; -import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.panels.Panel; import world.bentobox.bentobox.api.panels.PanelItem; import world.bentobox.bentobox.api.panels.builders.PanelBuilder; @@ -584,7 +582,7 @@ private PanelItem createStatisticRequirementButton(RequirementButton button, Sta icon = req.entity() == null ? new ItemStack(Material.BARRIER) : new ItemStack(PanelUtils.getEntityEgg(req.entity())); clickHandler = (panel, user, clickType, slot) -> { - SingleEntitySelector.open(this.user, true, (status, entity) -> { + SingleEntitySelector.open(this.user, (status, entity) -> { if (status) { // Replace the old with the new statisticsList.removeIf(sr -> sr.equals(req)); diff --git a/src/main/java/world/bentobox/challenges/panel/util/SingleEntitySelector.java b/src/main/java/world/bentobox/challenges/panel/util/SingleEntitySelector.java index f22080fb..fd254faf 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/SingleEntitySelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/SingleEntitySelector.java @@ -6,6 +6,8 @@ import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.Set; import java.util.function.BiConsumer; import java.util.stream.Collectors; @@ -28,216 +30,231 @@ */ public class SingleEntitySelector extends PagedSelector { - /** - * Instantiates a new Single entity selector. - * - * @param user the user - * @param asEggs the boolean - * @param mode the mode - * @param excluded the excluded - * @param consumer the consumer - */ - private SingleEntitySelector(User user, boolean asEggs, Mode mode, Set excluded, BiConsumer consumer) - { - super(user); - this.consumer = consumer; - this.asEggs = asEggs; - this.elements = Arrays.stream(EntityType.values()). - filter(entity -> !excluded.contains(entity)). - filter(entity -> { - if (mode == Mode.ALIVE) - { - return entity.isAlive(); - } - else - { - return true; - } - }). - // Sort by names - sorted(Comparator.comparing(EntityType::name)). - collect(Collectors.toList()); - // Init without filters applied. - this.filterElements = this.elements; - } - - - /** - * This method opens GUI that allows to select challenge type. - * - * @param user User who opens GUI. - * @param consumer Consumer that allows to get clicked type. - */ - public static void open(User user, boolean asEggs, Mode mode, Set excluded, BiConsumer consumer) - { - new SingleEntitySelector(user, asEggs, mode, excluded, consumer).build(); - } - - - /** - * This method opens GUI that allows to select challenge type. - * - * @param user User who opens GUI. - * @param consumer Consumer that allows to get clicked type. - */ - public static void open(User user, boolean asEggs, BiConsumer consumer) - { - new SingleEntitySelector(user, asEggs, Mode.ANY, new HashSet<>(), consumer).build(); - } - - - /** - * This method opens GUI that allows to select challenge type. - * - * @param user User who opens GUI. - * @param consumer Consumer that allows to get clicked type. - */ - public static void open(User user, boolean asEggs, Mode mode, BiConsumer consumer) - { - new SingleEntitySelector(user, asEggs, mode, new HashSet<>(), consumer).build(); - } - - -// --------------------------------------------------------------------- -// Section: Methods -// --------------------------------------------------------------------- - - - /** - * This method builds - */ - @Override - protected void build() - { - PanelBuilder panelBuilder = new PanelBuilder().user(this.user); - panelBuilder.name(this.user.getTranslation(Constants.TITLE + "entity-selector")); - - PanelUtils.fillBorder(panelBuilder, Material.BLUE_STAINED_GLASS_PANE); - - this.populateElements(panelBuilder, this.filterElements); - - panelBuilder.item(4, this.createButton()); - - panelBuilder.build(); - } - - - /** - * This method is called when filter value is updated. - */ - @Override - protected void updateFilters() - { - if (this.searchString == null || this.searchString.isBlank()) - { - this.filterElements = this.elements; - } - else - { - this.filterElements = this.elements.stream(). - filter(element -> { - // If element name is set and name contains search field, then do not filter out. - return element.name().toLowerCase().contains(this.searchString.toLowerCase()); - }). - distinct(). - collect(Collectors.toList()); - } - } - - - /** - * This method builds PanelItem for given entity. - * @param entity Entity which PanelItem must be created. - * @return new PanelItem for given Entity. - */ - @Override - protected PanelItem createElementButton(EntityType entity) - { - final String reference = Constants.BUTTON + "entity."; - - List description = new ArrayList<>(); - description.add(this.user.getTranslation(reference + "description", - "[id]", entity.name())); - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-choose")); - - return new PanelItemBuilder(). - name(this.user.getTranslation(reference + "name", "[entity]", - Utils.prettifyObject(entity, this.user))). - icon(this.asEggs ? PanelUtils.getEntityEgg(entity) : PanelUtils.getEntityHead(entity)). - description(description). - clickHandler((panel, user1, clickType, slot) -> { - this.consumer.accept(true, entity); - return true; - }). - build(); - } - - - /** - * This method creates PanelItem button of requested type. - * @return new PanelItem with requested functionality. - */ - private PanelItem createButton() - { - final String reference = Constants.BUTTON + "cancel."; - - final String name = this.user.getTranslation(reference + "name"); - final List description = new ArrayList<>(3); - description.add(this.user.getTranslation(reference + "description")); - - ItemStack icon = new ItemStack(Material.IRON_DOOR); - PanelItem.ClickHandler clickHandler = (panel, user1, clickType, slot) -> - { - this.consumer.accept(false, null); - return true; - }; - - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-cancel")); - - return new PanelItemBuilder(). - icon(icon). - name(name). - description(description). - clickHandler(clickHandler). - build(); - } - - -// --------------------------------------------------------------------- -// Section: Mode -// --------------------------------------------------------------------- - - - public enum Mode - { - ALIVE, - ANY - } - - -// --------------------------------------------------------------------- -// Section: Variables -// --------------------------------------------------------------------- - - /** - * List with elements that will be displayed in current GUI. - */ - private final List elements; - - /** - * Indicates if entities are displayed as eggs or heads. - */ - private final boolean asEggs; - - /** - * This variable stores consumer. - */ - private final BiConsumer consumer; - - /** - * Stores filtered items. - */ - private List filterElements; + + /** + * Non entities that really cannot be used + */ + private static final List NON_ENTITIES = List.of(EntityType.UNKNOWN, EntityType.BLOCK_DISPLAY, + EntityType.ITEM_DISPLAY, + EntityType.TEXT_DISPLAY, EntityType.FALLING_BLOCK, EntityType.FIREBALL, EntityType.FISHING_BOBBER, + EntityType.GIANT, EntityType.ILLUSIONER, EntityType.INTERACTION, EntityType.LIGHTNING_BOLT, + EntityType.LLAMA_SPIT, EntityType.MARKER, EntityType.SHULKER_BULLET, EntityType.SMALL_FIREBALL, + EntityType.DRAGON_FIREBALL, EntityType.EVOKER_FANGS, EntityType.BREEZE_WIND_CHARGE, + EntityType.AREA_EFFECT_CLOUD); + + /** + * Instantiates a new Single entity selector. + * + * @param user the user + * @param asEggs the boolean + * @param mode the mode + * @param excluded the excluded + * @param consumer the consumer + */ + private SingleEntitySelector(User user, Mode mode, Set excluded, + BiConsumer consumer) + { + super(user); + this.consumer = consumer; + this.elements = Arrays.stream(EntityType.values()).filter(entity -> !excluded.contains(entity)) + .filter(entity -> !NON_ENTITIES.contains(entity)) + .filter(entity -> { + if (mode == Mode.ALIVE) { + return entity.isAlive(); + } else { + return true; + } + }). + // Sort by names + sorted(Comparator.comparing(EntityType::name)).collect(Collectors.toList()); + // Init without filters applied. + this.filterElements = this.elements; + } + + + /** + * This method opens GUI that allows to select challenge type. + * + * @param user User who opens GUI. + * @param consumer Consumer that allows to get clicked type. + */ + public static void open(User user, Mode mode, Set excluded, BiConsumer consumer) + { + new SingleEntitySelector(user, mode, excluded, consumer).build(); + } + + + /** + * This method opens GUI that allows to select challenge type. + * + * @param user User who opens GUI. + * @param consumer Consumer that allows to get clicked type. + */ + public static void open(User user, BiConsumer consumer) + { + new SingleEntitySelector(user, Mode.ANY, new HashSet<>(), consumer).build(); + } + + + /** + * This method opens GUI that allows to select challenge type. + * + * @param user User who opens GUI. + * @param consumer Consumer that allows to get clicked type. + */ + public static void open(User user, Mode mode, BiConsumer consumer) + { + new SingleEntitySelector(user, mode, new HashSet<>(), consumer).build(); + } + + + // --------------------------------------------------------------------- + // Section: Methods + // --------------------------------------------------------------------- + + + /** + * This method builds + */ + @Override + protected void build() { + PanelBuilder panelBuilder = new PanelBuilder().user(this.user); + panelBuilder.name(this.user.getTranslation(Constants.TITLE + "entity-selector")); + + PanelUtils.fillBorder(panelBuilder, Material.BLUE_STAINED_GLASS_PANE); + + this.populateElements(panelBuilder, this.filterElements); + + panelBuilder.item(4, this.createButton()); + + panelBuilder.build(); + } + + + /** + * This method is called when filter value is updated. + */ + @Override + protected void updateFilters() { + if (this.searchString == null || this.searchString.isBlank()) { + this.filterElements = this.elements; + } else { + this.filterElements = this.elements.stream().filter(element -> { + // If element name is set and name contains search field, then do not filter out. + return element.name().toLowerCase().contains(this.searchString.toLowerCase()); + }).distinct().collect(Collectors.toList()); + } + } + + + /** + * This method builds PanelItem for given entity. + * @param entity Entity which PanelItem must be created. + * @return new PanelItem for given Entity. + */ + @Override + protected PanelItem createElementButton(EntityType entity) { + final String reference = Constants.BUTTON + "entity."; + + List description = new ArrayList<>(); + description.add(this.user.getTranslation(reference + "description", "[id]", entity.name())); + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-choose")); + + + return new PanelItemBuilder() + .name(this.user.getTranslation(reference + "name", "[entity]", Utils.prettifyObject(entity, this.user))) + .icon(getIcon(entity)).description(description) + .clickHandler((panel, user1, clickType, slot) -> { + this.consumer.accept(true, entity); + return true; + }).build(); + } + + /** + * Get an ItemStack icon for any entity type, or PAPER if it's not really known + * @param et entity type + * @return ItemStack + */ + public static ItemStack getIcon(EntityType et) { + // Check for Materials like boats that are named the same as their entitytype. + Material m = Material.getMaterial(et.getKey().getKey().toUpperCase(Locale.ENGLISH)); + if (m != null) { + return new ItemStack(m); + } + // Try to get the spawn egg for the given entity by using the naming convention. + String spawnEggName = et.name() + "_SPAWN_EGG"; + try { + Material spawnEgg = Material.valueOf(spawnEggName); + // If found, return an ItemStack of the spawn egg. + return new ItemStack(spawnEgg); + } catch (IllegalArgumentException ex) { + // No spawn egg material exists for this entity type. + } + // Fallback + return switch (et) { + case EYE_OF_ENDER -> new ItemStack(Material.ENDER_EYE); + case LEASH_KNOT -> new ItemStack(Material.LEAD); + case OMINOUS_ITEM_SPAWNER -> new ItemStack(Material.TRIAL_SPAWNER); + case PLAYER -> new ItemStack(Material.PLAYER_HEAD); + case SPAWNER_MINECART -> new ItemStack(Material.MINECART); + case TRADER_LLAMA -> new ItemStack(Material.LLAMA_SPAWN_EGG); + case WITHER_SKULL -> new ItemStack(Material.WITHER_SKELETON_SKULL); + default -> new ItemStack(Material.PAPER); + + }; + } + + /** + * This method creates PanelItem button of requested type. + * @return new PanelItem with requested functionality. + */ + private PanelItem createButton() { + final String reference = Constants.BUTTON + "cancel."; + + final String name = this.user.getTranslation(reference + "name"); + final List description = new ArrayList<>(3); + description.add(this.user.getTranslation(reference + "description")); + + ItemStack icon = new ItemStack(Material.IRON_DOOR); + PanelItem.ClickHandler clickHandler = (panel, user1, clickType, slot) -> { + this.consumer.accept(false, null); + return true; + }; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-cancel")); + + return new PanelItemBuilder().icon(icon).name(name).description(description).clickHandler(clickHandler).build(); + } + + + // --------------------------------------------------------------------- + // Section: Mode + // --------------------------------------------------------------------- + + + public enum Mode { + ALIVE, ANY + } + + + // --------------------------------------------------------------------- + // Section: Variables + // --------------------------------------------------------------------- + + /** + * List with elements that will be displayed in current GUI. + */ + private final List elements; + + /** + * This variable stores consumer. + */ + private final BiConsumer consumer; + + /** + * Stores filtered items. + */ + private List filterElements; } From 80b0fb4a90edecde5677281e401de9a8d28598b2 Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 10 Feb 2025 04:57:34 -0800 Subject: [PATCH 21/34] Unify entity icons across all GUIs --- .../panel/admin/ManageEntitiesPanel.java | 44 +++++++------------ .../panel/admin/ManageEntityGroupsPanel.java | 13 ++---- .../panel/util/MultiEntitySelector.java | 18 +++----- .../panel/util/SingleEntitySelector.java | 3 +- 4 files changed, 26 insertions(+), 52 deletions(-) diff --git a/src/main/java/world/bentobox/challenges/panel/admin/ManageEntitiesPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/ManageEntitiesPanel.java index 6df39307..a1534d95 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/ManageEntitiesPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/ManageEntitiesPanel.java @@ -5,15 +5,16 @@ import java.util.Map; import org.bukkit.Material; +import org.bukkit.Tag; import org.bukkit.entity.EntityType; import org.bukkit.inventory.ItemStack; -import lv.id.bonne.panelutils.PanelUtils; import world.bentobox.bentobox.api.panels.PanelItem; import world.bentobox.bentobox.api.panels.builders.PanelBuilder; import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; import world.bentobox.challenges.panel.CommonPanel; import world.bentobox.challenges.panel.util.MultiEntitySelector; +import world.bentobox.challenges.panel.util.SingleEntitySelector; import world.bentobox.challenges.utils.Constants; import world.bentobox.challenges.utils.Utils; @@ -32,17 +33,12 @@ public class ManageEntitiesPanel extends AbstractManageEnumPanel { /** - * Flag to indicate whether entities should be displayed as eggs (true) or mob heads (false). - */ - private boolean asEggs = true; - - /** - * Private constructor that initializes the ManageEntitiesPanel with the provided - * entities map. - * - * @param parentGUI The parent panel that spawns this panel. - * @param requiredEntities A map of EntityType objects to their required counts. - */ + * Private constructor that initializes the ManageEntitiesPanel with the provided + * entities map. + * + * @param parentGUI The parent panel that spawns this panel. + * @param requiredEntities A map of EntityType objects to their required counts. + */ private ManageEntitiesPanel(CommonPanel parentGUI, Map requiredEntities) { super(parentGUI, requiredEntities); } @@ -69,7 +65,12 @@ public static void open(CommonPanel parentGUI, Map required */ @Override protected ItemStack getElementIcon(EntityType entity, int count) { - return asEggs ? PanelUtils.getEntityEgg(entity, count) : PanelUtils.getEntityHead(entity, count); + if (Tag.ENTITY_TYPES_CAN_TURN_IN_BOATS.isTagged(entity) && count > 1) { + return new ItemStack(Material.OAK_PLANKS, count); // Boats cannot be stacked + } + ItemStack icon = SingleEntitySelector.getIcon(entity); + icon.setAmount(count); + return icon; } /** @@ -113,8 +114,6 @@ protected void addFunctionalButtons(PanelBuilder panelBuilder) { panelBuilder.item(3, createButton(Button.ADD_ENTITY)); // Position 5: Button for removing selected entities. panelBuilder.item(5, createButton(Button.REMOVE_ENTITY)); - // Position 8: Button to switch between displaying entity eggs and mob heads. - panelBuilder.item(8, createButton(Button.SWITCH_ENTITY)); } /** @@ -138,7 +137,7 @@ private PanelItem createButton(Button button) { icon = new ItemStack(Material.BUCKET); clickHandler = (panel, user, clickType, slot) -> { // Open a multi-selection tool to add new entities. - MultiEntitySelector.open(this.user, this.asEggs, MultiEntitySelector.Mode.ALIVE, this.itemsMap.keySet(), + MultiEntitySelector.open(this.user, MultiEntitySelector.Mode.ALIVE, this.itemsMap.keySet(), (status, entities) -> { if (status) { // For each selected entity, add it to the map with a default count. @@ -179,19 +178,6 @@ private PanelItem createButton(Button button) { description.add(""); description.add(this.user.getTranslation(Constants.TIPS + "click-to-remove")); } - case SWITCH_ENTITY -> { - // Button to toggle the display mode between entity eggs and mob heads. - icon = new ItemStack(asEggs ? Material.EGG : Material.PLAYER_HEAD); - clickHandler = (panel, user, clickType, slot) -> { - // Toggle the display mode flag and rebuild the panel. - this.asEggs = !this.asEggs; - this.build(); - return true; - }; - glow = false; - description.add(""); - description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); - } default -> { icon = new ItemStack(Material.PAPER); clickHandler = null; diff --git a/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java index 5bac9640..a8c8cf0e 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java @@ -26,6 +26,7 @@ import world.bentobox.challenges.panel.CommonPanel; import world.bentobox.challenges.panel.ConversationUtils; import world.bentobox.challenges.panel.util.MultiEntityTypeTagsSelector; +import world.bentobox.challenges.panel.util.SingleEntitySelector; import world.bentobox.challenges.panel.util.UnifiedMultiSelector.Mode; import world.bentobox.challenges.utils.Constants; import world.bentobox.challenges.utils.Utils; @@ -316,18 +317,12 @@ protected PanelItem createElementButton(Tag tag) if (entityTag.getKey().getKey().contains("boat")) { return new ItemStack(Material.OAK_PLANKS, quantity); // Boats cannot be stacked } - EntityType entType = Registry.ENTITY_TYPE.stream().filter(entityTag::isTagged).findAny().orElse(null); + EntityType entType = Registry.ENTITY_TYPE.stream() + .filter(entityTag::isTagged).findAny().orElse(null); if (entType == null) { return new ItemStack(Material.PAPER, quantity); } - String eggName = entType.getKey().getKey().toUpperCase(Locale.ENGLISH) + "_SPAWN_EGG"; - Material result; - try { - result = Material.valueOf(eggName); - } catch (Exception e) { - result = Material.PAPER; - } - return new ItemStack(result, quantity); + return SingleEntitySelector.getIcon(entType); } diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java index c5211abf..77d37149 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java @@ -11,7 +11,6 @@ import org.bukkit.entity.EntityType; import org.bukkit.inventory.ItemStack; -import lv.id.bonne.panelutils.PanelUtils; import world.bentobox.bentobox.api.user.User; import world.bentobox.challenges.utils.Utils; @@ -22,22 +21,19 @@ */ public class MultiEntitySelector extends UnifiedMultiSelector { - private final boolean asEgg; private final Set excluded; /** * Private constructor. * * @param user the user opening the selector - * @param asEgg if true, display entities using their spawn egg icon; otherwise, use the entity head * @param mode determines whether to show only living entities (ALIVE) or all (ANY) * @param excluded a set of EntityType values to exclude * @param consumer the callback to be invoked when the user confirms or cancels */ - private MultiEntitySelector(User user, boolean asEgg, Mode mode, Set excluded, + private MultiEntitySelector(User user, Mode mode, Set excluded, java.util.function.BiConsumer> consumer) { super(user, mode, consumer); - this.asEgg = asEgg; this.excluded = excluded; } @@ -45,26 +41,24 @@ private MultiEntitySelector(User user, boolean asEgg, Mode mode, Set * Opens the MultiEntitySelector GUI with the specified parameters. * * @param user the user who opens the GUI - * @param asEgg if true, show the entity spawn egg icon; otherwise, show the entity head * @param mode the filtering mode (ALIVE or ANY) * @param excluded a set of EntityType values to exclude from the list * @param consumer a callback to receive the result */ - public static void open(User user, boolean asEgg, Mode mode, Set excluded, + public static void open(User user, Mode mode, Set excluded, java.util.function.BiConsumer> consumer) { - new MultiEntitySelector(user, asEgg, mode, excluded, consumer).build(); + new MultiEntitySelector(user, mode, excluded, consumer).build(); } /** * Opens the MultiEntitySelector GUI with default parameters (mode ANY and no exclusions). * * @param user the user who opens the GUI - * @param asEgg if true, show the entity spawn egg icon; otherwise, show the entity head * @param consumer a callback to receive the result */ - public static void open(User user, boolean asEgg, + public static void open(User user, java.util.function.BiConsumer> consumer) { - new MultiEntitySelector(user, asEgg, Mode.ANY, new HashSet<>(), consumer).build(); + new MultiEntitySelector(user, Mode.ANY, new HashSet<>(), consumer).build(); } /** @@ -99,7 +93,7 @@ protected String getElementKeyPrefix() { */ @Override protected ItemStack getIcon(EntityType element) { - return asEgg ? PanelUtils.getEntityEgg(element) : PanelUtils.getEntityHead(element); + return SingleEntitySelector.getIcon(element); } /** diff --git a/src/main/java/world/bentobox/challenges/panel/util/SingleEntitySelector.java b/src/main/java/world/bentobox/challenges/panel/util/SingleEntitySelector.java index fd254faf..aa2355ac 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/SingleEntitySelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/SingleEntitySelector.java @@ -7,7 +7,6 @@ import java.util.HashSet; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.Set; import java.util.function.BiConsumer; import java.util.stream.Collectors; @@ -34,7 +33,7 @@ public class SingleEntitySelector extends PagedSelector /** * Non entities that really cannot be used */ - private static final List NON_ENTITIES = List.of(EntityType.UNKNOWN, EntityType.BLOCK_DISPLAY, + public static final List NON_ENTITIES = List.of(EntityType.UNKNOWN, EntityType.BLOCK_DISPLAY, EntityType.ITEM_DISPLAY, EntityType.TEXT_DISPLAY, EntityType.FALLING_BLOCK, EntityType.FIREBALL, EntityType.FISHING_BOBBER, EntityType.GIANT, EntityType.ILLUSIONER, EntityType.INTERACTION, EntityType.LIGHTNING_BOLT, From 23ed29304900dac0df93a3d5765eec5de6a0b73f Mon Sep 17 00:00:00 2001 From: tastybento Date: Mon, 10 Feb 2025 05:07:24 -0800 Subject: [PATCH 22/34] Unified icons and made non-alive entities available for selection --- .../bentobox/challenges/panel/util/MultiEntitySelector.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java b/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java index 77d37149..7df90a56 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java +++ b/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java @@ -67,7 +67,7 @@ public static void open(User user, @Override protected List getElements() { return Arrays.stream(EntityType.values()).filter(entity -> excluded == null || !excluded.contains(entity)) - .filter(entity -> mode == Mode.ALIVE ? entity.isAlive() : true) + .filter(entity -> !SingleEntitySelector.NON_ENTITIES.contains(entity)) .sorted(Comparator.comparing(EntityType::name)).collect(Collectors.toList()); } From f6ae07adbcaa6caebbad1a347e8cdf2d22f20cc9 Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 12 Feb 2025 08:18:12 +0900 Subject: [PATCH 23/34] Fix naming of individual materials --- src/main/java/world/bentobox/challenges/utils/Utils.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/challenges/utils/Utils.java b/src/main/java/world/bentobox/challenges/utils/Utils.java index 8d2b6b01..f2e8a810 100644 --- a/src/main/java/world/bentobox/challenges/utils/Utils.java +++ b/src/main/java/world/bentobox/challenges/utils/Utils.java @@ -316,6 +316,9 @@ public static > String prettifyObject(@Nullable T object, User if (object == null) { return ""; } + if (object instanceof Material m) { + return prettifyMaterial(m, user); + } // Build a translation key using the enum name. String translation = user .getTranslationOrNothing(Constants.MATERIALS + object.name().toLowerCase(Locale.ENGLISH) + ".name"); @@ -331,7 +334,7 @@ public static > String prettifyObject(@Nullable T object, User * @param user User who will see the object. * @return Prettified string for Material. */ - public static String prettifyObject(@Nullable Material object, User user) + public static String prettifyMaterial(@Nullable Material object, User user) { // Nothing to translate if (object == null) From dc7309ba99ee25740ed39068724bb49aeec33009 Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 12 Feb 2025 08:41:27 +0900 Subject: [PATCH 24/34] Reduce size of success notice to fit on screen --- src/main/resources/locales/en-US.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 01eb9a14..95ad729b 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -1124,9 +1124,9 @@ challenges: write-search: "&e Please enter a search value. (Type 'cancel' to exit)" search-updated: "&a Search value updated." titles: - challenge-title: "Successfully completed" + challenge-title: "Success!" challenge-subtitle: "[friendlyName]" - level-title: "Successfully completed" + level-title: "Success!" level-subtitle: "[friendlyName]" messages: completed: "&2 You completed the challenge [name] for [player]!" From 005fc33df1121b7ed5c967a3aa8a58702a4b1aa5 Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 12 Feb 2025 16:32:43 +0900 Subject: [PATCH 25/34] Add permission [gamemode].challenges.waiver-add.x to addon Enables per-player permission waivers. --- .../database/object/ChallengeLevel.java | 3 +- .../managers/ChallengesManager.java | 34 ++++++++++++------- .../challenges/panel/CommonPanel.java | 19 +++++++++-- .../panel/admin/EditLevelPanel.java | 2 +- 4 files changed, 40 insertions(+), 18 deletions(-) diff --git a/src/main/java/world/bentobox/challenges/database/object/ChallengeLevel.java b/src/main/java/world/bentobox/challenges/database/object/ChallengeLevel.java index 518ec828..f9d2982f 100644 --- a/src/main/java/world/bentobox/challenges/database/object/ChallengeLevel.java +++ b/src/main/java/world/bentobox/challenges/database/object/ChallengeLevel.java @@ -74,7 +74,8 @@ public ChallengeLevel() @ConfigComment("") @ConfigComment("The number of undone challenges that can be left on this level before") - @ConfigComment("unlocking next level.") + @ConfigComment("unlocking next level. Give players more with the permission") + @ConfigComment("[gamemode].challenges.waiver-add.x where x is a number") @Expose private int waiverAmount = 1; diff --git a/src/main/java/world/bentobox/challenges/managers/ChallengesManager.java b/src/main/java/world/bentobox/challenges/managers/ChallengesManager.java index 89036cef..7ae75e50 100644 --- a/src/main/java/world/bentobox/challenges/managers/ChallengesManager.java +++ b/src/main/java/world/bentobox/challenges/managers/ChallengesManager.java @@ -1121,8 +1121,9 @@ private void resetAllChallenges(@NonNull String storageDataID, @NonNull String g * @return Level status - how many challenges still to do on which level */ @NonNull - private List getAllChallengeLevelStatus(String storageDataID, String gameMode) + private List getAllChallengeLevelStatus(User user, World world, String gameMode) { + String storageDataID = this.getDataUniqueID(user, Util.getWorld(world)); this.addPlayerData(storageDataID); ChallengesPlayerData playerData = this.playerCacheData.get(storageDataID); @@ -1135,6 +1136,14 @@ private List getAllChallengeLevelStatus(String storageDataID, Strin int doneChallengeCount = 0; boolean previousUnlocked = true; + // Get user's waiver add from permission + // Get per-user waiver amount + int waiverAdd = user + .getPermissionValue(addon.getPlugin().getIWM().getPermissionPrefix(world) + "challenges.waiver-add", 0); + if (waiverAdd < 0) { + waiverAdd = 0; + } + // For each challenge level, check how many the storageDataID has done for (ChallengeLevel level : challengeLevelList) { @@ -1147,7 +1156,7 @@ private List getAllChallengeLevelStatus(String storageDataID, Strin this.getLevelChallenges(previousLevel); int challengesToDo = previousLevel == null ? 0 : - (previousChallengeList.size() - doneChallengeCount - previousLevel.getWaiverAmount()); + (previousChallengeList.size() - doneChallengeCount - (previousLevel.getWaiverAmount() + waiverAdd)); List challengeList = this.getLevelChallenges(level); @@ -1177,14 +1186,15 @@ private List getAllChallengeLevelStatus(String storageDataID, Strin /** * This method returns LevelStatus object for given challenge level. - * @param storageDataID User which level status must be acquired. + * @param user User which level status must be acquired. * @param world World where level is living. * @param level Level which status must be calculated. * @return LevelStatus of given level. */ @Nullable - private LevelStatus getChallengeLevelStatus(@NonNull String storageDataID, World world, @NonNull ChallengeLevel level) + private LevelStatus getChallengeLevelStatus(User user, World world, @NonNull ChallengeLevel level) { + String storageDataID = this.getDataUniqueID(user.getUniqueId(), Util.getWorld(world)); this.addPlayerData(storageDataID); ChallengesPlayerData playerData = this.playerCacheData.get(storageDataID); @@ -1651,11 +1661,9 @@ public boolean isLevelCompleted(User user, World world, ChallengeLevel level) */ public boolean isLevelUnlocked(User user, World world, ChallengeLevel level) { - String storageDataID = this.getDataUniqueID(user, Util.getWorld(world)); - this.addPlayerData(storageDataID); - - return this.islandWorldManager.getAddon(world).filter(gameMode -> this.getAllChallengeLevelStatus(storageDataID, gameMode.getDescription().getName()). - stream(). + return this.islandWorldManager.getAddon(world).filter(gameMode -> this + .getAllChallengeLevelStatus(user, world, gameMode.getDescription().getName()) + .stream(). filter(LevelStatus::isUnlocked). anyMatch(lv -> lv.getLevel().equals(level))). isPresent(); @@ -1709,7 +1717,8 @@ public boolean validateLevelCompletion(User user, World world, ChallengeLevel le @Nullable public LevelStatus getChallengeLevelStatus(UUID uniqueId, World world, ChallengeLevel level) { - return this.getChallengeLevelStatus(this.getDataUniqueID(uniqueId, Util.getWorld(world)), world, level); + User user = User.getInstance(uniqueId); + return this.getChallengeLevelStatus(user, world, level); } @@ -1725,9 +1734,8 @@ public List getAllChallengeLevelStatus(User user, World world) { return this.islandWorldManager.getAddon(world).map(gameMode -> this.getAllChallengeLevelStatus( - this.getDataUniqueID(user, Util.getWorld(world)), - gameMode.getDescription().getName())). - orElse(Collections.emptyList()); + user, world, gameMode.getDescription().getName())) + .orElse(Collections.emptyList()); } diff --git a/src/main/java/world/bentobox/challenges/panel/CommonPanel.java b/src/main/java/world/bentobox/challenges/panel/CommonPanel.java index 5a5b95b4..86118768 100644 --- a/src/main/java/world/bentobox/challenges/panel/CommonPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/CommonPanel.java @@ -13,7 +13,6 @@ import java.util.stream.Collectors; import org.bukkit.Material; -import org.bukkit.Statistic; import org.bukkit.World; import org.bukkit.inventory.ItemStack; import org.eclipse.jdt.annotation.NonNull; @@ -743,10 +742,17 @@ protected List generateLevelDescription(ChallengeLevel level) { // my eye :) // Get status in single string String status = ""; + // Get per-user waiver amount + int waiverAdd = user.getPermissionValue( + addon.getPlugin().getIWM().getPermissionPrefix(world) + "challenges.waiver-add", 0); + if (waiverAdd < 0) { + waiverAdd = 0; + } + waiverAdd += level.getWaiverAmount(); // Get requirements in single string String waiver = this.manager.isLastLevel(level, this.world) ? "" : this.user.getTranslationOrNothing(reference + "waiver", "[number]", - String.valueOf(level.getWaiverAmount())); + String.valueOf(waiverAdd)); // Get rewards in single string String rewards = this.generateReward(level); @@ -776,11 +782,18 @@ protected List generateLevelDescription(LevelStatus levelStatus, User us // my eye :) // Get status in single string String status = this.generateLevelStatus(levelStatus); + // Get per-user waiver amount + int waiverAdd = user + .getPermissionValue(addon.getPlugin().getIWM().getPermissionPrefix(world) + "challenges.waiver-add", 0); + if (waiverAdd < 0) { + waiverAdd = 0; + } + waiverAdd += level.getWaiverAmount(); // Get requirements in single string String waiver = this.manager.isLastLevel(level, this.world) || !levelStatus.isUnlocked() || levelStatus.isComplete() ? "" : this.user.getTranslationOrNothing(reference + "waiver", "[number]", - String.valueOf(level.getWaiverAmount())); + String.valueOf(waiverAdd)); // Get rewards in single string String rewards = !levelStatus.isUnlocked() ? "" : this.generateReward(level); diff --git a/src/main/java/world/bentobox/challenges/panel/admin/EditLevelPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/EditLevelPanel.java index 0b3dcedd..2ab24700 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/EditLevelPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/EditLevelPanel.java @@ -758,7 +758,7 @@ private PanelItem createButton(Button button) } case WAIVER_AMOUNT -> { description.add(this.user.getTranslation(reference + "value", - Constants.PARAMETER_NUMBER, String.valueOf(this.challengeLevel.getWaiverAmount()))); + Constants.PARAMETER_NUMBER, String.valueOf(this.challengeLevel.getWaiverAmount()))); icon = new ItemStack(Material.HOPPER, Math.max(1, this.challengeLevel.getWaiverAmount())); clickHandler = (panel, user, clickType, i) -> { From 9849c95ba304057b22be8b30ee7ffea47f9c7997 Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 12 Feb 2025 17:19:12 +0900 Subject: [PATCH 26/34] Add switch to hide reward items from the GUI list. The admin may want to hide the rewards and just use the reward description. If the number of rewards is very long, it can be a problem to list them all. --- .../challenges/database/object/Challenge.java | 66 ++++++++++++++----- .../challenges/panel/CommonPanel.java | 2 +- .../panel/admin/EditChallengePanel.java | 27 +++++++- .../challenges/tasks/TryToComplete.java | 4 +- src/main/resources/locales/en-US.yml | 7 ++ 5 files changed, 85 insertions(+), 21 deletions(-) diff --git a/src/main/java/world/bentobox/challenges/database/object/Challenge.java b/src/main/java/world/bentobox/challenges/database/object/Challenge.java index 6187529c..6c11f537 100644 --- a/src/main/java/world/bentobox/challenges/database/object/Challenge.java +++ b/src/main/java/world/bentobox/challenges/database/object/Challenge.java @@ -163,6 +163,12 @@ public enum ChallengeType @Expose private List rewardItems = new ArrayList<>(); + /** + * Hide the list of reward items in the GUI + */ + @Expose + private Boolean hideRewardItems; + /** * Experience point reward */ @@ -857,37 +863,49 @@ public Challenge clone() try { clone = new Challenge(); + // Copy primitive and String fields clone.setUniqueId(this.uniqueId); clone.setFriendlyName(this.friendlyName); clone.setDeployed(this.deployed); + clone.setOrder(this.order); + clone.setLevel(this.level); + + // Clone collections (Strings are immutable so shallow copy is fine) clone.setDescription(new ArrayList<>(this.description)); + clone.setEnvironment(new HashSet<>(this.environment)); + clone.setRewardCommands(new ArrayList<>(this.rewardCommands)); + clone.setIgnoreRewardMetaData(new HashSet<>(this.ignoreRewardMetaData)); + + // Clone mutable objects (ItemStacks, etc.) clone.setIcon(this.icon.clone()); - clone.setOrder(this.order); + clone.setRewardItems(this.rewardItems.stream().map(ItemStack::clone) + .collect(Collectors.toCollection(() -> new ArrayList<>(this.rewardItems.size())))); + clone.setRepeatItemReward(this.repeatItemReward.stream().map(ItemStack::clone) + .collect(Collectors.toCollection(() -> new ArrayList<>(this.repeatItemReward.size())))); + clone.setRepeatRewardCommands(new ArrayList<>(this.repeatRewardCommands)); + + // Clone enum field clone.setChallengeType(ChallengeType.valueOf(this.challengeType.name())); - clone.setEnvironment(new HashSet<>(this.environment)); - clone.setLevel(this.level); + + // Clone boolean and numeric fields clone.setRemoveWhenCompleted(this.removeWhenCompleted); - clone.setRequirements(this.requirements.copy()); - clone.setRewardText(this.rewardText); - clone.setRewardItems( - this.rewardItems.stream(). - map(ItemStack::clone). - collect(Collectors.toCollection(() -> new ArrayList<>(this.rewardItems.size())))); clone.setRewardExperience(this.rewardExperience); clone.setRewardMoney(this.rewardMoney); - clone.setRewardCommands(new ArrayList<>(this.rewardCommands)); clone.setRepeatable(this.repeatable); + clone.setTimeout(this.timeout); clone.setRepeatRewardText(this.repeatRewardText); clone.setMaxTimes(this.maxTimes); clone.setRepeatExperienceReward(this.repeatExperienceReward); - clone.setRepeatItemReward( - this.repeatItemReward.stream(). - map(ItemStack::clone). - collect(Collectors.toCollection(() -> new ArrayList<>(this.repeatItemReward.size())))); clone.setRepeatMoneyReward(this.repeatMoneyReward); - clone.setRepeatRewardCommands(new ArrayList<>(this.repeatRewardCommands)); - clone.setTimeout(this.timeout); - clone.setIgnoreRewardMetaData(new HashSet<>(this.ignoreRewardMetaData)); + + // Copy custom objects + clone.setRequirements(this.requirements.copy()); + + // Clone the remaining String field + clone.setRewardText(this.rewardText); + + // Clone the Boolean field + clone.setHideRewardItems(this.hideRewardItems); } catch (Exception e) { @@ -899,4 +917,18 @@ public Challenge clone() return clone; } + + /** + * @return the hideRewardItems + */ + public boolean isHideRewardItems() { + return Objects.requireNonNullElse(hideRewardItems, false); + } + + /** + * @param hideRewardItems the hideRewardItems to set + */ + public void setHideRewardItems(boolean hideRewardItems) { + this.hideRewardItems = hideRewardItems; + } } \ No newline at end of file diff --git a/src/main/java/world/bentobox/challenges/panel/CommonPanel.java b/src/main/java/world/bentobox/challenges/panel/CommonPanel.java index 86118768..32f531b8 100644 --- a/src/main/java/world/bentobox/challenges/panel/CommonPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/CommonPanel.java @@ -661,7 +661,7 @@ private String generateReward(Challenge challenge) { String items; - if (!challenge.getRewardItems().isEmpty()) { + if (!challenge.getRewardItems().isEmpty() && !challenge.isHideRewardItems()) { StringBuilder builder = new StringBuilder(); builder.append(this.user.getTranslationOrNothing(reference + "item-title")); Utils.groupEqualItems(challenge.getRewardItems(), challenge.getIgnoreRewardMetaData()).stream() diff --git a/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java index f886e134..c9264dcc 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java @@ -150,6 +150,7 @@ private void buildMainPropertiesPanel(PanelBuilder panelBuilder) { panelBuilder.listener(new IconChanger()); panelBuilder.item(10, this.createButton(Button.NAME)); + panelBuilder.item(13, this.createButton(Button.HIDE_REWARD_ITEMS)); panelBuilder.item(16, this.createButton(Button.DEPLOYED)); panelBuilder.item(19, this.createButton(Button.ICON)); @@ -376,6 +377,30 @@ private PanelItem createButton(Button button) { description.add(""); description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); } + case HIDE_REWARD_ITEMS -> { + description.add(this.user + .getTranslation(reference + (this.challenge.isHideRewardItems() ? "hide" : "show"))); + + icon = new ItemStack(Material.LEVER); + clickHandler = (panel, user, clickType, slot) -> { + if (this.challenge.isValid()) { + this.challenge.setHideRewardItems(!challenge.isHideRewardItems()); // Toggle + } else { + Utils.sendMessage(this.user, this.world, Constants.CONVERSATIONS + "invalid-challenge", + Constants.PARAMETER_CHALLENGE, this.challenge.getFriendlyName()); + this.challenge.setDeployed(false); + } + + this.build(); + return true; + }; + glow = this.challenge.isDeployed(); + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle")); + + } + case DEPLOYED -> { description .add(this.user.getTranslation(reference + (this.challenge.isDeployed() ? "enabled" : "disabled"))); @@ -1651,7 +1676,7 @@ private enum MenuType { * Represents different buttons that could be in menus. */ private enum Button { - NAME, DEPLOYED, ICON, DESCRIPTION, ORDER, ENVIRONMENT, REMOVE_ON_COMPLETE, + NAME, DEPLOYED, ICON, DESCRIPTION, ORDER, ENVIRONMENT, REMOVE_ON_COMPLETE, HIDE_REWARD_ITEMS, } /** diff --git a/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java b/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java index 3b2824dd..1f700425 100644 --- a/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java +++ b/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java @@ -1513,8 +1513,8 @@ private ChallengeResult checkStatistic(int factor) { cr.add(new ChallengeResult().setMeetsRequirements().setCompleteFactor(factor)); } } - // Check results -- all must pass - if (cr.stream().allMatch(result -> result.meetsRequirements)) { + // Check results -- there must be some and all must pass + if (!cr.isEmpty() && cr.stream().allMatch(result -> result.meetsRequirements)) { // Return any of them, because they pass return cr.getFirst(); } diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 95ad729b..7d883772 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -209,6 +209,13 @@ challenges: name: "&f&l Rewards" description: |- &7 View the rewards properties. + hide_reward_items: + name: "&f&l Hide Reward Items" + description: |- + &7 Toggle whether reward items + &7 are shown or not in GUI. + hide: "&2 Hide" + show: "&c Show" deployed: name: "&f&l Deployment" description: |- From 2edf1a7d59e918db041208e07862238f714bdc3d Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 12 Feb 2025 17:52:04 +0900 Subject: [PATCH 27/34] Fix bug with cloning and Boolean vs booleans --- .../world/bentobox/challenges/database/object/Challenge.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/world/bentobox/challenges/database/object/Challenge.java b/src/main/java/world/bentobox/challenges/database/object/Challenge.java index 6c11f537..e20d9b35 100644 --- a/src/main/java/world/bentobox/challenges/database/object/Challenge.java +++ b/src/main/java/world/bentobox/challenges/database/object/Challenge.java @@ -928,7 +928,7 @@ public boolean isHideRewardItems() { /** * @param hideRewardItems the hideRewardItems to set */ - public void setHideRewardItems(boolean hideRewardItems) { + public void setHideRewardItems(Boolean hideRewardItems) { this.hideRewardItems = hideRewardItems; } } \ No newline at end of file From fb782c23cd819082296adbfaa7bc899eecfe17be Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 12 Feb 2025 21:19:16 +0900 Subject: [PATCH 28/34] Added a PAPI formula option to Other challenge types. --- pom.xml | 12 ++ .../object/requirements/CheckPapi.java | 183 ++++++++++++++++++ .../requirements/OtherRequirements.java | 23 +++ .../panel/admin/EditChallengePanel.java | 32 ++- .../challenges/tasks/TryToComplete.java | 118 ++++++----- src/main/resources/locales/en-US.yml | 8 + 6 files changed, 314 insertions(+), 62 deletions(-) create mode 100644 src/main/java/world/bentobox/challenges/database/object/requirements/CheckPapi.java diff --git a/pom.xml b/pom.xml index ebe3f7cd..1de3c829 100644 --- a/pom.xml +++ b/pom.xml @@ -117,6 +117,11 @@ minecraft-repo https://libraries.minecraft.net/ + + + placeholderapi + https://repo.extendedclip.com/releases/ + @@ -198,6 +203,13 @@ commons-math3 3.6.1 + + + me.clip + placeholderapi + 2.11.6 + provided + diff --git a/src/main/java/world/bentobox/challenges/database/object/requirements/CheckPapi.java b/src/main/java/world/bentobox/challenges/database/object/requirements/CheckPapi.java new file mode 100644 index 00000000..5b6b738f --- /dev/null +++ b/src/main/java/world/bentobox/challenges/database/object/requirements/CheckPapi.java @@ -0,0 +1,183 @@ +package world.bentobox.challenges.database.object.requirements; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.bukkit.entity.Player; + +import me.clip.placeholderapi.PlaceholderAPI; + +public class CheckPapi { + + /** + * Evaluates the formula by first replacing PAPI placeholders (using the provided Player) + * and then evaluating the resulting expression. The expression is expected to be a series + * of numeric comparisons (using =, <>, <=, >=, <, >) joined by Boolean operators AND and OR. + * + * For example: + * "%aoneblock_my_island_lifetime_count% >= 1000 AND %Level_aoneblock_island_level% >= 100" + * + * If any placeholder evaluates to a non-numeric value or the formula is malformed, false is returned. + * + * @param player the Player used for placeholder replacement. + * @param formula the formula to evaluate. + * @return true if the formula evaluates to true, false otherwise. + */ + public static boolean evaluate(Player player, String formula) { + // Replace PAPI placeholders with actual values using the provided Player. + String parsedFormula = PlaceholderAPI.setPlaceholders(player, formula); + + // Tokenize the parsed formula (tokens are assumed to be separated by whitespace). + List tokens = tokenize(parsedFormula); + if (tokens.isEmpty()) { + return false; + } + + try { + Parser parser = new Parser(tokens); + boolean result = parser.parseExpression(); + // If there are leftover tokens, the expression is malformed. + if (parser.hasNext()) { + return false; + } + return result; + } catch (Exception e) { + // Any error in parsing or evaluation results in false. + return false; + } + } + + /** + * Splits the given string into tokens using whitespace as the delimiter. + * + * @param s the string to tokenize. + * @return a list of tokens. + */ + private static List tokenize(String s) { + return new ArrayList<>(Arrays.asList(s.split("\\s+"))); + } + + /** + * A simple recursive descent parser that evaluates expressions according to the following grammar: + * + *
+     * Expression -> Term { OR Term }
+     * Term       -> Factor { AND Factor }
+     * Factor     -> operand operator operand
+     * 
+ * + * A Factor is expected to be a numeric condition in the form: + * number operator number + * where operator is one of: =, <>, <=, >=, <, or >. + */ + private static class Parser { + private final List tokens; + private int pos = 0; + + public Parser(List tokens) { + this.tokens = tokens; + } + + /** + * Returns true if there are more tokens to process. + */ + public boolean hasNext() { + return pos < tokens.size(); + } + + /** + * Returns the next token without advancing. + */ + public String peek() { + return tokens.get(pos); + } + + /** + * Returns the next token and advances the position. + */ + public String next() { + return tokens.get(pos++); + } + + /** + * Parses an Expression: + * Expression -> Term { OR Term } + */ + public boolean parseExpression() { + boolean value = parseTerm(); + while (hasNext() && peek().equalsIgnoreCase("OR")) { + next(); // consume "OR" + boolean termValue = parseTerm(); + value = value || termValue; + } + return value; + } + + /** + * Parses a Term: + * Term -> Factor { AND Factor } + */ + public boolean parseTerm() { + boolean value = parseFactor(); + while (hasNext() && peek().equalsIgnoreCase("AND")) { + next(); // consume "AND" + boolean factorValue = parseFactor(); + value = value && factorValue; + } + return value; + } + + /** + * Parses a Factor, which is a single condition in the form: + * operand operator operand + * + * For example: "1234 >= 1000" + * + * @return the boolean result of the condition. + */ + public boolean parseFactor() { + // There must be at least three tokens remaining. + if (pos + 2 >= tokens.size()) { + throw new RuntimeException("Incomplete condition"); + } + + String leftOperand = next(); + String operator = next(); + String rightOperand = next(); + + // Validate operator. + if (!operator.equals("=") && !operator.equals("<>") && !operator.equals("<=") && !operator.equals(">=") + && !operator.equals("<") && !operator.equals(">")) { + throw new RuntimeException("Invalid operator: " + operator); + } + + double leftVal, rightVal; + try { + leftVal = Double.parseDouble(leftOperand); + rightVal = Double.parseDouble(rightOperand); + } catch (NumberFormatException e) { + // If either operand is not numeric, return false. + return false; + } + // Evaluate the condition. + switch (operator) { + case "=": + return Double.compare(leftVal, rightVal) == 0; + case "<>": + return Double.compare(leftVal, rightVal) != 0; + case "<=": + return leftVal <= rightVal; + case ">=": + return leftVal >= rightVal; + case "<": + return leftVal < rightVal; + case ">": + return leftVal > rightVal; + default: + // This case is never reached. + return false; + } + } + } +} \ No newline at end of file diff --git a/src/main/java/world/bentobox/challenges/database/object/requirements/OtherRequirements.java b/src/main/java/world/bentobox/challenges/database/object/requirements/OtherRequirements.java index 5f59e7d5..318b5375 100644 --- a/src/main/java/world/bentobox/challenges/database/object/requirements/OtherRequirements.java +++ b/src/main/java/world/bentobox/challenges/database/object/requirements/OtherRequirements.java @@ -139,6 +139,20 @@ public void setRequiredIslandLevel(long requiredIslandLevel) { this.requiredIslandLevel = requiredIslandLevel; } + + /** + * @return the papiString + */ + public String getPapiString() { + return papiString == null ? "" : papiString; + } + + /** + * @param papiString the papiString to set + */ + public void setPapiString(String papiString) { + this.papiString = papiString; + } // --------------------------------------------------------------------- @@ -162,6 +176,7 @@ public Requirements copy() clone.setRequiredMoney(this.requiredMoney); clone.setTakeMoney(this.takeMoney); clone.setRequiredIslandLevel(this.requiredIslandLevel); + clone.setPapiString(this.papiString); return clone; } @@ -201,4 +216,12 @@ public Requirements copy() */ @Expose private long requiredIslandLevel; + + /** + * Formulas that include math symbols and PAPI placeholders + */ + @Expose + private String papiString; + + } diff --git a/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java index c9264dcc..0ea6e836 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java @@ -215,6 +215,7 @@ private void buildOtherRequirementsPanel(PanelBuilder panelBuilder) { panelBuilder.item(12, this.createRequirementButton(RequirementButton.REQUIRED_MONEY)); panelBuilder.item(21, this.createRequirementButton(RequirementButton.REMOVE_MONEY)); + panelBuilder.item(14, this.createRequirementButton(RequirementButton.REQUIRED_PAPI)); panelBuilder.item(23, this.createRequirementButton(RequirementButton.REQUIRED_LEVEL)); panelBuilder.item(25, this.createRequirementButton(RequirementButton.REQUIRED_PERMISSIONS)); @@ -630,7 +631,7 @@ private PanelItem createRequirementButton(RequirementButton button) { return this.createInventoryRequirementButton(button); } // Buttons for Other Requirements - case REQUIRED_EXPERIENCE, REMOVE_EXPERIENCE, REQUIRED_LEVEL, REQUIRED_MONEY, REMOVE_MONEY -> { + case REQUIRED_EXPERIENCE, REMOVE_EXPERIENCE, REQUIRED_LEVEL, REQUIRED_MONEY, REMOVE_MONEY, REQUIRED_PAPI -> { return this.createOtherRequirementButton(button); } // Statistics @@ -1098,6 +1099,33 @@ private PanelItem createOtherRequirementButton(RequirementButton button) { description.add(""); description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); } + case REQUIRED_PAPI -> { + if (!requirements.getPapiString().isEmpty()) { + description + .add(this.user.getTranslation(reference + "value", "[formula]", requirements.getPapiString())); + } + icon = new ItemStack( + this.addon.getPlugin().getHooks().getHook("PlaceholderAPI").isPresent() ? Material.PAPER + : Material.BARRIER); + clickHandler = (panel, user, clickType, i) -> { + Consumer stringConsumer = string -> { + if (string != null) { + requirements.setPapiString(string); + } + + // reopen panel + this.build(); + }; + ConversationUtils.createStringInput(stringConsumer, user, + this.user.getTranslation(Constants.CONVERSATIONS + "enter-formula"), ""); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } case REQUIRED_MONEY -> { description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, String.valueOf(requirements.getRequiredMoney()))); @@ -1701,7 +1729,7 @@ public enum RequirementButton { REQUIRED_LEVEL, REQUIRED_MONEY, REMOVE_MONEY, STATISTIC, STATISTIC_BLOCKS, STATISTIC_ITEMS, STATISTIC_ENTITIES, STATISTIC_AMOUNT, REMOVE_STATISTIC, REQUIRED_MATERIALTAGS, REQUIRED_ENTITYTAGS, REQUIRED_STATISTICS, - REMOVE_STATISTICS, + REMOVE_STATISTICS, REQUIRED_PAPI, } // --------------------------------------------------------------------- diff --git a/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java b/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java index 1f700425..fb056756 100644 --- a/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java +++ b/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java @@ -37,6 +37,7 @@ import com.google.common.collect.UnmodifiableIterator; +import world.bentobox.bentobox.BentoBox; import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.objects.Island; @@ -45,6 +46,7 @@ import world.bentobox.challenges.database.object.Challenge; import world.bentobox.challenges.database.object.Challenge.ChallengeType; import world.bentobox.challenges.database.object.ChallengeLevel; +import world.bentobox.challenges.database.object.requirements.CheckPapi; import world.bentobox.challenges.database.object.requirements.InventoryRequirements; import world.bentobox.challenges.database.object.requirements.IslandRequirements; import world.bentobox.challenges.database.object.requirements.OtherRequirements; @@ -530,91 +532,79 @@ private void fullFillRequirements(ChallengeResult result) } } - // If challenges are in sync with all island members, then punish others too. - if (this.addon.getChallengesSettings().isStoreAsIslandData()) - { - Island island = this.addon.getIslands().getIsland(this.world, this.user); - - if (island == null) { - // hmm - return; - } - - for (UnmodifiableIterator iterator = island.getMemberSet().iterator(); iterator.hasNext() - && removeAmount > 0;) + // If challenges are in sync with all island members, then punish others too. + if (this.addon.getChallengesSettings().isStoreAsIslandData()) { - Player player = Bukkit.getPlayer(iterator.next()); + Island island = this.addon.getIslands().getIsland(this.world, this.user); - if (player == null || player == this.user.getPlayer()) { - // cannot punish null or player who already was punished. - continue; + if (island == null) { + // hmm + return; } - switch (Objects.requireNonNull(s.statistic()).getType()) { - case UNTYPED -> { - int statistic = player.getStatistic(s.statistic()); + for (UnmodifiableIterator iterator = island.getMemberSet().iterator(); iterator.hasNext() + && removeAmount > 0;) { + Player player = Bukkit.getPlayer(iterator.next()); - if (removeAmount >= statistic) - { - removeAmount -= statistic; - player.setStatistic(s.statistic(), 0); - } - else - { - player.setStatistic(s.statistic(), statistic - removeAmount); - removeAmount = 0; + if (player == null || player == this.user.getPlayer()) { + // cannot punish null or player who already was punished. + continue; } - } - case ITEM, BLOCK -> { - if (s.material() == null) - { - // Just a sanity check. Entity cannot be null at this point of code. - removeAmount = 0; - } - else - { - int statistic = player.getStatistic(s.statistic(), s.material()); + + switch (Objects.requireNonNull(s.statistic()).getType()) { + case UNTYPED -> { + int statistic = player.getStatistic(s.statistic()); if (removeAmount >= statistic) { removeAmount -= statistic; - player.setStatistic(s.statistic(), s.material(), 0); + player.setStatistic(s.statistic(), 0); } else { - player.setStatistic(s.statistic(), s.material(), - statistic - removeAmount); + player.setStatistic(s.statistic(), statistic - removeAmount); removeAmount = 0; } } - } - case ENTITY -> { - if (s.entity() == null) - { - // Just a sanity check. Entity cannot be null at this point of code. - removeAmount = 0; + case ITEM, BLOCK -> { + if (s.material() == null) { + // Just a sanity check. Entity cannot be null at this point of code. + removeAmount = 0; + } else { + int statistic = player.getStatistic(s.statistic(), s.material()); + + if (removeAmount >= statistic) { + removeAmount -= statistic; + player.setStatistic(s.statistic(), s.material(), 0); + } else { + player.setStatistic(s.statistic(), s.material(), statistic - removeAmount); + removeAmount = 0; + } + } } - else - { - int statistic = player.getStatistic(s.statistic(), s.entity()); - - if (removeAmount >= statistic) + case ENTITY -> { + if (s.entity() == null) { - removeAmount -= statistic; - player.setStatistic(s.statistic(), s.entity(), 0); + // Just a sanity check. Entity cannot be null at this point of code. + removeAmount = 0; } else { - player.setStatistic(s.statistic(), s.entity(), - statistic - removeAmount); - removeAmount = 0; + int statistic = player.getStatistic(s.statistic(), s.entity()); + + if (removeAmount >= statistic) { + removeAmount -= statistic; + player.setStatistic(s.statistic(), s.entity(), 0); + } else { + player.setStatistic(s.statistic(), s.entity(), statistic - removeAmount); + removeAmount = 0; + } } } - } + } } } } - } } } } @@ -1426,6 +1416,15 @@ private ChallengeResult checkOthers(int factor) { Utils.sendMessage(this.user, this.world, Constants.ERRORS + "island-level", TextVariables.NUMBER, String.valueOf(requirements.getRequiredIslandLevel())); + } else if (this.addon.getPlugin().getHooks().getHook("PlaceholderAPI").isPresent() + && !requirements.getPapiString().isEmpty() + && !CheckPapi.evaluate(user.getPlayer(), requirements.getPapiString())) { + Utils.sendMessage(this.user, this.world, Constants.ERRORS + "incorrect"); + if (!requirements.getPapiString().isEmpty()) { + addon.log("FYI:.Challenge failed for " + user.getName() + ". PAPI formula: " + + requirements.getPapiString() + " = " + + CheckPapi.evaluate(user.getPlayer(), requirements.getPapiString())); + } } else { @@ -1451,7 +1450,6 @@ private ChallengeResult checkOthers(int factor) { // Section: Statistic Challenge // --------------------------------------------------------------------- - /** * Checks if a statistic challenge can be completed or not * It returns ChallengeResult. diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 7d883772..88eb994b 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -415,6 +415,13 @@ challenges: &7 money on the player's &7 account for the challenge. value: "&7 Current value: &e [number]" + required_papi: + name: "&f&l Required PAPI" + description: |- + &7 Checks a formula that can + &7 include PAPI placeholders + &7 and math and logical elements. + value: "&7 Formula: &e [formula]" statistic: name: "&f&l Statistic" description: |- @@ -1130,6 +1137,7 @@ challenges: file-name-exist: "&c A file named '[id]' already exists. Cannot overwrite." write-search: "&e Please enter a search value. (Type 'cancel' to exit)" search-updated: "&a Search value updated." + enter-formula: "&a Enter a formula that uses PAPI number placeholders and symbols =,<>,<+,>=, AND, OR only.\n&a Example: &7 %aoneblock_my_island_lifetime_count% >= 1000 AND %Level_aoneblock_island_level% >= 100" titles: challenge-title: "Success!" challenge-subtitle: "[friendlyName]" From 7fad1b0943c0d8c06a2e7c1f8b7ca7945ee88d81 Mon Sep 17 00:00:00 2001 From: tastybento Date: Thu, 13 Feb 2025 21:41:53 +0900 Subject: [PATCH 29/34] Adds Advancements as an option to the Other challenges Enables giving rewards for advancements or combining them into a challenge. --- .../object/adapters/AdvancementsAdapter.java | 46 +++ .../adapters/AdvancementsListAdapter.java | 39 +++ .../requirements/OtherRequirements.java | 47 ++- .../panel/admin/EditChallengePanel.java | 27 +- .../panel/admin/ManageAdvancementsPanel.java | 275 ++++++++++++++++++ .../panel/admin/ManageStatisticsPanel.java | 1 - .../panel/util/SingleAdvancementSelector.java | 213 ++++++++++++++ .../challenges/tasks/TryToComplete.java | 9 + src/main/resources/locales/en-US.yml | 31 ++ 9 files changed, 679 insertions(+), 9 deletions(-) create mode 100644 src/main/java/world/bentobox/challenges/database/object/adapters/AdvancementsAdapter.java create mode 100644 src/main/java/world/bentobox/challenges/database/object/adapters/AdvancementsListAdapter.java create mode 100644 src/main/java/world/bentobox/challenges/panel/admin/ManageAdvancementsPanel.java create mode 100644 src/main/java/world/bentobox/challenges/panel/util/SingleAdvancementSelector.java diff --git a/src/main/java/world/bentobox/challenges/database/object/adapters/AdvancementsAdapter.java b/src/main/java/world/bentobox/challenges/database/object/adapters/AdvancementsAdapter.java new file mode 100644 index 00000000..91a12001 --- /dev/null +++ b/src/main/java/world/bentobox/challenges/database/object/adapters/AdvancementsAdapter.java @@ -0,0 +1,46 @@ +package world.bentobox.challenges.database.object.adapters; + + +import java.lang.reflect.Type; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.StreamSupport; + +import org.bukkit.Bukkit; +import org.bukkit.advancement.Advancement; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; + + +public class AdvancementsAdapter implements JsonSerializer, JsonDeserializer +{ + @Override + public JsonElement serialize(Advancement src, Type typeOfSrc, JsonSerializationContext context) + { + JsonObject result = new JsonObject(); + result.add("name", new JsonPrimitive(src.getKey().getKey())); + return result; + } + + @Override + public Advancement deserialize(JsonElement json, + Type typeOfT, + JsonDeserializationContext context) + throws JsonParseException + { + JsonObject jsonObject = json.getAsJsonObject(); + String name = jsonObject.get("name").getAsString(); + + return StreamSupport + .stream(Spliterators.spliteratorUnknownSize(Bukkit.advancementIterator(), Spliterator.ORDERED), false) + .filter(a -> a.getKey().getKey().equals(name)).findFirst().orElse(null); + } + +} \ No newline at end of file diff --git a/src/main/java/world/bentobox/challenges/database/object/adapters/AdvancementsListAdapter.java b/src/main/java/world/bentobox/challenges/database/object/adapters/AdvancementsListAdapter.java new file mode 100644 index 00000000..4dfe2952 --- /dev/null +++ b/src/main/java/world/bentobox/challenges/database/object/adapters/AdvancementsListAdapter.java @@ -0,0 +1,39 @@ +package world.bentobox.challenges.database.object.adapters; + +import com.google.gson.*; +import org.bukkit.advancement.Advancement; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +public class AdvancementsListAdapter implements JsonSerializer>, JsonDeserializer> { + + // Reuse your existing adapter for individual advancements + private final AdvancementsAdapter advancementAdapter = new AdvancementsAdapter(); + + @Override + public JsonElement serialize(List src, Type typeOfSrc, JsonSerializationContext context) { + JsonArray array = new JsonArray(); + for (Advancement advancement : src) { + // Serialize each advancement using existing adapter + JsonElement element = advancementAdapter.serialize(advancement, advancement.getClass(), context); + array.add(element); + } + return array; + } + + @Override + public List deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + List advancements = new ArrayList<>(); + JsonArray array = json.getAsJsonArray(); + for (JsonElement element : array) { + Advancement advancement = advancementAdapter.deserialize(element, Advancement.class, context); + if (advancement != null) { + advancements.add(advancement); + } + } + return advancements; + } +} \ No newline at end of file diff --git a/src/main/java/world/bentobox/challenges/database/object/requirements/OtherRequirements.java b/src/main/java/world/bentobox/challenges/database/object/requirements/OtherRequirements.java index 318b5375..3edb37b5 100644 --- a/src/main/java/world/bentobox/challenges/database/object/requirements/OtherRequirements.java +++ b/src/main/java/world/bentobox/challenges/database/object/requirements/OtherRequirements.java @@ -1,16 +1,23 @@ // // Created by BONNe // Copyright - 2019 +// Enhanced by tastybento // package world.bentobox.challenges.database.object.requirements; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; + +import org.bukkit.advancement.Advancement; import com.google.gson.annotations.Expose; +import com.google.gson.annotations.JsonAdapter; +import world.bentobox.challenges.database.object.adapters.AdvancementsListAdapter; /** * This class contains all necessary requirements to complete other type challenge. @@ -144,7 +151,10 @@ public void setRequiredIslandLevel(long requiredIslandLevel) * @return the papiString */ public String getPapiString() { - return papiString == null ? "" : papiString; + if (papiString == null) { + papiString = ""; + } + return papiString; } /** @@ -154,17 +164,35 @@ public void setPapiString(String papiString) { this.papiString = papiString; } + /** + * @return the advancements + */ + public List getAdvancements() { + if (advancements == null) { + advancements = new ArrayList<>(); + } + return advancements; + } + + /** + * @param advancements the advancements to set + */ + public void setAdvancements(List advancements) { + + this.advancements = advancements; + //advancements.stream().map(adv -> adv.getDisplay().getTitle()).collect(Collectors.toList()); + } // --------------------------------------------------------------------- // Section: Other methods // --------------------------------------------------------------------- - /** - * Method Requirements#copy allows copies Requirements object, to avoid changing content when it is necessary - * to use it. - * @return OtherRequirements copy - */ +/** + * Method Requirements#copy allows copies Requirements object, to avoid changing content when it is necessary + * to use it. + * @return OtherRequirements copy + */ @Override public Requirements copy() { @@ -177,6 +205,7 @@ public Requirements copy() clone.setTakeMoney(this.takeMoney); clone.setRequiredIslandLevel(this.requiredIslandLevel); clone.setPapiString(this.papiString); + clone.setAdvancements(this.advancements); return clone; } @@ -223,5 +252,11 @@ public Requirements copy() @Expose private String papiString; + /** + * List of advancements + */ + @Expose + @JsonAdapter(AdvancementsListAdapter.class) + private List advancements; } diff --git a/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java index 0ea6e836..e843223d 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java @@ -8,11 +8,13 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.stream.Collectors; import org.bukkit.Material; import org.bukkit.World; +import org.bukkit.advancement.Advancement; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.inventory.ItemStack; @@ -35,6 +37,8 @@ import world.bentobox.challenges.panel.util.EnvironmentSelector; import world.bentobox.challenges.panel.util.ItemSelector; import world.bentobox.challenges.panel.util.MultiBlockSelector; +import world.bentobox.challenges.panel.util.SingleAdvancementSelector; +import world.bentobox.challenges.panel.util.SingleAdvancementSelector.Mode; import world.bentobox.challenges.utils.Constants; import world.bentobox.challenges.utils.Utils; @@ -218,6 +222,8 @@ private void buildOtherRequirementsPanel(PanelBuilder panelBuilder) { panelBuilder.item(14, this.createRequirementButton(RequirementButton.REQUIRED_PAPI)); panelBuilder.item(23, this.createRequirementButton(RequirementButton.REQUIRED_LEVEL)); + panelBuilder.item(16, this.createRequirementButton(RequirementButton.REQUIRED_ADVANCEMENTS)); + panelBuilder.item(25, this.createRequirementButton(RequirementButton.REQUIRED_PERMISSIONS)); } @@ -631,7 +637,8 @@ private PanelItem createRequirementButton(RequirementButton button) { return this.createInventoryRequirementButton(button); } // Buttons for Other Requirements - case REQUIRED_EXPERIENCE, REMOVE_EXPERIENCE, REQUIRED_LEVEL, REQUIRED_MONEY, REMOVE_MONEY, REQUIRED_PAPI -> { + case REQUIRED_EXPERIENCE, REMOVE_EXPERIENCE, REQUIRED_LEVEL, REQUIRED_MONEY, REMOVE_MONEY, REQUIRED_PAPI, + REQUIRED_ADVANCEMENTS -> { return this.createOtherRequirementButton(button); } // Statistics @@ -1126,6 +1133,22 @@ private PanelItem createOtherRequirementButton(RequirementButton button) { description.add(""); description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); } + case REQUIRED_ADVANCEMENTS -> { + requirements.getAdvancements().forEach(adv -> description + .add(this.user.getTranslation(reference + "list", "[name]", adv.getDisplay().getTitle()))); + + icon = new ItemStack(Material.CYAN_BANNER); + clickHandler = (panel, user, clickType, i) -> { + // Deal with adding and removing statistics in the ManageAdvancementsPanel class + ManageAdvancementsPanel.open(this, requirements.getAdvancements()); + + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-change")); + } case REQUIRED_MONEY -> { description.add(this.user.getTranslation(reference + "value", Constants.PARAMETER_NUMBER, String.valueOf(requirements.getRequiredMoney()))); @@ -1729,7 +1752,7 @@ public enum RequirementButton { REQUIRED_LEVEL, REQUIRED_MONEY, REMOVE_MONEY, STATISTIC, STATISTIC_BLOCKS, STATISTIC_ITEMS, STATISTIC_ENTITIES, STATISTIC_AMOUNT, REMOVE_STATISTIC, REQUIRED_MATERIALTAGS, REQUIRED_ENTITYTAGS, REQUIRED_STATISTICS, - REMOVE_STATISTICS, REQUIRED_PAPI, + REMOVE_STATISTICS, REQUIRED_PAPI, REQUIRED_ADVANCEMENTS, } // --------------------------------------------------------------------- diff --git a/src/main/java/world/bentobox/challenges/panel/admin/ManageAdvancementsPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/ManageAdvancementsPanel.java new file mode 100644 index 00000000..c4715245 --- /dev/null +++ b/src/main/java/world/bentobox/challenges/panel/admin/ManageAdvancementsPanel.java @@ -0,0 +1,275 @@ +package world.bentobox.challenges.panel.admin; + + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.stream.Collectors; + +import org.bukkit.Material; +import org.bukkit.advancement.Advancement; +import org.bukkit.inventory.ItemStack; + +import lv.id.bonne.panelutils.PanelUtils; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.builders.PanelBuilder; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.challenges.panel.CommonPagedPanel; +import world.bentobox.challenges.panel.CommonPanel; +import world.bentobox.challenges.panel.util.SingleAdvancementSelector; +import world.bentobox.challenges.utils.Constants; + + +/** + * This class allows to edit material that are in required material map. + */ +public class ManageAdvancementsPanel extends CommonPagedPanel +{ + + // --------------------------------------------------------------------- + // Section: Enums + // --------------------------------------------------------------------- + + /** + * Functional buttons in current GUI. + */ + private enum Button { + ADD_ADVANCEMENT, REMOVE_ADVANCEMENT + } + + // --------------------------------------------------------------------- + // Section: Variables + // --------------------------------------------------------------------- + + /** + * Contains selected advancements. + */ + private final Set selectedAdvancements; + + /** + * List of required advancements + */ + private final List advancementsList; + + /** + * Stores filtered items. + */ + private List filterElements; + + private ManageAdvancementsPanel(CommonPanel parentGUI, List advancementsList) + { + super(parentGUI); + this.advancementsList = advancementsList; + + // Sort tags by their ordinal value. + this.advancementsList.sort(Comparator.comparing(advancement -> advancement.getDisplay().getTitle())); + + this.selectedAdvancements = new HashSet<>(); + + // Init without filters applied. + this.filterElements = this.advancementsList; + } + + + /** + * Open the Challenges Admin GUI. + */ + public static void open(CommonPanel parentGUI, List advancementsList) { + new ManageAdvancementsPanel(parentGUI, advancementsList).build(); + } + + +// --------------------------------------------------------------------- +// Section: Methods +// --------------------------------------------------------------------- + + + /** + * This method is called when filter value is updated. + */ + @Override + protected void updateFilters() + { + if (this.searchString == null || this.searchString.isBlank()) + { + this.filterElements = this.advancementsList; + } + else + { + this.filterElements = this.advancementsList.stream(). + filter(element -> { + // If element name is set and name contains search field, then do not filter out. + return element.getDisplay().getTitle().toLowerCase(Locale.ENGLISH) + .contains(this.searchString.toLowerCase(Locale.ENGLISH)); + }). + distinct(). + collect(Collectors.toList()); + } + } + + + /** + * This method builds all necessary elements in GUI panel. + */ + @Override + protected void build() + { + PanelBuilder panelBuilder = new PanelBuilder().user(this.user). + name(this.user.getTranslation(Constants.TITLE + "manage-advancements")); + + // Create nice border. + PanelUtils.fillBorder(panelBuilder); + + panelBuilder.item(3, this.createButton(Button.ADD_ADVANCEMENT)); + panelBuilder.item(5, this.createButton(Button.REMOVE_ADVANCEMENT)); + // Fill the box with what is selected + this.populateElements(panelBuilder, this.filterElements); + + // Add return button. + panelBuilder.item(44, this.returnButton); + + panelBuilder.build(); + } + + + /** + * This method creates PanelItem button of requested type. + * @param button Button which must be created. + * @return new PanelItem with requested functionality. + */ + private PanelItem createButton(Button button) + { + final String reference = Constants.BUTTON + button.name().toLowerCase() + "."; + + final String name = this.user.getTranslation(reference + "name"); + final List description = new ArrayList<>(3); + description.add(this.user.getTranslation(reference + "description")); + + ItemStack icon; + PanelItem.ClickHandler clickHandler; + boolean glow; + + switch (button) + { + case ADD_ADVANCEMENT -> { + icon = new ItemStack(Material.BUCKET); + clickHandler = (panel, user1, clickType, slot) -> + { + SingleAdvancementSelector.open(this.user, (status, advancement) -> + { + if (status) + { + this.advancementsList.add(advancement); + } + + this.build(); + }); + return true; + }; + glow = false; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-add")); + } + case REMOVE_ADVANCEMENT -> { + + if (!this.selectedAdvancements.isEmpty()) + { + this.selectedAdvancements.forEach(adv -> description.add(adv.getDisplay().getTitle())); + } + + icon = new ItemStack(Material.LAVA_BUCKET); + + clickHandler = (panel, user1, clickType, slot) -> + { + if (!this.selectedAdvancements.isEmpty()) + { + this.advancementsList.removeAll(this.selectedAdvancements); + this.selectedAdvancements.clear(); + this.build(); + } + + return true; + }; + + glow = !this.selectedAdvancements.isEmpty(); + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-remove")); + } + default -> { + icon = new ItemStack(Material.PAPER); + clickHandler = null; + glow = false; + } + } + + return new PanelItemBuilder(). + icon(icon). + name(name). + description(description). + clickHandler(clickHandler). + glow(glow). + build(); + } + + + /** + * This method creates button for given stat. + * @param rec material which button must be created. + * @return new Button for material. + */ + @Override + protected PanelItem createElementButton(Advancement rec) + { + final String reference = Constants.BUTTON + "advancement_element."; + + List description = new ArrayList<>(); + + // Show everything about this advancement + description + .add(this.user.getTranslation(reference + "description", "[description]", + rec.getDisplay().getDescription())); + + if (this.selectedAdvancements.contains(rec)) + { + description.add(this.user.getTranslation(reference + "selected")); + } + + description.add(""); + + if (this.selectedAdvancements.contains(rec)) + { + description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-deselect")); + } + else + { + description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-select")); + } + + return new PanelItemBuilder(). + name(this.user.getTranslation(reference + "name", "[name]", rec.getDisplay().getTitle())) + .icon(rec.getDisplay().getIcon()). + description(description). + clickHandler((panel, user1, clickType, slot) -> { + // On right click change which entities are selected for deletion. + if (clickType.isRightClick()) + { + if (!this.selectedAdvancements.add(rec)) + { + // Remove material if it is already selected + this.selectedAdvancements.remove(rec); + } + + this.build(); + } + return true; + }). + glow(this.selectedAdvancements.contains(rec)). + build(); + } + +} diff --git a/src/main/java/world/bentobox/challenges/panel/admin/ManageStatisticsPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/ManageStatisticsPanel.java index 2a8f68ab..7d2a40a4 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/ManageStatisticsPanel.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/ManageStatisticsPanel.java @@ -437,7 +437,6 @@ private Panel buildStatisticPanel(StatisticRec req) { break; } - panelBuilder.item(25, this.createStatisticRequirementButton(RequirementButton.REQUIRED_PERMISSIONS, req)); panelBuilder.item(44, this.returnButton); return panelBuilder.build(); } diff --git a/src/main/java/world/bentobox/challenges/panel/util/SingleAdvancementSelector.java b/src/main/java/world/bentobox/challenges/panel/util/SingleAdvancementSelector.java new file mode 100644 index 00000000..e8868034 --- /dev/null +++ b/src/main/java/world/bentobox/challenges/panel/util/SingleAdvancementSelector.java @@ -0,0 +1,213 @@ +package world.bentobox.challenges.panel.util; + + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.advancement.Advancement; +import org.bukkit.inventory.ItemStack; + +import lv.id.bonne.panelutils.PanelUtils; +import world.bentobox.bentobox.api.panels.PanelItem; +import world.bentobox.bentobox.api.panels.builders.PanelBuilder; +import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; +import world.bentobox.bentobox.api.user.User; +import world.bentobox.challenges.utils.Constants; + + +/** + * This GUI allows to select single entity and return it via Consumer. + */ +public class SingleAdvancementSelector extends PagedSelector +{ + /** + * Instantiates a new Single advancement selector. + * + * @param user the user + * @param mode the mode + * @param excluded the excluded + * @param consumer the consumer + */ + private SingleAdvancementSelector(User user, Mode mode, Set excluded, + BiConsumer consumer) + { + super(user); + this.elements = new ArrayList(); + this.consumer = consumer; + Bukkit.advancementIterator().forEachRemaining(elements::add); + elements.removeIf(a -> a.getDisplay() == null); // Remove any that don't get displayed + elements.sort(Comparator.comparing(advancement -> advancement.getDisplay().getTitle())); + // Init without filters applied. + this.filterElements = this.elements; + } + + + /** + * This method opens GUI that allows to select challenge type. + * + * @param user User who opens GUI. + * @param consumer Consumer that allows to get clicked type. + */ + public static void open(User user, Mode mode, Set excluded, BiConsumer consumer) + { + new SingleAdvancementSelector(user, mode, excluded, consumer).build(); + } + + + /** + * This method opens GUI that allows to select challenge type. + * + * @param user User who opens GUI. + * @param consumer Consumer that allows to get clicked type. + */ + public static void open(User user, BiConsumer consumer) + { + new SingleAdvancementSelector(user, Mode.ANY, new HashSet<>(), consumer).build(); + } + + + /** + * This method opens GUI that allows to select challenge type. + * + * @param user User who opens GUI. + * @param consumer Consumer that allows to get clicked type. + */ + public static void open(User user, Mode mode, BiConsumer consumer) + { + new SingleAdvancementSelector(user, mode, new HashSet<>(), consumer).build(); + } + + + // --------------------------------------------------------------------- + // Section: Methods + // --------------------------------------------------------------------- + + + /** + * This method builds + */ + @Override + protected void build() { + PanelBuilder panelBuilder = new PanelBuilder().user(this.user); + panelBuilder.name(this.user.getTranslation(Constants.TITLE + "advancement-selector")); + + PanelUtils.fillBorder(panelBuilder, Material.BLUE_STAINED_GLASS_PANE); + + this.populateElements(panelBuilder, this.filterElements); + + panelBuilder.item(4, this.createButton()); + + panelBuilder.build(); + } + + + /** + * This method is called when filter value is updated. + */ + @Override + protected void updateFilters() { + if (this.searchString == null || this.searchString.isBlank()) { + this.filterElements = this.elements; + } else { + this.filterElements = this.elements.stream().filter(element -> { + // If element name is set and name contains search field, then do not filter out. + return element.getDisplay().getTitle().toLowerCase().contains(this.searchString.toLowerCase()); + }).distinct().collect(Collectors.toList()); + } + } + + + /** + * This method builds PanelItem for given entity. + * @param entity Entity which PanelItem must be created. + * @return new PanelItem for given Entity. + */ + @Override + protected PanelItem createElementButton(Advancement advancement) { + final String reference = Constants.BUTTON + "advancement."; + List description = new ArrayList<>(); + description.add(this.user.getTranslation(reference + "description", "[description]", + advancement.getDisplay().getDescription())); + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-choose")); + + + return new PanelItemBuilder() + .name(this.user.getTranslation(reference + "name", "[name]", + advancement.getDisplay().getTitle())) + .icon(getIcon(advancement)).description(description) + .clickHandler((panel, user1, clickType, slot) -> { + this.consumer.accept(true, advancement); + return true; + }).build(); + } + + /** + * Get an ItemStack icon for any entity type, or PAPER if it's not really known + * @param et entity type + * @return ItemStack + */ + public static ItemStack getIcon(Advancement advancement) { + return advancement.getDisplay().getIcon(); + } + + /** + * This method creates PanelItem button of requested type. + * @return new PanelItem with requested functionality. + */ + private PanelItem createButton() { + final String reference = Constants.BUTTON + "cancel."; + + final String name = this.user.getTranslation(reference + "name"); + final List description = new ArrayList<>(3); + description.add(this.user.getTranslation(reference + "description")); + + ItemStack icon = new ItemStack(Material.IRON_DOOR); + PanelItem.ClickHandler clickHandler = (panel, user1, clickType, slot) -> { + this.consumer.accept(false, null); + return true; + }; + + description.add(""); + description.add(this.user.getTranslation(Constants.TIPS + "click-to-cancel")); + + return new PanelItemBuilder().icon(icon).name(name).description(description).clickHandler(clickHandler).build(); + } + + + // --------------------------------------------------------------------- + // Section: Mode + // --------------------------------------------------------------------- + + + public enum Mode { + ANY + } + + + // --------------------------------------------------------------------- + // Section: Variables + // --------------------------------------------------------------------- + + /** + * List with elements that will be displayed in current GUI. + */ + private final List elements; + + /** + * This variable stores consumer. + */ + private final BiConsumer consumer; + + /** + * Stores filtered items. + */ + private List filterElements; +} diff --git a/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java b/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java index fb056756..8bde71c7 100644 --- a/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java +++ b/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java @@ -27,6 +27,7 @@ import org.bukkit.Material; import org.bukkit.Tag; import org.bukkit.World; +import org.bukkit.advancement.AdvancementProgress; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.entity.Entity; @@ -1425,6 +1426,14 @@ private ChallengeResult checkOthers(int factor) { + requirements.getPapiString() + " = " + CheckPapi.evaluate(user.getPlayer(), requirements.getPapiString())); } + } else if (!requirements.getAdvancements().stream().map(user.getPlayer()::getAdvancementProgress) + .allMatch(AdvancementProgress::isDone)) { + Utils.sendMessage(this.user, this.world, Constants.ERRORS + "incorrect"); + user.sendMessage("challenges.gui.buttons.required_advancements.title"); + requirements.getAdvancements().stream().filter(ad -> !user.getPlayer().getAdvancementProgress(ad).isDone()) + .forEach(ad -> Utils.sendMessage(this.user, this.world, + "challenges.gui.buttons.advancement_element.name", "[name]", + ad.getDisplay().getTitle())); } else { diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 88eb994b..a576a4c4 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -64,6 +64,8 @@ challenges: manage-entity-groups: "&0&l Manage Entity Groups" manage-entities: "&0&l Manage Entities" manage-statistics: "&0&l Manage Statistics" + manage-advancements: "&0&l Manage Advancements" + advancement-selector: "&0&l Advancement Selector" type-selector: "&0&l Challenge Type Selector" item-selector: "&0&l Item Selector" block-selector: "&0&l Block Selector" @@ -322,6 +324,15 @@ challenges: title: "&7 Statistics: " list: " &8 - [name]" none: "&7 No statistics have been added." + required_advancements: + name: "&f&l Required Advancements" + description: |- + &7 Allows you to change the required + &7 advancements for this challenge to + &7 be completed. + title: "&7 Advancements: " + list: " &8 - [name]" + none: "&7 No advancements have been added." search_radius: name: "&f&l Search Radius" description: |- @@ -449,6 +460,22 @@ challenges: &7 from the list. enabled: "&2 Enabled" disabled: "&c Disabled" + advancement: + name: "&f&l [name]" + description: "[description]" + add_advancement: + name: "&f&l Add Advancement" + description: |- + &7 Allows you to add a new + &7 advancement to the list. + remove_advancement: + name: "&f&l Remove Advancement" + description: |- + &7 Allows you to remove + &7 selected advancements + &7 from the list. + enabled: "&2 Enabled" + disabled: "&c Disabled" statistic_blocks: name: "&f&l Target Block" description: |- @@ -833,6 +860,10 @@ challenges: &7 and reopens the previous GUI. title: "&7 Selected: " element: "&8 - [element]" + advancement_element: + name: "&f&l [name]" + description: "[description]" + selected: "&2 Selected" statistic_element: name: "&f&l [statistic]" amount: "&7 Target Value: &e [number]" From f166df2b69c1f75d7354cac2926e9b00c6786c49 Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 14 Feb 2025 16:24:08 +0900 Subject: [PATCH 30/34] Add string comparison for PAPI placeholders #366 --- .../object/requirements/CheckPapi.java | 255 ++++++++++++------ 1 file changed, 175 insertions(+), 80 deletions(-) diff --git a/src/main/java/world/bentobox/challenges/database/object/requirements/CheckPapi.java b/src/main/java/world/bentobox/challenges/database/object/requirements/CheckPapi.java index 5b6b738f..60f56d81 100644 --- a/src/main/java/world/bentobox/challenges/database/object/requirements/CheckPapi.java +++ b/src/main/java/world/bentobox/challenges/database/object/requirements/CheckPapi.java @@ -11,24 +11,51 @@ public class CheckPapi { /** - * Evaluates the formula by first replacing PAPI placeholders (using the provided Player) - * and then evaluating the resulting expression. The expression is expected to be a series - * of numeric comparisons (using =, <>, <=, >=, <, >) joined by Boolean operators AND and OR. - * - * For example: - * "%aoneblock_my_island_lifetime_count% >= 1000 AND %Level_aoneblock_island_level% >= 100" - * - * If any placeholder evaluates to a non-numeric value or the formula is malformed, false is returned. + * Evaluates the given formula by first replacing PAPI placeholders using the provided Player, + * then parsing and evaluating one or more conditions. + *

+ * The formula may contain conditions comparing numeric or string values. + * Operands may contain spaces. The grammar for a condition is: + *

+     *     leftOperand operator rightOperand
+     * 
+ * where the leftOperand is a sequence of tokens (separated by whitespace) until a valid + * operator is found, and the rightOperand is a sequence of tokens until a boolean operator + * ("AND" or "OR") is encountered or the end of the formula is reached. + *

+ * Supported comparison operators (case sensitive) are: + *

    + *
  • "=" or "==" for equality
  • + *
  • "<>" or "!=" for inequality
  • + *
  • "<=" and ">=" for less than or equal and greater than or equal
  • + *
  • "<" and ">" for less than and greater than
  • + *
+ * + * For strings: + *
    + *
  • "=" for case insensitive equality
  • + *
  • "==" for case-sensitive equality
  • + *
  • "<>" for case-insensitive inequality
  • + *
  • "!=" for case sensitive inequality
  • + *
+ * Boolean connectors "AND" and "OR" (case insensitive) combine multiple conditions; + * AND has higher precedence than OR. + *

+ * Examples: + *

+     *     "%aoneblock_my_island_lifetime_count% >= 1000 AND %aoneblock_my_island_level% >= 100"
+     *     "john smith == tasty bento AND 40 > 20"
+     * 
* - * @param player the Player used for placeholder replacement. - * @param formula the formula to evaluate. + * @param player the Player used for placeholder replacement + * @param formula the formula to evaluate * @return true if the formula evaluates to true, false otherwise. */ public static boolean evaluate(Player player, String formula) { - // Replace PAPI placeholders with actual values using the provided Player. + // Replace PAPI placeholders with actual values. String parsedFormula = PlaceholderAPI.setPlaceholders(player, formula); - // Tokenize the parsed formula (tokens are assumed to be separated by whitespace). + // Tokenize the resulting formula by whitespace. List tokens = tokenize(parsedFormula); if (tokens.isEmpty()) { return false; @@ -37,19 +64,19 @@ public static boolean evaluate(Player player, String formula) { try { Parser parser = new Parser(tokens); boolean result = parser.parseExpression(); - // If there are leftover tokens, the expression is malformed. + // If there are extra tokens after parsing the full expression, the formula is malformed. if (parser.hasNext()) { return false; } return result; } catch (Exception e) { - // Any error in parsing or evaluation results in false. + // Any error in parsing or evaluating the expression results in false. return false; } } /** - * Splits the given string into tokens using whitespace as the delimiter. + * Splits a string into tokens using whitespace as the delimiter. * * @param s the string to tokenize. * @return a list of tokens. @@ -59,17 +86,8 @@ private static List tokenize(String s) { } /** - * A simple recursive descent parser that evaluates expressions according to the following grammar: - * - *
-     * Expression -> Term { OR Term }
-     * Term       -> Factor { AND Factor }
-     * Factor     -> operand operator operand
-     * 
- * - * A Factor is expected to be a numeric condition in the form: - * number operator number - * where operator is one of: =, <>, <=, >=, <, or >. + * A simple recursive descent parser that evaluates the formula. + * It supports multi-token operands for conditions. */ private static class Parser { private final List tokens; @@ -79,34 +97,27 @@ public Parser(List tokens) { this.tokens = tokens; } - /** - * Returns true if there are more tokens to process. - */ public boolean hasNext() { return pos < tokens.size(); } - /** - * Returns the next token without advancing. - */ public String peek() { return tokens.get(pos); } - /** - * Returns the next token and advances the position. - */ public String next() { return tokens.get(pos++); } /** * Parses an Expression: - * Expression -> Term { OR Term } + * Expression -> Term { OR Term } + * + * @return the boolean value of the expression. */ public boolean parseExpression() { boolean value = parseTerm(); - while (hasNext() && peek().equalsIgnoreCase("OR")) { + while (hasNext() && isOr(peek())) { next(); // consume "OR" boolean termValue = parseTerm(); value = value || termValue; @@ -116,67 +127,151 @@ public boolean parseExpression() { /** * Parses a Term: - * Term -> Factor { AND Factor } + * Term -> Condition { AND Condition } + * + * @return the boolean value of the term. */ public boolean parseTerm() { - boolean value = parseFactor(); - while (hasNext() && peek().equalsIgnoreCase("AND")) { + boolean value = parseCondition(); + while (hasNext() && isAnd(peek())) { next(); // consume "AND" - boolean factorValue = parseFactor(); - value = value && factorValue; + boolean conditionValue = parseCondition(); + value = value && conditionValue; } return value; } /** - * Parses a Factor, which is a single condition in the form: - * operand operator operand - * - * For example: "1234 >= 1000" + * Parses a single condition of the form: + * leftOperand operator rightOperand + *

+ * The left operand is built by collecting tokens until a valid operator is found. + * The right operand is built by collecting tokens until a boolean operator ("AND" or "OR") + * is encountered or the end of the token list is reached. * * @return the boolean result of the condition. */ - public boolean parseFactor() { - // There must be at least three tokens remaining. - if (pos + 2 >= tokens.size()) { - throw new RuntimeException("Incomplete condition"); + public boolean parseCondition() { + // Parse left operand. + StringBuilder leftSB = new StringBuilder(); + if (!hasNext()) { + throw new RuntimeException("Expected left operand but reached end of expression"); } - - String leftOperand = next(); + // Collect tokens for the left operand until an operator is encountered. + while (hasNext() && !isOperator(peek())) { + if (leftSB.length() > 0) { + leftSB.append(" "); + } + leftSB.append(next()); + } + if (!hasNext()) { + throw new RuntimeException("Operator expected after left operand"); + } + // Next token should be an operator. String operator = next(); - String rightOperand = next(); - - // Validate operator. - if (!operator.equals("=") && !operator.equals("<>") && !operator.equals("<=") && !operator.equals(">=") - && !operator.equals("<") && !operator.equals(">")) { + if (!isValidOperator(operator)) { throw new RuntimeException("Invalid operator: " + operator); } + // Parse right operand. + StringBuilder rightSB = new StringBuilder(); + while (hasNext() && !isBooleanOperator(peek())) { + if (rightSB.length() > 0) { + rightSB.append(" "); + } + rightSB.append(next()); + } + String leftOperand = leftSB.toString().trim(); + String rightOperand = rightSB.toString().trim(); - double leftVal, rightVal; + // Evaluate the condition: + // If both operands can be parsed as numbers, use numeric comparison; + // otherwise, perform string comparison. + Double leftNum = tryParseDouble(leftOperand); + Double rightNum = tryParseDouble(rightOperand); + if (leftNum != null && rightNum != null) { + // Numeric comparison. + switch (operator) { + case "=": + case "==": + return Double.compare(leftNum, rightNum) == 0; + case "<>": + case "!=": + return Double.compare(leftNum, rightNum) != 0; + case "<=": + return leftNum <= rightNum; + case ">=": + return leftNum >= rightNum; + case "<": + return leftNum < rightNum; + case ">": + return leftNum > rightNum; + default: + throw new RuntimeException("Unsupported operator: " + operator); + } + } else { + // String comparison. + switch (operator) { + case "=": + return leftOperand.equalsIgnoreCase(rightOperand); + case "==": + return leftOperand.equals(rightOperand); + case "<>": + return !leftOperand.equalsIgnoreCase(rightOperand); + case "!=": + return !leftOperand.equals(rightOperand); + case "<=": + return leftOperand.compareTo(rightOperand) <= 0; + case ">=": + return leftOperand.compareTo(rightOperand) >= 0; + case "<": + return leftOperand.compareTo(rightOperand) < 0; + case ">": + return leftOperand.compareTo(rightOperand) > 0; + default: + throw new RuntimeException("Unsupported operator: " + operator); + } + } + } + + /** + * Checks if the given token is one of the valid comparison operators. + */ + private boolean isValidOperator(String token) { + return token.equals("=") || token.equals("==") || token.equals("<>") || token.equals("!=") + || token.equals("<=") || token.equals(">=") || token.equals("<") || token.equals(">"); + } + + /** + * Returns true if the token is a comparison operator. + */ + private boolean isOperator(String token) { + return isValidOperator(token); + } + + /** + * Returns true if the token is a boolean operator ("AND" or "OR"). + */ + private boolean isBooleanOperator(String token) { + return token.equalsIgnoreCase("AND") || token.equalsIgnoreCase("OR"); + } + + private boolean isAnd(String token) { + return token.equalsIgnoreCase("AND"); + } + + private boolean isOr(String token) { + return token.equalsIgnoreCase("OR"); + } + + /** + * Tries to parse the given string as a Double. + * Returns the Double if successful, or null if parsing fails. + */ + private Double tryParseDouble(String s) { try { - leftVal = Double.parseDouble(leftOperand); - rightVal = Double.parseDouble(rightOperand); + return Double.parseDouble(s); } catch (NumberFormatException e) { - // If either operand is not numeric, return false. - return false; - } - // Evaluate the condition. - switch (operator) { - case "=": - return Double.compare(leftVal, rightVal) == 0; - case "<>": - return Double.compare(leftVal, rightVal) != 0; - case "<=": - return leftVal <= rightVal; - case ">=": - return leftVal >= rightVal; - case "<": - return leftVal < rightVal; - case ">": - return leftVal > rightVal; - default: - // This case is never reached. - return false; + return null; } } } From 73e3df6ff7f18d2067bc7aa84196d3df991f31ca Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 14 Feb 2025 16:46:42 +0900 Subject: [PATCH 31/34] Added test class and fixed issue. --- .../object/requirements/CheckPapi.java | 4 + .../object/requirements/CheckPapiTest.java | 102 ++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 src/test/java/world/bentobox/challenges/database/object/requirements/CheckPapiTest.java diff --git a/src/main/java/world/bentobox/challenges/database/object/requirements/CheckPapi.java b/src/main/java/world/bentobox/challenges/database/object/requirements/CheckPapi.java index 60f56d81..76a45a39 100644 --- a/src/main/java/world/bentobox/challenges/database/object/requirements/CheckPapi.java +++ b/src/main/java/world/bentobox/challenges/database/object/requirements/CheckPapi.java @@ -183,6 +183,10 @@ public boolean parseCondition() { String leftOperand = leftSB.toString().trim(); String rightOperand = rightSB.toString().trim(); + if (rightOperand.isEmpty()) { + return false; + } + // Evaluate the condition: // If both operands can be parsed as numbers, use numeric comparison; // otherwise, perform string comparison. diff --git a/src/test/java/world/bentobox/challenges/database/object/requirements/CheckPapiTest.java b/src/test/java/world/bentobox/challenges/database/object/requirements/CheckPapiTest.java new file mode 100644 index 00000000..b0013913 --- /dev/null +++ b/src/test/java/world/bentobox/challenges/database/object/requirements/CheckPapiTest.java @@ -0,0 +1,102 @@ +package world.bentobox.challenges.database.object.requirements; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +import org.bukkit.entity.Player; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.stubbing.Answer; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import me.clip.placeholderapi.PlaceholderAPI; + +@RunWith(PowerMockRunner.class) +@PrepareForTest(PlaceholderAPI.class) +public class CheckPapiTest { + + @Mock + private Player player; + + @Before + public void setUp() { + PowerMockito.mockStatic(PlaceholderAPI.class, Mockito.RETURNS_MOCKS); + // Return back the input string + when(PlaceholderAPI.setPlaceholders(eq(player), anyString())) + .thenAnswer((Answer) invocation -> invocation.getArgument(1, String.class)); + } + + @Test + public void testNumericEquality() { + // Using numeric equality comparisons. + assertTrue(CheckPapi.evaluate(player, "40 == 40")); + assertFalse(CheckPapi.evaluate(player, "40 == 50")); + assertTrue(CheckPapi.evaluate(player, "100 = 100")); + assertFalse(CheckPapi.evaluate(player, "100 = 101")); + } + + @Test + public void testNumericComparison() { + assertTrue(CheckPapi.evaluate(player, "40 > 20")); + assertFalse(CheckPapi.evaluate(player, "20 > 40")); + assertTrue(CheckPapi.evaluate(player, "20 < 40")); + assertFalse(CheckPapi.evaluate(player, "40 < 20")); + assertTrue(CheckPapi.evaluate(player, "30 <= 30")); + assertFalse(CheckPapi.evaluate(player, "31 <= 30")); + assertTrue(CheckPapi.evaluate(player, "30 >= 30")); + assertFalse(CheckPapi.evaluate(player, "29 >= 30")); + // Extra tokens beyond a valid expression. + assertTrue(CheckPapi.evaluate(player, "40 > 20 extra")); + + } + + @Test + public void testStringEquality() { + // String comparisons with multi-word operands. + assertTrue(CheckPapi.evaluate(player, "john smith == john smith")); + assertFalse(CheckPapi.evaluate(player, "john smith == jane doe")); + // Using inequality operators. + assertTrue(CheckPapi.evaluate(player, "john smith <> jane doe")); + assertFalse(CheckPapi.evaluate(player, "john smith <> john smith")); + } + + @Test + public void testStringLexicographicalComparison() { + // Lexicographical comparison using string compareTo semantics. + assertTrue(CheckPapi.evaluate(player, "apple < banana")); + assertTrue(CheckPapi.evaluate(player, "banana > apple")); + assertTrue(CheckPapi.evaluate(player, "cat >= cat")); + assertTrue(CheckPapi.evaluate(player, "cat <= cat")); + } + + @Test + public void testMultipleConditionsAndOr() { + // AND has higher precedence than OR. + // "john smith == john smith AND 40 > 20" should be true. + assertTrue(CheckPapi.evaluate(player, "john smith == john smith AND 40 > 20")); + // "john smith == jane doe OR 40 > 20" should be true because second condition is true. + assertTrue(CheckPapi.evaluate(player, "john smith == jane doe OR 40 > 20")); + // "john smith == jane doe AND 40 > 20" should be false because first condition fails. + assertFalse(CheckPapi.evaluate(player, "john smith == jane doe AND 40 > 20")); + // Mixed AND and OR: AND is evaluated first. + // Equivalent to: (john smith == jane doe) OR ((40 > 20) AND (10 < 20)) + assertTrue(CheckPapi.evaluate(player, "john smith == jane doe OR 40 > 20 AND 10 < 20")); + } + + @Test + public void testInvalidFormula() { + // Missing operator between operands. + assertFalse(CheckPapi.evaluate(player, "40 40")); + // Incomplete condition. + assertFalse(CheckPapi.evaluate(player, "40 >")); + } + +} From d64b1a609e73636d3fd110d52978410388c7ccbc Mon Sep 17 00:00:00 2001 From: tastybento Date: Fri, 14 Feb 2025 16:52:07 +0900 Subject: [PATCH 32/34] Log errors to console. --- .../database/object/requirements/CheckPapi.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/main/java/world/bentobox/challenges/database/object/requirements/CheckPapi.java b/src/main/java/world/bentobox/challenges/database/object/requirements/CheckPapi.java index 76a45a39..20f5b151 100644 --- a/src/main/java/world/bentobox/challenges/database/object/requirements/CheckPapi.java +++ b/src/main/java/world/bentobox/challenges/database/object/requirements/CheckPapi.java @@ -7,6 +7,7 @@ import org.bukkit.entity.Player; import me.clip.placeholderapi.PlaceholderAPI; +import world.bentobox.bentobox.BentoBox; public class CheckPapi { @@ -155,7 +156,9 @@ public boolean parseCondition() { // Parse left operand. StringBuilder leftSB = new StringBuilder(); if (!hasNext()) { - throw new RuntimeException("Expected left operand but reached end of expression"); + BentoBox.getInstance() + .logError("Challenges PAPI formula error: Expected left operand but reached end of expression"); + return false; } // Collect tokens for the left operand until an operator is encountered. while (hasNext() && !isOperator(peek())) { @@ -210,7 +213,8 @@ public boolean parseCondition() { case ">": return leftNum > rightNum; default: - throw new RuntimeException("Unsupported operator: " + operator); + BentoBox.getInstance().logError("Challenges PAPI formula error: Unsupported operator: " + operator); + return false; } } else { // String comparison. @@ -232,7 +236,8 @@ public boolean parseCondition() { case ">": return leftOperand.compareTo(rightOperand) > 0; default: - throw new RuntimeException("Unsupported operator: " + operator); + BentoBox.getInstance().logError("Challenges PAPI formula error: Unsupported operator: " + operator); + return false; } } } From d4053903398aa5fe483d88e757ec762ab6e02614 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 15 Feb 2025 08:32:24 +0900 Subject: [PATCH 33/34] Correct text now that PAPI strings supported --- src/main/resources/locales/en-US.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index a576a4c4..197e5c44 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -1168,7 +1168,7 @@ challenges: file-name-exist: "&c A file named '[id]' already exists. Cannot overwrite." write-search: "&e Please enter a search value. (Type 'cancel' to exit)" search-updated: "&a Search value updated." - enter-formula: "&a Enter a formula that uses PAPI number placeholders and symbols =,<>,<+,>=, AND, OR only.\n&a Example: &7 %aoneblock_my_island_lifetime_count% >= 1000 AND %Level_aoneblock_island_level% >= 100" + enter-formula: "&a Enter a formula that uses PAPI placeholders and symbols =,<>,<+,>=, ==, !=, AND, OR only.\n&a Example: &7 %my_lifetime_count% >= 1000 AND %island_level% >= 100" titles: challenge-title: "Success!" challenge-subtitle: "[friendlyName]" From 23dae376b8ffb0f0bc32074ff8e773ae051ff1d1 Mon Sep 17 00:00:00 2001 From: tastybento Date: Sat, 15 Feb 2025 13:08:52 +0900 Subject: [PATCH 34/34] Fix import and export of challenges. Added Tag TypeAdapter from BBox --- .../bentobox/challenges/managers/ChallengesImportManager.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/world/bentobox/challenges/managers/ChallengesImportManager.java b/src/main/java/world/bentobox/challenges/managers/ChallengesImportManager.java index 85abe79a..70a21dd1 100644 --- a/src/main/java/world/bentobox/challenges/managers/ChallengesImportManager.java +++ b/src/main/java/world/bentobox/challenges/managers/ChallengesImportManager.java @@ -40,6 +40,7 @@ import world.bentobox.bentobox.api.localization.TextVariables; import world.bentobox.bentobox.api.user.User; import world.bentobox.bentobox.database.json.BentoboxTypeAdapterFactory; +import world.bentobox.bentobox.database.json.adapters.TagTypeAdapterFactory; import world.bentobox.bentobox.database.objects.DataObject; import world.bentobox.bentobox.util.ItemParser; import world.bentobox.bentobox.util.Util; @@ -1147,6 +1148,7 @@ private static final class DefaultJSONHandler GsonBuilder builder = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().enableComplexMapKeySerialization(); // Register adapters builder.registerTypeAdapterFactory(new BentoboxTypeAdapterFactory(addon.getPlugin())); + builder.registerTypeAdapterFactory(new TagTypeAdapterFactory()); // Keep null in the database builder.serializeNulls(); // Allow characters like < or > without escaping them