Implement an option to set which item type will ignore metadata per challenge.

Fixes #261
Fixes #252
This commit is contained in:
BONNe 2021-09-24 17:16:33 +03:00
parent 99a3e3f165
commit 08d1561138
9 changed files with 212 additions and 82 deletions

View File

@ -7,12 +7,10 @@
package world.bentobox.challenges.database.object.requirements;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.*;
import java.util.stream.Collectors;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import com.google.gson.annotations.Expose;
@ -81,6 +79,34 @@ public class InventoryRequirements extends Requirements
}
/**
* Gets ignore meta data.
*
* @return the ignore meta data
*/
public Set<Material> getIgnoreMetaData()
{
if (this.ignoreMetaData == null)
{
// Fixes null-pointer, that should not be possible, but may be.
this.ignoreMetaData = new HashSet<>();
}
return this.ignoreMetaData;
}
/**
* Sets ignore meta data.
*
* @param ignoreMetaData the ignore meta data
*/
public void setIgnoreMetaData(Set<Material> ignoreMetaData)
{
this.ignoreMetaData = ignoreMetaData;
}
// ---------------------------------------------------------------------
// Section: Other methods
// ---------------------------------------------------------------------
@ -114,6 +140,7 @@ public class InventoryRequirements extends Requirements
map(ItemStack::clone).
collect(Collectors.toCollection(() -> new ArrayList<>(this.requiredItems.size()))));
clone.setTakeItems(this.takeItems);
clone.setIgnoreMetaData(new HashSet<>(this.ignoreMetaData));
return clone;
}
@ -129,6 +156,12 @@ public class InventoryRequirements extends Requirements
@Expose
private List<ItemStack> requiredItems = new ArrayList<>();
/**
* Set of item stacks that should ignore metadata.
*/
@Expose
private Set<Material> ignoreMetaData = new HashSet<>();
/**
* Boolean that indicate if challenge completion should remove items from inventory.
*/

View File

