Merge pull request #368 from BentoBoxWorld/add_tags

Add tags
This commit is contained in:
tastybento 2025-02-06 21:52:55 -08:00 committed by GitHub
commit 7935c62738
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
43 changed files with 4065 additions and 2977 deletions

View File

@ -44,7 +44,7 @@
<!-- More visible way how to change dependency versions -->
<spigot.version>1.21.3-R0.1-SNAPSHOT</spigot.version>
<spigot-annotations.version>1.2.3-SNAPSHOT</spigot-annotations.version>
<bentobox.version>2.7.1-SNAPSHOT</bentobox.version>
<bentobox.version>3.2.4-SNAPSHOT</bentobox.version>
<level.version>2.6.3</level.version>
<vault.version>1.7</vault.version>
<panelutils.version>1.2.0</panelutils.version>

View File

@ -15,8 +15,8 @@ import world.bentobox.bentobox.api.flags.Flag;
import world.bentobox.bentobox.database.DatabaseSetup.DatabaseType;
import world.bentobox.bentobox.hooks.VaultHook;
import world.bentobox.bentobox.managers.RanksManager;
import world.bentobox.challenges.commands.ChallengesPlayerCommand;
import world.bentobox.challenges.commands.ChallengesGlobalPlayerCommand;
import world.bentobox.challenges.commands.ChallengesPlayerCommand;
import world.bentobox.challenges.commands.admin.ChallengesAdminCommand;
import world.bentobox.challenges.commands.admin.ChallengesGlobalAdminCommand;
import world.bentobox.challenges.config.Settings;

View File

@ -1,7 +1,11 @@
package world.bentobox.challenges.database.object;
import java.util.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.bukkit.Material;

View File

@ -1,7 +1,11 @@
package world.bentobox.challenges.database.object;
import java.util.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import org.bukkit.Material;

View File

@ -7,12 +7,20 @@
package world.bentobox.challenges.database.object.adapters;
import com.google.gson.*;
import org.bukkit.entity.EntityType;
import java.lang.reflect.Type;
import java.util.EnumMap;
import java.util.Map;
import org.bukkit.entity.EntityType;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import world.bentobox.bentobox.BentoBox;

View File

@ -7,9 +7,16 @@
package world.bentobox.challenges.database.object.adapters;
import com.google.gson.*;
import java.lang.reflect.Type;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import world.bentobox.challenges.database.object.Challenge;

View File

@ -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;

View File

