tokens) {
+ this.tokens = tokens;
+ }
+
+ public boolean hasNext() {
+ return pos < tokens.size();
+ }
+
+ public String peek() {
+ return tokens.get(pos);
+ }
+
+ public String next() {
+ return tokens.get(pos++);
+ }
+
+ /**
+ * Parses an Expression:
+ * Expression -> Term { OR Term }
+ *
+ * @return the boolean value of the expression.
+ */
+ public boolean parseExpression() {
+ boolean value = parseTerm();
+ while (hasNext() && isOr(peek())) {
+ next(); // consume "OR"
+ boolean termValue = parseTerm();
+ value = value || termValue;
+ }
+ return value;
+ }
+
+ /**
+ * Parses a Term:
+ * Term -> Condition { AND Condition }
+ *
+ * @return the boolean value of the term.
+ */
+ public boolean parseTerm() {
+ boolean value = parseCondition();
+ while (hasNext() && isAnd(peek())) {
+ next(); // consume "AND"
+ boolean conditionValue = parseCondition();
+ value = value && conditionValue;
+ }
+ return value;
+ }
+
+ /**
+ * 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 parseCondition() {
+ // Parse left operand.
+ StringBuilder leftSB = new StringBuilder();
+ if (!hasNext()) {
+ 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())) {
+ 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();
+ 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();
+
+ if (rightOperand.isEmpty()) {
+ return false;
+ }
+
+ // 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:
+ BentoBox.getInstance().logError("Challenges PAPI formula error: Unsupported operator: " + operator);
+ return false;
+ }
+ } 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:
+ BentoBox.getInstance().logError("Challenges PAPI formula error: Unsupported operator: " + operator);
+ return false;
+ }
+ }
+ }
+
+ /**
+ * 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 {
+ return Double.parseDouble(s);
+ } catch (NumberFormatException e) {
+ return null;
+ }
+ }
+ }
+}
\ No newline at end of file
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/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/OtherRequirements.java b/src/main/java/world/bentobox/challenges/database/object/requirements/OtherRequirements.java
index 5f59e7d5..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.
@@ -139,18 +146,53 @@ public void setRequiredIslandLevel(long requiredIslandLevel)
{
this.requiredIslandLevel = requiredIslandLevel;
}
-
+
+ /**
+ * @return the papiString
+ */
+ public String getPapiString() {
+ if (papiString == null) {
+ papiString = "";
+ }
+ return papiString;
+ }
+
+ /**
+ * @param papiString the papiString to set
+ */
+ 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()
{
@@ -162,6 +204,8 @@ public Requirements copy()
clone.setRequiredMoney(this.requiredMoney);
clone.setTakeMoney(this.takeMoney);
clone.setRequiredIslandLevel(this.requiredIslandLevel);
+ clone.setPapiString(this.papiString);
+ clone.setAdvancements(this.advancements);
return clone;
}
@@ -201,4 +245,18 @@ public Requirements copy()
*/
@Expose
private long requiredIslandLevel;
+
+ /**
+ * Formulas that include math symbols and PAPI placeholders
+ */
+ @Expose
+ private String papiString;
+
+ /**
+ * List of advancements
+ */
+ @Expose
+ @JsonAdapter(AdvancementsListAdapter.class)
+ private List advancements;
+
}
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/database/object/requirements/StatisticRequirements.java b/src/main/java/world/bentobox/challenges/database/object/requirements/StatisticRequirements.java
index b90ec701..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,227 +2,139 @@
// Created by BONNe
// Copyright - 2021
//
+// Enhanced by tastybento
package world.bentobox.challenges.database.object.requirements;
-import com.google.gson.annotations.Expose;
+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;
import org.eclipse.jdt.annotation.Nullable;
+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/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..70a21dd1 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;
@@ -31,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;
@@ -41,6 +51,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;
@@ -347,13 +358,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);
}
}
@@ -1138,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
diff --git a/src/main/java/world/bentobox/challenges/managers/ChallengesManager.java b/src/main/java/world/bentobox/challenges/managers/ChallengesManager.java
index a61e175c..7ae75e50 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;
@@ -1109,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);
@@ -1123,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)
{
@@ -1135,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);
@@ -1165,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);
@@ -1572,7 +1594,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());
@@ -1638,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();
@@ -1696,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);
}
@@ -1712,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/CommonPagedPanel.java b/src/main/java/world/bentobox/challenges/panel/CommonPagedPanel.java
index 947c10e5..dec98c8a 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;
@@ -103,6 +103,7 @@ else if (this.pageIndex > (size / MAX_ELEMENTS))
{
if (!panelBuilder.slotOccupied(index))
{
+ // Show an item
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 6709d617..32f531b8 100644
--- a/src/main/java/world/bentobox/challenges/panel/CommonPanel.java
+++ b/src/main/java/world/bentobox/challenges/panel/CommonPanel.java
@@ -29,6 +29,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;
@@ -38,6 +39,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 +151,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,50 +287,47 @@ private String generateRequirements(Challenge challenge, @Nullable User target)
private String generateIslandChallenge(IslandRequirements requirement) {
final String reference = Constants.DESCRIPTIONS + "challenge.requirements.island.";
- String blocks;
-
+ // Required 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,
@@ -340,10 +340,63 @@ private String generateIslandChallenge(IslandRequirements requirement) {
? this.user.getTranslationOrNothing(reference + "warning-entity")
: "";
- return this.user.getTranslationOrNothing(reference + "lore", "[blocks]", blocks, "[entities]", entities,
+ 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.
*
@@ -415,7 +468,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.
@@ -423,48 +476,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();
}
/**
@@ -607,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()
@@ -688,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);
@@ -721,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/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/AbstractManageEnumPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/AbstractManageEnumPanel.java
new file mode 100644
index 00000000..da5a6ffd
--- /dev/null
+++ b/src/main/java/world/bentobox/challenges/panel/admin/AbstractManageEnumPanel.java
@@ -0,0 +1,161 @@
+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/EditChallengePanel.java b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengePanel.java
index 147f8f7b..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,9 +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.SingleBlockSelector;
-import world.bentobox.challenges.panel.util.SingleEntitySelector;
-import world.bentobox.challenges.panel.util.StatisticSelector;
+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;
@@ -60,10 +61,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 +72,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 +89,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 +100,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 +112,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 +151,18 @@ 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(13, this.createButton(Button.HIDE_REWARD_ITEMS));
+ 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,17 @@ 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(20, this.createRequirementButton(RequirementButton.REQUIRED_ENTITYTAGS));
+ 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 +192,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 +213,18 @@ 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(14, this.createRequirementButton(RequirementButton.REQUIRED_PAPI));
+ panelBuilder.item(23, this.createRequirementButton(RequirementButton.REQUIRED_LEVEL));
- panelBuilder.item(23, this.createRequirementButton(RequirementButton.REQUIRED_LEVEL));
+ panelBuilder.item(16, this.createRequirementButton(RequirementButton.REQUIRED_ADVANCEMENTS));
- panelBuilder.item(25, this.createRequirementButton(RequirementButton.REQUIRED_PERMISSIONS));
+ panelBuilder.item(25, this.createRequirementButton(RequirementButton.REQUIRED_PERMISSIONS));
}
/**
@@ -226,24 +234,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));
}
/**
@@ -252,34 +244,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 +285,57 @@ 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 +345,228 @@ 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 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")));
+
+ 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 +576,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, REQUIRED_ENTITYTAGS -> {
+ 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, REQUIRED_PAPI,
+ REQUIRED_ADVANCEMENTS -> {
+ return this.createOtherRequirementButton(button);
+ }
+ // Statistics
+ case REQUIRED_STATISTICS -> {
+ return this.createStatisticsRequirementButton(button);
+ }
+ // Default behaviour.
+ default -> {
+ return PanelItem.empty();
+ }
+ }
}
/**
@@ -643,127 +659,170 @@ 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", "[tag]",
+ Utils.prettifyObject(block, this.user), "[number]", String.valueOf(count))));
+ }
+
+ icon = new ItemStack(Material.STONE_BRICKS);
+ clickHandler = (panel, user, clickType, slot) -> {
+ 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;
+
+ 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();
}
/**
@@ -772,432 +831,370 @@ private PanelItem createIslandRequirementButton(RequirementButton button) {
* @param button Button which panel item must be created.
* @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();
+ 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 -> {
+ 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 other requirements menu.
+ * 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 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();
+ 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();
}
/**
- * Creates a button for statistic requirements.
+ * This method creates buttons for other requirements menu.
*
- * @param button Button that must be created.
- * @return PanelItem button.
+ * @param button Button which panel item must be created.
+ * @return PanelItem that represents given 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();
+ 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_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_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())));
+ 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();
}
/**
@@ -1207,462 +1204,461 @@ 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 +1669,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 +1720,39 @@ 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, HIDE_REWARD_ITEMS,
}
/**
* 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,
+ 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_STATISTICS,
+ REMOVE_STATISTICS, REQUIRED_PAPI, REQUIRED_ADVANCEMENTS,
}
// ---------------------------------------------------------------------
@@ -1772,4 +1770,5 @@ private enum RequirementButton {
* Variable holds current active menu.
*/
private MenuType currentMenuType;
+
}
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..2ab24700 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;
@@ -750,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) -> {
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/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/ManageBlockGroupsPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/ManageBlockGroupsPanel.java
new file mode 100644
index 00000000..025b2060
--- /dev/null
+++ b/src/main/java/world/bentobox/challenges/panel/admin/ManageBlockGroupsPanel.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 Block Groups that are in required.
+ */
+public class ManageBlockGroupsPanel extends CommonPagedPanel>
+{
+
+ // ---------------------------------------------------------------------
+ // Section: Enums
+ // ---------------------------------------------------------------------
+
+ /**
+ * Functional buttons in current GUI.
+ */
+ private enum Button {
+ ADD_BLOCK_GROUP, REMOVE_BLOCK_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 ManageBlockGroupsPanel(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 ManageBlockGroupsPanel(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-block-groups"));
+
+ // Create nice border.
+ PanelUtils.fillBorder(panelBuilder);
+
+ 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);
+
+ // 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_GROUP -> {
+ 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_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 + "block-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", "[id]",
+ 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/admin/ManageBlocksPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/ManageBlocksPanel.java
index cd43adf7..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,
- 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;
+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
+ }
}
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..a1534d95 100644
--- a/src/main/java/world/bentobox/challenges/panel/admin/ManageEntitiesPanel.java
+++ b/src/main/java/world/bentobox/challenges/panel/admin/ManageEntitiesPanel.java
@@ -1,330 +1,198 @@
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.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.CommonPagedPanel;
import world.bentobox.challenges.panel.CommonPanel;
-import world.bentobox.challenges.panel.ConversationUtils;
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;
-
/**
- * 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 {
+
+ /**
+ * 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) {
+ 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;
+ }
+
+ /**
+ * 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));
+ }
+
+ /**
+ * 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, 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"));
+ }
+ 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/panel/admin/ManageEntityGroupsPanel.java b/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java
new file mode 100644
index 00000000..a8c8cf0e
--- /dev/null
+++ b/src/main/java/world/bentobox/challenges/panel/admin/ManageEntityGroupsPanel.java
@@ -0,0 +1,330 @@
+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.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.panel.util.SingleEntitySelector;
+import world.bentobox.challenges.panel.util.UnifiedMultiSelector.Mode;
+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, 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 + "entity", "[tag]",
+ 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", "[id]",
+ 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);
+ if (entType == null) {
+ return new ItemStack(Material.PAPER, quantity);
+ }
+ return SingleEntitySelector.getIcon(entType);
+
+ }
+
+
+}
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..7d2a40a4
--- /dev/null
+++ b/src/main/java/world/bentobox/challenges/panel/admin/ManageStatisticsPanel.java
@@ -0,0 +1,613 @@
+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 lv.id.bonne.panelutils.PanelUtils;
+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(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, (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/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 e847c97f..00bb833d 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,11 @@
package world.bentobox.challenges.panel.util;
-
-import java.util.*;
+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;
@@ -9,286 +13,109 @@
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 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 Set excluded;
+
+ /**
+ * 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, mode, consumer);
+
+ 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();
+ }
+
+ @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 fefe9927..7df90a56 100644
--- a/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java
+++ b/src/main/java/world/bentobox/challenges/panel/util/MultiEntitySelector.java
@@ -1,282 +1,119 @@
package world.bentobox.challenges.panel.util;
+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.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;
-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 Set excluded;
+
+ /**
+ * Private constructor.
+ *
+ * @param user the user opening the selector
+ * @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, Mode mode, Set excluded,
+ java.util.function.BiConsumer> consumer) {
+ super(user, mode, consumer);
+ this.excluded = excluded;
+ }
+
+ /**
+ * Opens the MultiEntitySelector GUI with the specified parameters.
+ *
+ * @param user the user who opens the GUI
+ * @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, Mode mode, Set excluded,
+ java.util.function.BiConsumer> consumer) {
+ 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 consumer a callback to receive the result
+ */
+ public static void open(User user,
+ java.util.function.BiConsumer> consumer) {
+ new MultiEntitySelector(user, 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 -> !SingleEntitySelector.NON_ENTITIES.contains(entity))
+ .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 SingleEntitySelector.getIcon(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();
+ }
+
+ @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
new file mode 100644
index 00000000..e9191870
--- /dev/null
+++ b/src/main/java/world/bentobox/challenges/panel/util/MultiEntityTypeTagsSelector.java
@@ -0,0 +1,196 @@
+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.Locale;
+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.entity.EntityType;
+import org.bukkit.inventory.ItemStack;
+
+import world.bentobox.bentobox.api.user.User;
+import world.bentobox.challenges.utils.Utils;
+
+/**
+ * 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> {
+
+ private final Set> excluded;
+
+ /**
+ * 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,
+ BiConsumer>> consumer) {
+ super(user, mode, consumer);
+ 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,
+ 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,
+ 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 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()));
+
+ // 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 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 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