@ -436,7 +436,7 @@ public abstract class CommonPanel
{
StringBuilder builder = new StringBuilder();
builder.append(this.user.getTranslationOrNothing(reference + "item-title"));
Utils.groupEqualItems(requirement.getRequiredItems()).stream().
Utils.groupEqualItems(requirement.getRequiredItems(), requirement.getIgnoreMetaData()).stream().
sorted(Comparator.comparing(ItemStack::getType)).
forEach(itemStack ->
{
@ -659,7 +659,7 @@ public abstract class CommonPanel
{
StringBuilder builder = new StringBuilder();
builder.append(this.user.getTranslationOrNothing(reference + "item-title"));
Utils.groupEqualItems(challenge.getRepeatItemReward()).stream().
Utils.groupEqualItems(challenge.getRepeatItemReward(), Collections.emptySet()).stream().
sorted(Comparator.comparing(ItemStack::getType)).
forEach(itemStack ->
{
@ -760,7 +760,7 @@ public abstract class CommonPanel
{
StringBuilder builder = new StringBuilder();
builder.append(this.user.getTranslationOrNothing(reference + "item-title"));
Utils.groupEqualItems(challenge.getRewardItems()).stream().
Utils.groupEqualItems(challenge.getRewardItems(), Collections.emptySet()).stream().
sorted(Comparator.comparing(ItemStack::getType)).
forEach(itemStack ->
{
@ -974,7 +974,7 @@ public abstract class CommonPanel
{
StringBuilder builder = new StringBuilder();
builder.append(this.user.getTranslationOrNothing(reference + "item-title"));
Utils.groupEqualItems(level.getRewardItems()).stream().
Utils.groupEqualItems(level.getRewardItems(), Collections.emptySet()).stream().
sorted(Comparator.comparing(ItemStack::getType)).
forEach(itemStack ->
{

View File

@ -4,6 +4,7 @@ package world.bentobox.challenges.panel.admin;
import java.time.Duration;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.bukkit.Material;
import org.bukkit.World;
@ -207,6 +208,16 @@ public class EditChallengePanel extends CommonPanel
panelBuilder.item(10, this.createRequirementButton(RequirementButton.REQUIRED_ITEMS));
panelBuilder.item(19, this.createRequirementButton(RequirementButton.REMOVE_ITEMS));
if (!this.challenge.<InventoryRequirements>getRequirements().getRequiredItems().isEmpty())
{
panelBuilder.item(12, this.createRequirementButton(RequirementButton.ADD_IGNORED_META));
if (!this.challenge.<InventoryRequirements>getRequirements().getIgnoreMetaData().isEmpty())
{
panelBuilder.item(21, this.createRequirementButton(RequirementButton.REMOVE_IGNORED_META));
}
}
panelBuilder.item(25, this.createRequirementButton(RequirementButton.REQUIRED_PERMISSIONS));
}
@ -683,7 +694,7 @@ public class EditChallengePanel extends CommonPanel
return this.createIslandRequirementButton(button);
}
// Buttons for Inventory Requirements
case REQUIRED_ITEMS, REMOVE_ITEMS -> {
case REQUIRED_ITEMS, REMOVE_ITEMS, ADD_IGNORED_META, REMOVE_IGNORED_META -> {
return this.createInventoryRequirementButton(button);
}
// Buttons for Other Requirements
@ -882,7 +893,8 @@ public class EditChallengePanel extends CommonPanel
{
description.add(this.user.getTranslation(reference + "title"));
Utils.groupEqualItems(requirements.getRequiredItems()).stream().
Utils.groupEqualItems(requirements.getRequiredItems(), requirements.getIgnoreMetaData()).
stream().
sorted(Comparator.comparing(ItemStack::getType)).
forEach(itemStack ->
description.add(this.user.getTranslationOrNothing(reference + "list",
@ -923,6 +935,90 @@ public class EditChallengePanel extends CommonPanel
description.add("");
description.add(this.user.getTranslation(Constants.TIPS + "click-to-toggle"));
}
case ADD_IGNORED_META -> {
if (requirements.getIgnoreMetaData().isEmpty())
{
description.add(this.user.getTranslation(reference + "none"));
}
else
{
description.add(this.user.getTranslation(reference + "title"));
requirements.getIgnoreMetaData().stream().
sorted(Comparator.comparing(Material::name)).
forEach(itemStack ->
description.add(this.user.getTranslationOrNothing(reference + "list",
"[item]", Utils.prettifyObject(itemStack, this.user))));
}
icon = new ItemStack(Material.GREEN_SHULKER_BOX);
clickHandler = (panel, user, clickType, slot) -> {
if (requirements.getRequiredItems().isEmpty())
{
// Do nothing if no requirements are set.
return true;
}
// Allow choosing only from inventory items.
Set<Material> collection = Arrays.stream(Material.values()).collect(Collectors.toSet());
requirements.getRequiredItems().stream().
map(ItemStack::getType).
forEach(collection::remove);
collection.addAll(requirements.getIgnoreMetaData());
MultiBlockSelector.open(this.user,
MultiBlockSelector.Mode.ANY,
collection,
(status, materials) ->
{
if (status)
{
requirements.setIgnoreMetaData(new HashSet<>(materials));
}
this.build();
});
return true;
};
glow = false;
description.add("");
description.add(this.user.getTranslation(Constants.TIPS + "click-to-add"));
}
case REMOVE_IGNORED_META -> {
icon = new ItemStack(Material.RED_SHULKER_BOX);
clickHandler = (panel, user, clickType, slot) -> {
if (requirements.getIgnoreMetaData().isEmpty())
{
// Do nothing if no requirements are set.
return true;
}
// Allow choosing only from inventory items.
Set<Material> collection = Arrays.stream(Material.values()).collect(Collectors.toSet());
collection.removeAll(requirements.getIgnoreMetaData());
MultiBlockSelector.open(this.user,
MultiBlockSelector.Mode.ANY,
collection,
(status, materials) ->
{
if (status)
{
requirements.getIgnoreMetaData().removeAll(materials);
}
this.build();
});
return true;
};
glow = false;
description.add("");
description.add(this.user.getTranslation(Constants.TIPS + "click-to-remove"));
}
default -> {
icon = new ItemStack(Material.PAPER);
clickHandler = null;
@ -1346,7 +1442,8 @@ public class EditChallengePanel extends CommonPanel
{
description.add(this.user.getTranslation(reference + "title"));
Utils.groupEqualItems(this.challenge.getRewardItems()).stream().
Utils.groupEqualItems(this.challenge.getRewardItems(), Collections.emptySet()).
stream().
sorted(Comparator.comparing(ItemStack::getType)).
forEach(itemStack ->
description.add(this.user.getTranslationOrNothing(reference + "list",
@ -1597,7 +1694,8 @@ public class EditChallengePanel extends CommonPanel
{
description.add(this.user.getTranslation(reference + "title"));
Utils.groupEqualItems(this.challenge.getRepeatItemReward()).stream().
Utils.groupEqualItems(this.challenge.getRepeatItemReward(), Collections.emptySet()).
stream().
sorted(Comparator.comparing(ItemStack::getType)).
forEach(itemStack ->
description.add(this.user.getTranslationOrNothing(reference + "list",
@ -1869,6 +1967,8 @@ public class EditChallengePanel extends CommonPanel
REQUIRED_PERMISSIONS,
REQUIRED_ITEMS,
REMOVE_ITEMS,
ADD_IGNORED_META,
REMOVE_IGNORED_META,
REQUIRED_EXPERIENCE,
REMOVE_EXPERIENCE,
REQUIRED_LEVEL,

View File

@ -387,7 +387,8 @@ public class EditLevelPanel extends CommonPagedPanel<Challenge>
{
description.add(this.user.getTranslation(reference + "title"));
Utils.groupEqualItems(this.challengeLevel.getRewardItems()).stream().
Utils.groupEqualItems(this.challengeLevel.getRewardItems(), Collections.emptySet()).
stream().
sorted(Comparator.comparing(ItemStack::getType)).
forEach(itemStack ->
description.add(this.user.getTranslationOrNothing(reference + "list",

View File

@ -888,30 +888,29 @@ public class TryToComplete
// Players in creative game mode has got all items. No point to search for them.
if (this.user.getPlayer().getGameMode() != GameMode.CREATIVE)
{
requiredItems = Utils.groupEqualItems(this.getInventoryRequirements().getRequiredItems());
requiredItems = Utils.groupEqualItems(this.getInventoryRequirements().getRequiredItems(),
this.getInventoryRequirements().getIgnoreMetaData());
// Check if all required items are in players inventory.
for (ItemStack required : requiredItems)
{
int numInInventory;
if (Utils.canIgnoreMeta(required.getType()))
if (this.getInventoryRequirements().getIgnoreMetaData().contains(required.getType()))
{
numInInventory =
Arrays.stream(this.user.getInventory().getContents()).
filter(Objects::nonNull).
filter(i -> i.getType().equals(required.getType())).
mapToInt(ItemStack::getAmount).
sum();
numInInventory = Arrays.stream(this.user.getInventory().getContents()).
filter(Objects::nonNull).
filter(i -> i.getType().equals(required.getType())).
mapToInt(ItemStack::getAmount).
sum();
}
else
{
numInInventory =
Arrays.stream(this.user.getInventory().getContents()).
filter(Objects::nonNull).
filter(i -> i.isSimilar(required)).
mapToInt(ItemStack::getAmount).
sum();
numInInventory = Arrays.stream(this.user.getInventory().getContents()).
filter(Objects::nonNull).
filter(i -> i.isSimilar(required)).
mapToInt(ItemStack::getAmount).
sum();
}
if (numInInventory < required.getAmount())
@ -952,22 +951,23 @@ public class TryToComplete
int amountToBeRemoved = required.getAmount() * factor;
List<ItemStack> itemsInInventory;
if (Utils.canIgnoreMeta(required.getType()))
if (this.getInventoryRequirements().getIgnoreMetaData().contains(required.getType()))
{
// Use collecting method that ignores item meta.
itemsInInventory = Arrays.stream(user.getInventory().getContents()).
filter(Objects::nonNull).
filter(i -> i.getType().equals(required.getType())).
collect(Collectors.toList());
filter(Objects::nonNull).
filter(i -> i.getType().equals(required.getType())).
collect(Collectors.toList());
}
else
{
// Use collecting method that compares item meta.
itemsInInventory = Arrays.stream(user.getInventory().getContents()).
filter(Objects::nonNull).
filter(i -> i.isSimilar(required)).
collect(Collectors.toList());
filter(Objects::nonNull).
filter(i -> i.isSimilar(required)).
collect(Collectors.toList());
}
for (ItemStack itemStack : itemsInInventory)
{
if (amountToBeRemoved > 0)

View File

@ -5,6 +5,7 @@ import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.bukkit.ChatColor;
import org.bukkit.Material;
@ -15,9 +16,7 @@ import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.*;
import org.bukkit.potion.PotionData;
import org.bukkit.potion.PotionEffect;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.potion.PotionType;
import org.eclipse.jdt.annotation.Nullable;
import world.bentobox.bentobox.BentoBox;
@ -37,7 +36,7 @@ public class Utils
* @param requiredItems Input item list
* @return List that contains unique items that cannot be grouped.
*/
public static List<ItemStack> groupEqualItems(List<ItemStack> requiredItems)
public static List<ItemStack> groupEqualItems(List<ItemStack> requiredItems, Set<Material> ignoreMetaData)
{
List<ItemStack> returnItems = new ArrayList<>(requiredItems.size());
@ -55,8 +54,7 @@ public class Utils
ItemStack required = returnItems.get(i);
// Merge items which meta can be ignored or is similar to item in required list.
if (Utils.canIgnoreMeta(item.getType()) && item.getType().equals(required.getType()) ||
required.isSimilar(item))
if (ignoreMetaData.contains(item.getType()) && item.getType().equals(required.getType()))
{
required.setAmount(required.getAmount() + item.getAmount());
isUnique = false;
@ -76,29 +74,6 @@ public class Utils
}
/**
* This method returns if meta data of these items can be ignored. It means, that items will be searched
* and merged by they type instead of using ItemStack#isSimilar(ItemStack) method.
*
* This limits custom Challenges a lot. It comes from ASkyBlock times, and that is the reason why it is
* still here. It would be a great Challenge that could be completed by collecting 4 books, that cannot
* be crafted. Unfortunately, this prevents it.
* The same happens with firework rockets, enchanted books and filled maps.
* In future it should be able to specify, which items meta should be ignored when adding item in required
* item list.
*
* @param material Material that need to be checked.
* @return True if material meta can be ignored, otherwise false.
*/
public static boolean canIgnoreMeta(Material material)
{
return material.equals(Material.FIREWORK_ROCKET) ||
material.equals(Material.ENCHANTED_BOOK) ||
material.equals(Material.WRITTEN_BOOK) ||
material.equals(Material.FILLED_MAP);
}
/**
* This method transforms given World into GameMode name. If world is not a GameMode
* world then it returns null.

View File

@ -330,6 +330,23 @@ challenges:
title: "&7 Items: "
list: " &8 - [item]"
none: "&7 Items are not added."
add_ignored_meta:
name: "&f&l Add Ignore Metadata"
description: |-
&7 Allows to add which
&7 items should ignore
&7 any meta-data that
&7 is assigned to them.
title: "&7 Items: "
list: " &8 - [item]"
none: "&7 Items are not added."
remove_ignored_meta:
name: "&f&l Remove Ignore Metadata"
description: |-
&7 Allows to remove which
&7 items should ignore
&7 any meta-data that
&7 is assigned to them.
remove_experience:
name: "&f&l Remove Experience"
description: |-

View File

@ -325,7 +325,24 @@ challenges:
&7 uzdevuma izpildei.
title: "&7 Priekšmeti: "
list: " &8 - [item]"
none: "&7 Priekšmeti nav uzstādītas."
none: "&7 Priekšmeti nav uzstādīti."
add_ignored_meta:
name: "&f&l Pievienot Meta-datu Ignorēšanu"
description: |-
&7 Ļauj uzstādīt, kuriem
&7 priekšmetiem drīkst
&7 ignorēt meta-datus, kā
&7 bojājumi, vārdi, uzlabojumi.
title: "&7 Priekšmeti: "
list: " &8 - [item]"
none: "&7 Priekšmeti nav uzstādīti."
remove_ignored_meta:
name: "&f&l Noņemt Meta-datu Ignorēšanu"
description: |-
&7 Ļauj noņemt, kuriem
&7 priekšmetiem drīkst
&7 ignorēt meta-datus, kā
&7 bojājumi, vārdi, uzlabojumi.
remove_experience:
name: "&f&l Noņemt Pieredzi"
description: |-

View File

@ -82,15 +82,15 @@ public class UtilsTest {
}
/**
* Test method for {@link world.bentobox.challenges.utils.Utils#groupEqualItems(java.util.List)}.
* Test method for {@link world.bentobox.challenges.utils.Utils#groupEqualItems(java.util.List, java.util.Set)}.
*/
@Test
public void testGroupEqualItemsEmpty() {
assertTrue(Utils.groupEqualItems(Collections.emptyList()).isEmpty());
assertTrue(Utils.groupEqualItems(Collections.emptyList(), Collections.emptySet()).isEmpty());
}
/**
* Test method for {@link world.bentobox.challenges.utils.Utils#groupEqualItems(java.util.List)}.
* Test method for {@link world.bentobox.challenges.utils.Utils#groupEqualItems(java.util.List, java.util.Set)}.
*/
@Test
public void testGroupEqualItems() {
@ -112,14 +112,14 @@ public class UtilsTest {
when(is2.clone()).thenReturn(is);
requiredItems.add(is2);
}
List<ItemStack> list = Utils.groupEqualItems(requiredItems);
List<ItemStack> list = Utils.groupEqualItems(requiredItems, Collections.emptySet());
// Result should be two stacks stack of 64 doors and 36 doors
assertEquals(1, list.size());
verify(is, times(9)).setAmount(2);
}
/**
* Test method for {@link world.bentobox.challenges.utils.Utils#groupEqualItems(java.util.List)}.
* Test method for {@link world.bentobox.challenges.utils.Utils#groupEqualItems(java.util.List, java.util.Set)}.
*/
@Test
public void testGroupEqualItemsUnique() {
@ -141,24 +141,12 @@ public class UtilsTest {
when(is2.clone()).thenReturn(is);
requiredItems.add(is2);
}
List<ItemStack> list = Utils.groupEqualItems(requiredItems);
List<ItemStack> list = Utils.groupEqualItems(requiredItems, Collections.emptySet());
// Result should be two stacks stack of 64 doors and 36 doors
assertEquals(10, list.size());
verify(is, never()).setAmount(2);
}
/**
* Test method for {@link world.bentobox.challenges.utils.Utils#canIgnoreMeta(org.bukkit.Material)}.
*/
@Test
public void testCanIgnoreMeta() {
assertTrue(Utils.canIgnoreMeta(Material.FIREWORK_ROCKET));
assertTrue(Utils.canIgnoreMeta(Material.ENCHANTED_BOOK));
assertTrue(Utils.canIgnoreMeta(Material.WRITTEN_BOOK));
assertTrue(Utils.canIgnoreMeta(Material.FILLED_MAP));
assertFalse(Utils.canIgnoreMeta(Material.CHISELED_RED_SANDSTONE));
}
/**
* Test method for {@link world.bentobox.challenges.utils.Utils#getGameMode(org.bukkit.World)}.
*/
@ -195,5 +183,4 @@ public class UtilsTest {
assertEquals(VisibilityMode.VISIBLE, Utils.getPreviousValue(VisibilityMode.values(), VisibilityMode.HIDDEN));
assertEquals(VisibilityMode.HIDDEN, Utils.getPreviousValue(VisibilityMode.values(), VisibilityMode.TOGGLEABLE));
}
}