@ -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 @@ import world.bentobox.challenges.database.object.adapters.EntityCompatibilityAda
*/
public class IslandRequirements extends Requirements
{
/**
* Constructor Requirements creates a new Requirements instance.
*/
public IslandRequirements()
{
// Empty constructor for data loader
}
/**
* Constructor Requirements creates a new Requirements instance.
*/
public IslandRequirements() {
// Empty constructor for data loader
}
// ---------------------------------------------------------------------
// Section: Getters and Setters
// ---------------------------------------------------------------------
// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
/**
* Method IslandRequirements#getRequiredBlocks returns the requiredBlocks of this object.
*
* @return the requiredBlocks (type {@code Map<Material, Integer>}) of this object.
*/
public Map<Material, Integer> getRequiredBlocks()
{
return requiredBlocks;
}
/**
* Map that contains which materials and how many is necessary around player to complete challenge.
*/
@Expose
private Map<Material, Integer> requiredBlocks = new EnumMap<>(Material.class);
@Expose
private Map<Tag<Material>, Integer> requiredMaterialTags = new HashMap<>();
@Expose
private Map<Tag<Fluid>, Integer> requiredFluidTags = new HashMap<>();
@Expose
private Map<Tag<EntityType>, 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<EntityType, Integer> 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#setRequiredBlocks sets new value for the requiredBlocks of this object.
* @param requiredBlocks new value for this object.
*
*/
public void setRequiredBlocks(Map<Material, Integer> requiredBlocks)
{
this.requiredBlocks = requiredBlocks;
}
/**
* Method IslandRequirements#getRequiredBlocks returns the requiredBlocks of this object.
*
* @return the requiredBlocks (type {@code Map<Material, Integer>}) of this object.
*/
public Map<Material, Integer> getRequiredBlocks() {
return 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#setRequiredBlocks sets new value for the requiredBlocks of this object.
* @param requiredBlocks new value for this object.
*
*/
public void setRequiredBlocks(Map<Material, Integer> requiredBlocks) {
this.requiredBlocks = requiredBlocks;
}
/**
* 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#isRemoveBlocks returns the removeBlocks of this object.
*
* @return the removeBlocks (type boolean) of this object.
*/
public boolean isRemoveBlocks() {
return removeBlocks;
}
/**
* Method IslandRequirements#getRequiredEntities returns the requiredEntities of this object.
*
* @return the requiredEntities (type {@code Map<EntityType, Integer>}) of this object.
*/
public Map<EntityType, Integer> getRequiredEntities()
{
return requiredEntities;
}
/**
* 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#setRequiredEntities sets new value for the requiredEntities of this object.
* @param requiredEntities new value for this object.
*
*/
public void setRequiredEntities(Map<EntityType, Integer> requiredEntities)
{
this.requiredEntities = requiredEntities;
}
/**
* Method IslandRequirements#getRequiredEntities returns the requiredEntities of this object.
*
* @return the requiredEntities (type {@code Map<EntityType, Integer>}) of this object.
*/
public Map<EntityType, Integer> getRequiredEntities() {
return 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#setRequiredEntities sets new value for the requiredEntities of this object.
* @param requiredEntities new value for this object.
*
*/
public void setRequiredEntities(Map<EntityType, Integer> 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#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#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 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 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;
}
/**
* 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()));
/**
* @return the requiredMaterialTags
*/
public Map<Tag<Material>, Integer> getRequiredMaterialTags() {
return requiredMaterialTags;
}
clone.setRequiredBlocks(new HashMap<>(this.requiredBlocks));
clone.setRemoveBlocks(this.removeBlocks);
clone.setRequiredEntities(new HashMap<>(this.requiredEntities));
clone.setRemoveEntities(this.removeEntities);
/**
* @param requiredMaterialTags the requiredMaterialTags to set
*/
public void setRequiredMaterialTags(Map<Tag<Material>, Integer> requiredMaterialTags) {
this.requiredMaterialTags = requiredMaterialTags;
}
clone.setSearchRadius(this.searchRadius);
/**
* @return the requiredFluidTags
*/
public Map<Tag<Fluid>, Integer> getRequiredFluidTags() {
return requiredFluidTags;
}
return clone;
}
/**
* @param requiredFluidTags the requiredFluidTags to set
*/
public void setRequiredFluidTags(Map<Tag<Fluid>, Integer> requiredFluidTags) {
this.requiredFluidTags = requiredFluidTags;
}
/**
* @return the requiredEntityTypeTags
*/
public Map<Tag<EntityType>, Integer> getRequiredEntityTypeTags() {
return requiredEntityTypeTags;
}
// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
/**
* @param requiredEntityTypeTags the requiredEntityTypeTags to set
*/
public void setRequiredEntityTypeTags(Map<Tag<EntityType>, Integer> requiredEntityTypeTags) {
this.requiredEntityTypeTags = requiredEntityTypeTags;
}
/**
* Map that contains which materials and how many is necessary around player to complete challenge.
*/
@Expose
private Map<Material, Integer> 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<EntityType, Integer> 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;
}

View File

@ -83,8 +83,8 @@ public abstract class Requirements
/**
* 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<String> requiredPermissions = new HashSet<>();
}

View File

@ -7,12 +7,13 @@
package world.bentobox.challenges.database.object.requirements;
import com.google.gson.annotations.Expose;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.entity.EntityType;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.annotations.Expose;
public class StatisticRequirements extends Requirements
{

View File

@ -2,9 +2,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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -9,7 +9,16 @@ import java.io.IOException;
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;

View File

@ -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;
@ -1572,7 +1584,8 @@ public class ChallengesManager
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());

View File

@ -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 @@ public abstract class CommonPagedPanel<T> extends CommonPanel
{
if (!panelBuilder.slotOccupied(index))
{
// Show a challenge
panelBuilder.item(index, this.createElementButton(objectList.get(objectIndex++)));
}

View File

@ -38,6 +38,8 @@ import world.bentobox.challenges.utils.Utils;
* This class contains common methods for all panels.
*/
public abstract class CommonPanel {
private static final long MAXSIZE = 10;
/**
* This is default constructor for all classes that extends CommonPanel.
*
@ -148,7 +150,7 @@ public abstract class CommonPanel {
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 +286,47 @@ public abstract class CommonPanel {
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 +339,63 @@ public abstract class CommonPanel {
? 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.
*

View File

@ -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;

View File

@ -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.panels.builders.PanelItemBuilder;
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;

View File

@ -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;

View File

@ -0,0 +1,321 @@
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.BentoBox;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.builders.PanelBuilder;
import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
import world.bentobox.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<Tag<Material>>
{
// ---------------------------------------------------------------------
// Section: Enums
// ---------------------------------------------------------------------
/**
* Functional buttons in current GUI.
*/
private enum Button {
ADD_BLOCK_GROUP, REMOVE_BLOCK_GROUP
}
// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
/**
* Contains selected materials.
*/
private final Set<Tag<Material>> selectedTags;
/**
* List of materials to avoid order issues.
*/
private final List<Tag<Material>> materialList;
/**
* List of required materials.
*/
private final Map<Tag<Material>, Integer> tagMap;
/**
* Stores filtered items.
*/
private List<Tag<Material>> filterElements;
private ManageBlockGroupsPanel(CommonPanel parentGUI, Map<Tag<Material>, 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<Tag<Material>, 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<String> 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<Material> tag)
{
final String reference = Constants.BUTTON + "block-group.";
List<String> description = new ArrayList<>();
if (this.selectedTags.contains(tag))
{
description.add(this.user.getTranslation(reference + "selected"));
}
description.add("");
description.add(this.user.getTranslation(Constants.TIPS + "left-click-to-choose"));
if (this.selectedTags.contains(tag))
{
description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-deselect"));
}
else
{
description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-select"));
}
return new PanelItemBuilder().
name(this.user.getTranslation(reference + "name", "[tag]",
Utils.prettifyObject(tag, this.user))).
icon(getIcon(tag, this.tagMap.get(tag))).
description(description).
clickHandler((panel, user1, clickType, slot) -> {
// On right click change which entities are selected for deletion.
if (clickType.isRightClick())
{
if (!this.selectedTags.add(tag))
{
// Remove material if it is already selected
this.selectedTags.remove(tag);
}
this.build();
}
else
{
Consumer<Number> 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<Material> 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);
}
}

View File

@ -282,7 +282,7 @@ public class ManageBlocksPanel extends CommonPagedPanel<Material>
private enum Button
{
ADD_BLOCK,
REMOVE_BLOCK
REMOVE_BLOCK
}

View File

@ -0,0 +1,334 @@
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.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<Tag<EntityType>>
{
// ---------------------------------------------------------------------
// Section: Enums
// ---------------------------------------------------------------------
/**
* Functional buttons in current GUI.
*/
private enum Button {
ADD_ENTITY_GROUP, REMOVE_ENTITY_GROUP
}
// ---------------------------------------------------------------------
// Section: Variables
// ---------------------------------------------------------------------
/**
* Contains selected materials.
*/
private final Set<Tag<EntityType>> selectedTags;
/**
* List of materials to avoid order issues.
*/
private final List<Tag<EntityType>> materialList;
/**
* List of required materials.
*/
private final Map<Tag<EntityType>, Integer> tagMap;
/**
* Stores filtered items.
*/
private List<Tag<EntityType>> filterElements;
private ManageEntityGroupsPanel(CommonPanel parentGUI, Map<Tag<EntityType>, 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<Tag<EntityType>, 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<String> description = new ArrayList<>(3);
description.add(this.user.getTranslation(reference + "description"));
ItemStack icon;
PanelItem.ClickHandler clickHandler;
boolean glow;
switch (button)
{
case ADD_ENTITY_GROUP -> {
icon = new ItemStack(Material.BUCKET);
clickHandler = (panel, user1, clickType, slot) ->
{
MultiEntityTypeTagsSelector.open(this.user, MultiEntityTypeTagsSelector.Mode.ENTITY_TYPE,
new HashSet<>(this.materialList),
(status, materials) ->
{
if (status)
{
materials.forEach(material ->
{
this.tagMap.put(material, 1);
this.materialList.add(material);
});
}
this.build();
});
return true;
};
glow = false;
description.add("");
description.add(this.user.getTranslation(Constants.TIPS + "click-to-add"));
}
case REMOVE_ENTITY_GROUP -> {
if (!this.selectedTags.isEmpty())
{
description.add(this.user.getTranslation(reference + "title"));
this.selectedTags.forEach(material ->
description.add(this.user.getTranslation(reference + "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<EntityType> tag)
{
final String reference = Constants.BUTTON + "entity-group.";
List<String> description = new ArrayList<>();
if (this.selectedTags.contains(tag))
{
description.add(this.user.getTranslation(reference + "selected"));
}
description.add("");
description.add(this.user.getTranslation(Constants.TIPS + "left-click-to-choose"));
if (this.selectedTags.contains(tag))
{
description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-deselect"));
}
else
{
description.add(this.user.getTranslation(Constants.TIPS + "right-click-to-select"));
}
return new PanelItemBuilder().
name(this.user.getTranslation(reference + "name", "[tag]",
Utils.prettifyObject(tag, this.user))).
icon(getIcon(tag, this.tagMap.get(tag))).
description(description).
clickHandler((panel, user1, clickType, slot) -> {
// On right click change which entities are selected for deletion.
if (clickType.isRightClick())
{
if (!this.selectedTags.add(tag))
{
// Remove material if it is already selected
this.selectedTags.remove(tag);
}
this.build();
}
else
{
Consumer<Number> 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<EntityType> 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);
}
String eggName = entType.getKey().getKey().toUpperCase(Locale.ENGLISH) + "_SPAWN_EGG";
Material result;
try {
result = Material.valueOf(eggName);
} catch (Exception e) {
result = Material.PAPER;
}
return new ItemStack(result, quantity);
}
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -20,7 +20,11 @@ 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.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;

View File

@ -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,108 @@ import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import lv.id.bonne.panelutils.PanelUtils;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.builders.PanelBuilder;
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<Material>
{
private MultiBlockSelector(User user, Mode mode, Set<Material> excluded, BiConsumer<Boolean, Collection<Material>> consumer)
{
super(user);
this.consumer = consumer;
public class MultiBlockSelector extends UnifiedMultiSelector<Material> {
// 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);
private final Mode mode;
private final Set<Material> excluded;
// Piston head and moving piston is not necessary. useless.
excluded.add(Material.PISTON_HEAD);
excluded.add(Material.MOVING_PISTON);
public enum Mode {
BLOCKS, ITEMS, ANY
}
// Barrier cannot be accessible to user.
excluded.add(Material.BARRIER);
/**
* 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<Material> excluded,
BiConsumer<Boolean, Collection<Material>> consumer) {
super(user, consumer);
this.mode = mode;
if (excluded == null) {
excluded = new HashSet<>();
}
this.excluded = excluded;
// Add default exclusions
this.excluded.add(Material.AIR);
this.excluded.add(Material.CAVE_AIR);
this.excluded.add(Material.VOID_AIR);
this.excluded.add(Material.PISTON_HEAD);
this.excluded.add(Material.MOVING_PISTON);
this.excluded.add(Material.BARRIER);
}
this.selectedElements = new HashSet<>();
/**
* 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<Material> excluded,
BiConsumer<Boolean, Collection<Material>> consumer) {
new MultiBlockSelector(user, mode, excluded, consumer).build();
}
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;
}
/**
* 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<Boolean, Collection<Material>> consumer) {
new MultiBlockSelector(user, Mode.ANY, new HashSet<>(), consumer).build();
}
@Override
protected List<Material> 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());
}
/**
* 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<Material> excluded, BiConsumer<Boolean, Collection<Material>> consumer)
{
new MultiBlockSelector(user, mode, excluded, consumer).build();
}
@Override
protected String getTitleKey() {
return "block-selector";
}
@Override
protected String getElementKeyPrefix() {
return "material.";
}
/**
* 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<Boolean, Collection<Material>> consumer)
{
new MultiBlockSelector(user, Mode.ANY, new HashSet<>(), consumer).build();
}
@Override
protected ItemStack getIcon(Material element) {
return PanelUtils.getMaterialItem(element);
}
@Override
protected String getElementDisplayName(Material element) {
return Utils.prettifyObject(element, this.user);
}
// ---------------------------------------------------------------------
// 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<String> 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<String> 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<Material> elements;
/**
* Set that contains selected materials.
*/
private final Set<Material> selectedElements;
/**
* This variable stores consumer.
*/
private final BiConsumer<Boolean, Collection<Material>> consumer;
/**
* Stores filtered items.
*/
private List<Material> filterElements;
@Override
protected String elementToString(Material element) {
return element.name();
}
}

View File

@ -1,282 +1,129 @@
package world.bentobox.challenges.panel.util;
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.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.entity.EntityType;
import org.bukkit.inventory.ItemStack;
import lv.id.bonne.panelutils.PanelUtils;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.builders.PanelBuilder;
import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.challenges.utils.Constants;
import world.bentobox.challenges.utils.Utils;
/**
* This class contains all necessary things that allows to select single block from all ingame blocks. Selected
* block will be returned via BiConsumer.
* This class provides a multi-selector GUI for selecting entities.
* It extends the unified multi-selector base class and supplies the
* type-specific implementations required for entity selection.
*/
public class MultiEntitySelector extends PagedSelector<EntityType>
{
private MultiEntitySelector(User user, boolean asEgg, Mode mode, Set<EntityType> excluded, BiConsumer<Boolean, Collection<EntityType>> consumer)
{
super(user);
public class MultiEntitySelector extends UnifiedMultiSelector<EntityType> {
this.consumer = consumer;
this.asEgg = asEgg;
this.selectedElements = new HashSet<>();
private final boolean asEgg;
private final Mode mode;
private final Set<EntityType> excluded;
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;
}
/**
* Specifies which entities to display.
*/
public enum Mode {
ALIVE, ANY
}
/**
* Private constructor.
*
* @param user the user opening the selector
* @param asEgg if true, display entities using their spawn egg icon; otherwise, use the entity head
* @param mode determines whether to show only living entities (ALIVE) or all (ANY)
* @param excluded a set of EntityType values to exclude
* @param consumer the callback to be invoked when the user confirms or cancels
*/
private MultiEntitySelector(User user, boolean asEgg, Mode mode, Set<EntityType> excluded,
java.util.function.BiConsumer<Boolean, Collection<EntityType>> consumer) {
super(user, consumer);
this.asEgg = asEgg;
this.mode = mode;
this.excluded = excluded;
}
/**
* 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<EntityType> excluded, BiConsumer<Boolean, Collection<EntityType>> consumer)
{
new MultiEntitySelector(user, asEgg, mode, excluded, consumer).build();
}
/**
* Opens the MultiEntitySelector GUI with the specified parameters.
*
* @param user the user who opens the GUI
* @param asEgg if true, show the entity spawn egg icon; otherwise, show the entity head
* @param mode the filtering mode (ALIVE or ANY)
* @param excluded a set of EntityType values to exclude from the list
* @param consumer a callback to receive the result
*/
public static void open(User user, boolean asEgg, Mode mode, Set<EntityType> excluded,
java.util.function.BiConsumer<Boolean, Collection<EntityType>> consumer) {
new MultiEntitySelector(user, asEgg, mode, excluded, consumer).build();
}
/**
* Opens the MultiEntitySelector GUI with default parameters (mode ANY and no exclusions).
*
* @param user the user who opens the GUI
* @param asEgg if true, show the entity spawn egg icon; otherwise, show the entity head
* @param consumer a callback to receive the result
*/
public static void open(User user, boolean asEgg,
java.util.function.BiConsumer<Boolean, Collection<EntityType>> consumer) {
new MultiEntitySelector(user, asEgg, Mode.ANY, new HashSet<>(), consumer).build();
}
/**
* This method opens GUI that allows to select challenge type.
*
* @param user User who opens GUI.
* @param consumer Consumer that allows to get clicked type.
*/
public static void open(User user, boolean asEgg, BiConsumer<Boolean, Collection<EntityType>> consumer)
{
new MultiEntitySelector(user, asEgg, Mode.ANY, new HashSet<>(), consumer).build();
}
/**
* Returns the list of EntityType values to display, applying the specified exclusions and mode.
*/
@Override
protected List<EntityType> getElements() {
return Arrays.stream(EntityType.values()).filter(entity -> excluded == null || !excluded.contains(entity))
.filter(entity -> mode == Mode.ALIVE ? entity.isAlive() : true)
.sorted(Comparator.comparing(EntityType::name)).collect(Collectors.toList());
}
/**
* Returns the title key used to form the GUI title.
*/
@Override
protected String getTitleKey() {
return "entity-selector";
}
// ---------------------------------------------------------------------
// Section: Methods
// ---------------------------------------------------------------------
/**
* Returns the translation key prefix for element buttons.
*/
@Override
protected String getElementKeyPrefix() {
return "entity.";
}
/**
* Returns the icon for the given EntityType.
* If asEgg is true, an entity spawn egg is returned; otherwise, the entity head is returned.
*/
@Override
protected ItemStack getIcon(EntityType element) {
return asEgg ? PanelUtils.getEntityEgg(element) : PanelUtils.getEntityHead(element);
}
/**
* 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"));
/**
* Returns the display name for the given EntityType.
*/
@Override
protected String getElementDisplayName(EntityType element) {
return Utils.prettifyObject(element, this.user);
}
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<String> 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<String> 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<EntityType> elements;
/**
* Set that contains selected materials.
*/
private final Set<EntityType> selectedElements;
/**
* This variable stores consumer.
*/
private final BiConsumer<Boolean, Collection<EntityType>> consumer;
/**
* Indicates that entity must be displayed as egg.
*/
private final boolean asEgg;
/**
* Stores filtered items.
*/
private List<EntityType> filterElements;
/**
* Returns a string representation of the given EntityType used for filtering.
*/
@Override
protected String elementToString(EntityType element) {
return element.name();
}
}

View File

@ -0,0 +1,200 @@
package world.bentobox.challenges.panel.util;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.HashSet;
import java.util.function.BiConsumer;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Registry;
import org.bukkit.Tag;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.challenges.utils.Utils;
/**
* 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<Tag<EntityType>> {
private final Mode mode;
private final Set<Tag<EntityType>> excluded;
/**
* Defines filtering modes.
*/
public enum Mode {
ENTITY_TYPE, ANY
}
/**
* Private constructor.
*
* @param user the user opening the selector
* @param mode the mode (ENTITY_TYPE or ANY) that might influence filtering behavior
* @param excluded a set of tags to be excluded from display
* @param consumer a callback to receive the selected tags (or cancellation)
*/
private MultiEntityTypeTagsSelector(User user, Mode mode, Set<Tag<EntityType>> excluded,
BiConsumer<Boolean, java.util.Collection<Tag<EntityType>>> consumer) {
super(user, consumer);
this.mode = mode; // This is not currently used
this.excluded = excluded;
}
/**
* Opens the entity-type tag selector GUI with the specified mode and exclusions.
*
* @param user the user who opens the GUI
* @param mode filtering mode (ENTITY_TYPE or ANY)
* @param excluded a set of tags to exclude
* @param consumer a callback to receive the result
*/
public static void open(User user, Mode mode, Set<Tag<EntityType>> excluded,
BiConsumer<Boolean, java.util.Collection<Tag<EntityType>>> 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<Boolean, java.util.Collection<Tag<EntityType>>> consumer) {
new MultiEntityTypeTagsSelector(user, Mode.ANY, new HashSet<>(), consumer).build();
}
/**
* Retrieves the list of available entity-type tags.
* <p>
* This method uses Bukkits 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.
* </p>
*
* @return a sorted and filtered list of Tag&lt;EntityType&gt;
*/
@Override
protected List<Tag<EntityType>> getElements() {
List<Tag<EntityType>> tagList = new ArrayList<>();
Iterable<Tag<EntityType>> 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<EntityType> 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.
* <p>
* If the tags 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.
* </p>
*
* @param element the Tag&lt;EntityType&gt; for which to determine the icon
* @return an ItemStack representing the icon
*/
@Override
protected ItemStack getIcon(Tag<EntityType> 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&lt;EntityType&gt;
* @return a pretty-printed string for display
*/
@Override
protected String getElementDisplayName(Tag<EntityType> element) {
return Utils.prettifyObject(element, this.user);
}
/**
* Returns a string representation of the tag used for filtering.
*
* @param element the Tag&lt;EntityType&gt;
* @return the tags key (as a string)
*/
@Override
protected String elementToString(Tag<EntityType> element) {
return element.getKey().getKey();
}
}

View File

@ -0,0 +1,229 @@
package world.bentobox.challenges.panel.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.function.BiConsumer;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.Registry;
import org.bukkit.Tag;
import org.bukkit.inventory.ItemStack;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.challenges.utils.Utils;
/**
* This class provides a multi-selector GUI for selecting material tags.
* It extends the unified multi-selector base class and supplies the tagspecific
* implementations such as retrieving the tag list, filtering unwanted tags,
* selecting an appropriate icon, and generating display names.
*
* <p>
* Two static open methods are provided: one that accepts a filtering mode and an excluded set,
* and one that uses default parameters.
* </p>
*
* @see UnifiedMultiSelector
*/
public class MultiMaterialTagsSelector extends UnifiedMultiSelector<Tag<Material>> {
/**
* A map of specific tags to custom icon materials.
*/
public static final Map<Tag<Material>, Material> ICONS = Map.of(
Tag.AIR, Material.BARRIER, Tag.FIRE, Material.TORCH, Tag.CANDLE_CAKES, Material.CAKE, Tag.PORTALS,
Material.MAGENTA_STAINED_GLASS_PANE, Tag.WALL_HANGING_SIGNS, Material.ACACIA_SIGN, Tag.WALL_SIGNS,
Material.OAK_SIGN,
Tag.WALL_CORALS, Material.BUBBLE_CORAL_FAN, Tag.CAVE_VINES, Material.VINE
);
private final Mode mode;
private final Set<Tag<Material>> excluded;
/**
* Modes for filtering material tags.
*/
public enum Mode {
BLOCKS, ITEMS, ANY
}
/**
* Private constructor.
*
* @param user the user opening the selector
* @param mode filtering mode (BLOCKS, ITEMS, or ANY)
* @param excluded a set of tags to exclude from display
* @param consumer the callback to receive the selected tags or cancellation
*/
private MultiMaterialTagsSelector(User user, Mode mode, Set<Tag<Material>> excluded,
BiConsumer<Boolean, Collection<Tag<Material>>> consumer) {
super(user, consumer);
this.mode = mode; // Not currently used
this.excluded = excluded;
}
/**
* Opens the material tag selector GUI with a specified mode and exclusions.
*
* @param user the user who opens the GUI
* @param mode the filtering mode (BLOCKS, ITEMS, or ANY)
* @param excluded a set of tags to exclude
* @param consumer a callback to receive the result
*/
public static void open(User user, Mode mode, Set<Tag<Material>> excluded,
BiConsumer<Boolean, Collection<Tag<Material>>> consumer) {
new MultiMaterialTagsSelector(user, mode, excluded, consumer).build();
}
/**
* Opens the material tag selector GUI with default parameters (mode ANY and no exclusions).
*
* @param user the user who opens the GUI
* @param consumer a callback to receive the result
*/
public static void open(User user, BiConsumer<Boolean, Collection<Tag<Material>>> consumer) {
new MultiMaterialTagsSelector(user, Mode.ANY, new HashSet<>(), consumer).build();
}
/**
* Retrieves the list of available material tags.
*
* <p>
* This method obtains tags using Bukkits tag API for the "blocks" category,
* sorts them by their key name, applies several removeIf filters to eliminate irrelevant tags,
* and then removes any tags provided in the excluded set.
* </p>
*
* @return a sorted and filtered list of Tag&lt;Material&gt;
*/
@Override
protected List<Tag<Material>> getElements() {
List<Tag<Material>> list = new ArrayList<>();
Iterable<Tag<Material>> iterable = Bukkit.getTags("blocks", Material.class);
iterable.forEach(list::add);
list.sort(Comparator.comparing(tag -> tag.getKey().getKey()));
// Remove irrelevant tags based on their key.
list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("SPAWNABLE"));
list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("PLACE"));
list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("TEMPT"));
list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("_ON"));
list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("BASE"));
list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("SOUND_BLOCKS"));
list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("DRAGON"));
list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("VALID"));
list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("INCORRECT"));
list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("INFINIBURN"));
list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("MINEABLE"));
list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("TOOL"));
list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("SNIFFER"));
list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("OVERRIDE"));
list.removeIf(t -> t.getKey().getKey().toUpperCase(Locale.ENGLISH).contains("OVERWORLD"));
// Remove specific known tags.
list.remove(Tag.BLOCKS_WIND_CHARGE_EXPLOSIONS);
list.remove(Tag.CONVERTABLE_TO_MUD);
list.remove(Tag.DAMPENS_VIBRATIONS);
list.remove(Tag.DOES_NOT_BLOCK_HOPPERS);
list.remove(Tag.ENCHANTMENT_POWER_PROVIDER);
list.remove(Tag.ENCHANTMENT_POWER_TRANSMITTER);
list.remove(Tag.ENDERMAN_HOLDABLE);
list.remove(Tag.FEATURES_CANNOT_REPLACE);
list.remove(Tag.FALL_DAMAGE_RESETTING);
list.remove(Tag.FROG_PREFER_JUMP_TO);
list.remove(Tag.MAINTAINS_FARMLAND);
list.remove(Tag.MANGROVE_LOGS_CAN_GROW_THROUGH);
list.remove(Tag.MANGROVE_ROOTS_CAN_GROW_THROUGH);
list.remove(Tag.BEE_GROWABLES);
list.remove(Tag.MOB_INTERACTABLE_DOORS);
list.remove(Tag.HOGLIN_REPELLENTS);
list.remove(Tag.PIGLIN_REPELLENTS);
list.remove(Tag.SNAPS_GOAT_HORN);
list.remove(Tag.SOUL_SPEED_BLOCKS);
list.remove(Tag.STRIDER_WARM_BLOCKS);
list.remove(Tag.SWORD_EFFICIENT);
list.remove(Tag.UNSTABLE_BOTTOM_CENTER);
list.remove(Tag.COMPLETES_FIND_TREE_TUTORIAL);
list.remove(Tag.GUARDED_BY_PIGLINS);
list.remove(Tag.IMPERMEABLE);
list.remove(Tag.PREVENT_MOB_SPAWNING_INSIDE);
list.remove(Tag.SMELTS_TO_GLASS);
list.remove(Tag.WITHER_IMMUNE);
// Remove any tags specified in the excluded set.
if (excluded != null) {
for (Tag<Material> ex : excluded) {
list.removeIf(tag -> tag.equals(ex));
}
}
return list;
}
/**
* Returns the title key used for the GUI.
*
* @return "block-selector"
*/
@Override
protected String getTitleKey() {
return "block-selector";
}
/**
* Returns the translation key prefix for individual element buttons.
*
* @return "block-group."
*/
@Override
protected String getElementKeyPrefix() {
return "block-group.";
}
/**
* Returns the icon for the given material tag.
*
* <p>
* This method first checks the ICONS map; if a mapping exists for the tag, that material is used.
* Otherwise, it searches through the Bukkit material registry for any material tagged by the given tag
* that is also an item. If none is found, it falls back to PAPER.
* </p>
*
* @param element the Tag&lt;Material&gt; for which to determine the icon
* @return an ItemStack representing the icon
*/
@Override
protected ItemStack getIcon(Tag<Material> element) {
Material iconMaterial = ICONS.getOrDefault(element, Registry.MATERIAL.stream().filter(element::isTagged)
.filter(Material::isItem).findAny().orElse(Material.PAPER));
return new ItemStack(iconMaterial);
}
/**
* Returns the display name for the given material tag.
*
* @param element the Tag&lt;Material&gt;
* @return a pretty-printed string for display
*/
@Override
protected String getElementDisplayName(Tag<Material> element) {
return Utils.prettifyObject(element, this.user);
}
/**
* Returns a string representation of the tag used for filtering.
*
* @param element the Tag&lt;Material&gt;
* @return the tag's key (as a string)
*/
@Override
protected String elementToString(Tag<Material> element) {
return element.getKey().getKey();
}
}

View File

@ -7,12 +7,13 @@
package world.bentobox.challenges.panel.util;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.builders.PanelBuilder;
import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;

View File

@ -1,12 +1,18 @@
package world.bentobox.challenges.panel.util;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import lv.id.bonne.panelutils.PanelUtils;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.builders.PanelBuilder;

View File

@ -1,7 +1,12 @@
package world.bentobox.challenges.panel.util;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;

View File

@ -1,12 +1,16 @@
package world.bentobox.challenges.panel.util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.inventory.ItemStack;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import lv.id.bonne.panelutils.PanelUtils;
import world.bentobox.bentobox.api.panels.PanelItem;

View File

@ -0,0 +1,192 @@
package world.bentobox.challenges.panel.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import lv.id.bonne.panelutils.PanelUtils;
import world.bentobox.bentobox.BentoBox;
import world.bentobox.bentobox.api.panels.PanelItem;
import world.bentobox.bentobox.api.panels.builders.PanelBuilder;
import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder;
import world.bentobox.bentobox.api.user.User;
import world.bentobox.challenges.utils.Constants;
/**
* Unified abstract class for multiselector GUIs.
* <p>
* This class provides the common logic for building the GUI, filtering the list,
* and creating the functional buttons. Subclasses must supply the list of available elements
* and typespecific details such as how to obtain an elements display name, icon, and
* string representation for filtering.
* </p>
*
* @param <T> The type of element shown in the GUI (e.g. Material, EntityType, or Tag&lt;Material&gt;, etc.)
*/
public abstract class UnifiedMultiSelector<T> extends PagedSelector<T> {
protected final List<T> elements;
protected final Set<T> selectedElements;
protected final BiConsumer<Boolean, Collection<T>> consumer;
protected List<T> filterElements;
protected UnifiedMultiSelector(User user, BiConsumer<Boolean, Collection<T>> consumer) {
super(user);
this.consumer = consumer;
this.selectedElements = new HashSet<>();
// Obtain the complete list of elements from the subclass.
this.elements = getElements();
// Sort elements using the provided string representation.
this.elements.sort(Comparator.comparing(this::elementToString));
// Start with the full list as the filtered list.
this.filterElements = this.elements;
}
/**
* Subclasses must return the complete list of available elements.
*/
protected abstract List<T> getElements();
/**
* Returns the title key (to be appended to Constants.TITLE)
* for this selector (for example, "entity-selector" or "block-selector").
*/
protected abstract String getTitleKey();
/**
* Returns the translation key prefix used for element buttons
* (for example, "entity." or "material.").
*/
protected abstract String getElementKeyPrefix();
/**
* Returns the icon for the given element.
*/
protected abstract ItemStack getIcon(T element);
/**
* Returns the display name for the given element.
* (For instance, by calling Utils.prettifyObject(element, user)).
*/
protected abstract String getElementDisplayName(T element);
/**
* Returns a string representation of the element used for filtering.
* (For enums you might simply return element.name().)
*/
protected abstract String elementToString(T element);
@Override
protected void build() {
PanelBuilder panelBuilder = new PanelBuilder().user(this.user);
panelBuilder.name(this.user.getTranslation(Constants.TITLE + getTitleKey()));
PanelUtils.fillBorder(panelBuilder, Material.BLUE_STAINED_GLASS_PANE);
// Populate the GUI with the filtered list.
this.populateElements(panelBuilder, this.filterElements);
// Add functional buttons.
panelBuilder.item(3, createButton(Button.ACCEPT_SELECTED));
panelBuilder.item(5, createButton(Button.CANCEL));
panelBuilder.build();
}
@Override
protected void updateFilters() {
if (this.searchString == null || this.searchString.isBlank()) {
this.filterElements = this.elements;
} else {
this.filterElements = this.elements.stream()
.filter(element -> elementToString(element).toLowerCase(Locale.ENGLISH)
.contains(this.searchString.toLowerCase(Locale.ENGLISH)))
.distinct().collect(Collectors.toList());
}
}
private PanelItem createButton(Button button) {
final String reference = Constants.BUTTON + button.name().toLowerCase() + ".";
final String name = this.user.getTranslation(reference + "name");
final List<String> description = new ArrayList<>();
description.add(this.user.getTranslation(reference + "description"));
ItemStack icon;
PanelItem.ClickHandler clickHandler;
switch (button) {
case ACCEPT_SELECTED -> {
if (!this.selectedElements.isEmpty()) {
description.add(this.user.getTranslation(reference + "title"));
for (T element : this.selectedElements) {
description.add(this.user.getTranslation(reference + "element", "[element]",
getElementDisplayName(element)));
}
}
icon = new ItemStack(Material.COMMAND_BLOCK);
clickHandler = (panel, user1, clickType, slot) -> {
this.consumer.accept(true, this.selectedElements);
return true;
};
description.add("");
description.add(this.user.getTranslation(Constants.TIPS + "click-to-save"));
}
case CANCEL -> {
icon = new ItemStack(Material.IRON_DOOR);
clickHandler = (panel, user1, clickType, slot) -> {
this.consumer.accept(false, null);
return true;
};
description.add("");
description.add(this.user.getTranslation(Constants.TIPS + "click-to-cancel"));
}
default -> {
icon = new ItemStack(Material.PAPER);
clickHandler = null;
}
}
return new PanelItemBuilder().icon(icon).name(name).description(description).clickHandler(clickHandler).build();
}
@Override
protected PanelItem createElementButton(T element) {
final String reference = Constants.BUTTON + getElementKeyPrefix();
List<String> description = new ArrayList<>();
description.add(this.user.getTranslation(reference + "description", "[id]", elementToString(element)));
if (this.selectedElements.contains(element)) {
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", "[id]",
getElementDisplayName(element)))
.icon(getIcon(element)).description(description).clickHandler((panel, user1, clickType, slot) -> {
// Toggle the selection state.
if (!this.selectedElements.add(element)) {
this.selectedElements.remove(element);
}
this.build();
return true;
}).glow(this.selectedElements.contains(element)).build();
}
protected enum Button {
ACCEPT_SELECTED, CANCEL
}
}

View File

@ -11,6 +11,7 @@ import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.Statistic;
import org.bukkit.Tag;
import org.bukkit.World;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.EntityType;
@ -284,6 +285,25 @@ public class Utils
return "";
}
/**
* Prettify the Tag object for user.
* @param object a tag, like ALL_HANGING_SIGNS
* @param user user
* @return prettified tag
*/
public static String prettifyObject(@Nullable Tag<?> object, User user) {
// Nothing to translate
if (object == null) {
return "";
}
String translation = user.getTranslationOrNothing(
Constants.MATERIALS + object.getKey().getKey().toLowerCase(Locale.ENGLISH) + ".name");
String any = user.getTranslationOrNothing(Constants.MATERIALS + "any");
// Prettify and remove last s
String tag = any + Util.prettifyText(object.getKey().getKey()).replaceAll("s$", "");
return translation.isEmpty() ? tag : translation;
}
/**
* Prettify Material object for user.

View File

@ -1,7 +1,7 @@
name: Challenges
main: world.bentobox.challenges.ChallengesAddon
version: ${version}${build.number}
api-version: 2.7.1
api-version: 3.2.4
repository: 'BentoBoxWorld/Challenges'
metrics: true
@ -9,7 +9,7 @@ authors:
- tastybento
- BONNe
softdepend: AcidIsland, BSkyBlock, CaveBlock, SkyGrid, Level
softdepend: AcidIsland, BSkyBlock, CaveBlock, SkyGrid, Level, Poseidon, Boxed
permissions:
addon.admin.challenges:

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,11 @@ import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.bukkit.Bukkit;
import org.bukkit.Material;