From c63087c5af4dd03e2caa582e58d7ad3c23444ca6 Mon Sep 17 00:00:00 2001 From: BONNe Date: Sat, 14 Aug 2021 19:25:04 +0300 Subject: [PATCH] Create Statistic Requirement for Challenges addon. Statistic requirement is a new type of challenge that is based on Statistic page for clients. --- .../challenges/ChallengesManager.java | 98 +++++ .../challenges/database/object/Challenge.java | 5 + .../requirements/StatisticRequirements.java | 223 +++++++++++ .../bentobox/challenges/panel/CommonGUI.java | 62 ++- .../panel/admin/EditChallengeGUI.java | 353 ++++++++++++++---- .../panel/util/ChallengeTypeGUI.java | 13 +- .../panel/util/SelectStatisticGUI.java | 168 +++++++++ .../challenges/tasks/TryToComplete.java | 256 +++++++++++-- src/main/resources/locales/en-US.yml | 26 ++ src/main/resources/plugin.yml | 2 +- 10 files changed, 1090 insertions(+), 116 deletions(-) create mode 100644 src/main/java/world/bentobox/challenges/database/object/requirements/StatisticRequirements.java create mode 100644 src/main/java/world/bentobox/challenges/panel/util/SelectStatisticGUI.java diff --git a/src/main/java/world/bentobox/challenges/ChallengesManager.java b/src/main/java/world/bentobox/challenges/ChallengesManager.java index 6624dc5..20018ed 100644 --- a/src/main/java/world/bentobox/challenges/ChallengesManager.java +++ b/src/main/java/world/bentobox/challenges/ChallengesManager.java @@ -17,7 +17,10 @@ import java.util.UUID; import java.util.stream.Collectors; import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Statistic; import org.bukkit.World; +import org.bukkit.entity.EntityType; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; @@ -1287,6 +1290,101 @@ public class ChallengesManager // --------------------------------------------------------------------- + /** + * Gets statistic data. + * + * @param user the user + * @param world the world + * @param statistic the statistic + * @return the statistic data + */ + public int getStatisticData(User user, World world, Statistic statistic) + { + if (this.settings.isStoreAsIslandData()) + { + Island island = this.addon.getIslands().getIsland(world, user); + + if (island == null) + { + return 0; + } + + return island.getMemberSet().stream().map(Bukkit::getPlayer). + filter(Objects::nonNull). + mapToInt(player -> player.getStatistic(statistic)). + sum(); + } + else + { + return user.getPlayer().getStatistic(statistic); + } + } + + + /** + * Gets statistic data. + * + * @param user the user + * @param world the world + * @param statistic the statistic + * @param material the material + * @return the statistic data + */ + public int getStatisticData(User user, World world, Statistic statistic, Material material) + { + if (this.settings.isStoreAsIslandData()) + { + Island island = this.addon.getIslands().getIsland(world, user); + + if (island == null) + { + return 0; + } + + return island.getMemberSet().stream().map(Bukkit::getPlayer). + filter(Objects::nonNull). + mapToInt(player -> player.getStatistic(statistic, material)). + sum(); + } + else + { + return user.getPlayer().getStatistic(statistic, material); + } + } + + + /** + * Gets statistic data. + * + * @param user the user + * @param world the world + * @param statistic the statistic + * @param entity the entity + * @return the statistic data + */ + public int getStatisticData(User user, World world, Statistic statistic, EntityType entity) + { + if (this.settings.isStoreAsIslandData()) + { + Island island = this.addon.getIslands().getIsland(world, user); + + if (island == null) + { + return 0; + } + + return island.getMemberSet().stream().map(Bukkit::getPlayer). + filter(Objects::nonNull). + mapToInt(player -> player.getStatistic(statistic, entity)). + sum(); + } + else + { + return user.getPlayer().getStatistic(statistic, entity); + } + } + + /** * This method returns if given user has completed given challenge in world. * @param user - User that must be checked. diff --git a/src/main/java/world/bentobox/challenges/database/object/Challenge.java b/src/main/java/world/bentobox/challenges/database/object/Challenge.java index fb14d8a..f80959a 100644 --- a/src/main/java/world/bentobox/challenges/database/object/Challenge.java +++ b/src/main/java/world/bentobox/challenges/database/object/Challenge.java @@ -57,6 +57,11 @@ public class Challenge implements DataObject * other plugins to be setup before it could work. */ OTHER, + + /** + * Challenge based on player statistic data. + */ + STATISTIC } diff --git a/src/main/java/world/bentobox/challenges/database/object/requirements/StatisticRequirements.java b/src/main/java/world/bentobox/challenges/database/object/requirements/StatisticRequirements.java new file mode 100644 index 0000000..e676164 --- /dev/null +++ b/src/main/java/world/bentobox/challenges/database/object/requirements/StatisticRequirements.java @@ -0,0 +1,223 @@ +// +// Created by BONNe +// Copyright - 2021 +// + + +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; + + +public class StatisticRequirements extends Requirements +{ + /** + * Constructor Requirements creates a new Requirements instance. + */ + public StatisticRequirements() + { + // Empty constructor + } + + + /** + * This method clones given statistic object. + * @return Clone of this object. + */ + @Override + public Requirements clone() + { + StatisticRequirements requirements = new StatisticRequirements(); + requirements.setStatistic(this.statistic); + requirements.setEntity(this.entity); + requirements.setMaterial(this.material); + requirements.setAmount(this.amount); + requirements.setReduceStatistic(this.reduceStatistic); + + return requirements; + } + + + @Override + public boolean isValid() + { + if (!super.isValid()) + { + return false; + } + + if (this.statistic == null) + { + return false; + } + + switch (this.statistic.getType()) + { + case ITEM -> { + return this.material != null && this.material.isItem(); + } + case BLOCK -> { + return this.material != null && this.material.isBlock(); + } + case ENTITY -> { + return this.entity != null; + } + } + + return true; + } + + + // --------------------------------------------------------------------- +// Section: Getters and setters +// --------------------------------------------------------------------- + + + /** + * Gets statistic. + * + * @return the statistic + */ + public Statistic getStatistic() + { + return statistic; + } + + + /** + * Sets statistic. + * + * @param statistic the statistic + */ + public void setStatistic(Statistic statistic) + { + this.statistic = statistic; + } + + + /** + * Gets entity. + * + * @return the entity + */ + public EntityType getEntity() + { + return entity; + } + + + /** + * Sets entity. + * + * @param entity the entity + */ + public void setEntity(EntityType entity) + { + this.entity = entity; + } + + + /** + * Gets material. + * + * @return the material + */ + public Material getMaterial() + { + return material; + } + + + /** + * Sets material. + * + * @param material the material + */ + public void setMaterial(Material material) + { + this.material = material; + } + + + /** + * Gets amount. + * + * @return the amount + */ + public int getAmount() + { + return amount; + } + + + /** + * Sets amount. + * + * @param amount the amount + */ + public void setAmount(int amount) + { + this.amount = amount; + } + + + /** + * Is reduce statistic boolean. + * + * @return the boolean + */ + public boolean isReduceStatistic() + { + return reduceStatistic; + } + + + /** + * Sets reduce statistic. + * + * @param reduceStatistic the reduce statistic + */ + public void setReduceStatistic(boolean reduceStatistic) + { + this.reduceStatistic = reduceStatistic; + } + + +// --------------------------------------------------------------------- +// Section: Variables +// --------------------------------------------------------------------- + + /** + * Type of the statistic field. + */ + @Expose + private Statistic statistic; + + /** + * Type of entity for entity related statistics. + */ + @Expose + private EntityType entity; + + /** + * Type of material for block and item related statistics. + */ + @Expose + private Material material; + + /** + * Amount of the stats. + */ + @Expose + private int amount; + + /** + * Indicate that player statistic fields must be adjusted after completing challenges. + */ + @Expose + private boolean reduceStatistic; +} diff --git a/src/main/java/world/bentobox/challenges/panel/CommonGUI.java b/src/main/java/world/bentobox/challenges/panel/CommonGUI.java index e3d69af..2719036 100644 --- a/src/main/java/world/bentobox/challenges/panel/CommonGUI.java +++ b/src/main/java/world/bentobox/challenges/panel/CommonGUI.java @@ -44,6 +44,8 @@ import world.bentobox.challenges.database.object.ChallengeLevel; 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.StatisticRequirements; +import world.bentobox.challenges.utils.GuiUtils; import world.bentobox.challenges.utils.LevelStatus; import world.bentobox.challenges.utils.Utils; @@ -488,15 +490,18 @@ public abstract class CommonGUI { switch (challenge.getChallengeType()) { - case INVENTORY: - result.addAll(this.getInventoryRequirements(challenge.getRequirements())); - break; - case ISLAND: - result.addAll(this.getIslandRequirements(challenge.getRequirements())); - break; - case OTHER: - result.addAll(this.getOtherRequirements(challenge.getRequirements())); - break; + case INVENTORY: + result.addAll(this.getInventoryRequirements(challenge.getRequirements())); + break; + case ISLAND: + result.addAll(this.getIslandRequirements(challenge.getRequirements())); + break; + case OTHER: + result.addAll(this.getOtherRequirements(challenge.getRequirements())); + break; + case STATISTIC: + result.addAll(this.getStatisticRequirements(challenge.getRequirements())); + break; } } @@ -727,6 +732,45 @@ public abstract class CommonGUI } + /** + * This method returns list of strings that contains basic information about requirements. + * @param requirements which requirements message must be created. + * @return list of strings that contains requirements message. + */ + private List getStatisticRequirements(StatisticRequirements requirements) + { + List result = new ArrayList<>(); + + result.add(this.user.getTranslation("challenges.gui.challenge-description.required-stats", + "[stat]", GuiUtils.sanitizeInput(requirements.getStatistic().name()))); + + switch (requirements.getStatistic().getType()) + { + case ITEM -> { + result.add(this.user.getTranslation("challenges.gui.challenge-description.stat-item", + "[material]", GuiUtils.sanitizeInput(requirements.getMaterial().name()), + "[amount]", String.valueOf(requirements.getAmount()))); + } + case BLOCK -> { + result.add(this.user.getTranslation("challenges.gui.challenge-description.stat-block", + "[material]", GuiUtils.sanitizeInput(requirements.getMaterial().name()), + "[amount]", String.valueOf(requirements.getAmount()))); + } + case ENTITY -> { + result.add(this.user.getTranslation("challenges.gui.challenge-description.stat-entity", + "[entity]", GuiUtils.sanitizeInput(requirements.getEntity().name()), + "[amount]", String.valueOf(requirements.getAmount()))); + } + case UNTYPED -> { + result.add(this.user.getTranslation("challenges.gui.challenge-description.stat-amount", + "[amount]", String.valueOf(requirements.getAmount()))); + } + } + + return result; + } + + /** * This method returns list of strings that contains required items, entities and blocks from given challenge. * @param challenge Challenge which requirement items, entities and blocks must be returned. diff --git a/src/main/java/world/bentobox/challenges/panel/admin/EditChallengeGUI.java b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengeGUI.java index 837e7ee..c6f4663 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/EditChallengeGUI.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/EditChallengeGUI.java @@ -23,12 +23,9 @@ import world.bentobox.challenges.database.object.Challenge; 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.StatisticRequirements; import world.bentobox.challenges.panel.CommonGUI; -import world.bentobox.challenges.panel.util.ItemSwitchGUI; -import world.bentobox.challenges.panel.util.NumberGUI; -import world.bentobox.challenges.panel.util.SelectBlocksGUI; -import world.bentobox.challenges.panel.util.SelectEnvironmentGUI; -import world.bentobox.challenges.panel.util.StringListGUI; +import world.bentobox.challenges.panel.util.*; import world.bentobox.challenges.utils.GuiUtils; import world.bentobox.challenges.utils.Utils; @@ -118,15 +115,10 @@ public class EditChallengeGUI extends CommonGUI { switch (this.challenge.getChallengeType()) { - case INVENTORY: - this.buildInventoryRequirementsPanel(panelBuilder); - break; - case ISLAND: - this.buildIslandRequirementsPanel(panelBuilder); - break; - case OTHER: - this.buildOtherRequirementsPanel(panelBuilder); - break; + case INVENTORY -> this.buildInventoryRequirementsPanel(panelBuilder); + case ISLAND -> this.buildIslandRequirementsPanel(panelBuilder); + case OTHER -> this.buildOtherRequirementsPanel(panelBuilder); + case STATISTIC -> this.buildStatisticRequirementsPanel(panelBuilder); } } else if (this.currentMenuType.equals(MenuType.REWARDS)) @@ -139,6 +131,8 @@ public class EditChallengeGUI extends CommonGUI // Every time when this GUI is build, save challenge // This will ensure that all main things will be always stored this.addon.getChallengesManager().saveChallenge(this.challenge); + // If for some reason challenge is not loaded, do it. + this.addon.getChallengesManager().loadChallenge(this.challenge, false, null, true); panelBuilder.build(); } @@ -210,6 +204,33 @@ public class EditChallengeGUI extends CommonGUI } + /** + * This class populates ChallengesEditGUI with other challenges requirement elements. + * @param panelBuilder PanelBuilder where icons must be added. + */ + private void buildStatisticRequirementsPanel(PanelBuilder panelBuilder) + { + panelBuilder.item(10, this.createRequirementButton(RequirementButton.STATISTIC)); + panelBuilder.item(19, this.createRequirementButton(RequirementButton.REMOVE_STATISTIC)); + + panelBuilder.item(11, this.createRequirementButton(RequirementButton.STATISTIC_AMOUNT)); + + StatisticRequirements requirements = this.challenge.getRequirements(); + + if (requirements.getStatistic() != null) + { + switch (requirements.getStatistic().getType()) + { + case ITEM -> panelBuilder.item(13, this.createRequirementButton(RequirementButton.STATISTIC_ITEMS)); + case BLOCK -> panelBuilder.item(13, this.createRequirementButton(RequirementButton.STATISTIC_BLOCKS)); + case ENTITY -> panelBuilder.item(13, this.createRequirementButton(RequirementButton.STATISTIC_ENTITIES)); + } + } + + panelBuilder.item(25, this.createRequirementButton(RequirementButton.REQUIRED_PERMISSIONS)); + } + + /** * This class populates ChallengesEditGUI with challenges reward elements. * @param panelBuilder PanelBuilder where icons must be added. @@ -344,7 +365,16 @@ public class EditChallengeGUI extends CommonGUI icon = new ItemStack(Material.LEVER); clickHandler = (panel, user, clickType, slot) -> { - this.challenge.setDeployed(!this.challenge.isDeployed()); + if (this.challenge.isValid()) + { + this.challenge.setDeployed(!this.challenge.isDeployed()); + } + else + { + this.user.sendMessage("challenges.errors.invalid-challenge", + "[challenge]", this.challenge.getFriendlyName()); + this.challenge.setDeployed(false); + } this.build(); return true; @@ -547,24 +577,25 @@ public class EditChallengeGUI extends CommonGUI switch (button) { - case REQUIRED_PERMISSIONS: - { - name = this.user.getTranslation("challenges.gui.buttons.admin.required-permissions"); - description = new ArrayList<>(this.challenge.getRequirements().getRequiredPermissions().size() + 1); - description.add(this.user.getTranslation("challenges.gui.descriptions.admin.required-permissions")); + case REQUIRED_PERMISSIONS -> { + name = this.user.getTranslation("challenges.gui.buttons.admin.required-permissions"); + description = new ArrayList<>(this.challenge.getRequirements().getRequiredPermissions().size() + 1); + description.add(this.user.getTranslation("challenges.gui.descriptions.admin.required-permissions")); - for (String permission : this.challenge.getRequirements().getRequiredPermissions()) - { - description.add(this.user.getTranslation("challenges.gui.descriptions.permission", + for (String permission : this.challenge.getRequirements().getRequiredPermissions()) + { + description.add(this.user.getTranslation("challenges.gui.descriptions.permission", "[permission]", permission)); - } + } - icon = new ItemStack(Material.REDSTONE_LAMP); - clickHandler = (panel, user, clickType, slot) -> { - new StringListGUI(this.user, + icon = new ItemStack(Material.REDSTONE_LAMP); + clickHandler = (panel, user, clickType, slot) -> + { + new StringListGUI(this.user, this.challenge.getRequirements().getRequiredPermissions(), lineLength, - (status, value) -> { + (status, value) -> + { if (status) { this.challenge.getRequirements().setRequiredPermissions(new HashSet<>(value)); @@ -573,47 +604,39 @@ public class EditChallengeGUI extends CommonGUI this.build(); }); - return true; - }; - glow = false; - break; - } - - case REQUIRED_ENTITIES: - case REMOVE_ENTITIES: - case REQUIRED_BLOCKS: - case REMOVE_BLOCKS: - case SEARCH_RADIUS: - { - return this.createIslandRequirementButton(button); - } - - case REQUIRED_ITEMS: - case REMOVE_ITEMS: - { - return this.createInventoryRequirementButton(button); - } - - case REQUIRED_EXPERIENCE: - case REMOVE_EXPERIENCE: - case REQUIRED_LEVEL: - case REQUIRED_MONEY: - case REMOVE_MONEY: - { - return this.createOtherRequirementButton(button); - } - - default: - return null; + return true; + }; + glow = false; + } + // Buttons for Island Requirements + case REQUIRED_ENTITIES, REMOVE_ENTITIES, REQUIRED_BLOCKS, REMOVE_BLOCKS, SEARCH_RADIUS -> { + return this.createIslandRequirementButton(button); + } + // Buttons for Inventory Requirements + case REQUIRED_ITEMS, REMOVE_ITEMS -> { + return this.createInventoryRequirementButton(button); + } + // Buttons for Other Requirements + case REQUIRED_EXPERIENCE, REMOVE_EXPERIENCE, REQUIRED_LEVEL, REQUIRED_MONEY, REMOVE_MONEY -> { + return this.createOtherRequirementButton(button); + } + // Buttons for Statistic Requirements + case STATISTIC, STATISTIC_BLOCKS, STATISTIC_ITEMS, STATISTIC_ENTITIES, STATISTIC_AMOUNT, REMOVE_STATISTIC -> { + return this.createStatisticRequirementButton(button); + } + // Default behaviour. + default -> { + return null; + } } return new PanelItemBuilder(). - icon(icon). - name(name). - description(GuiUtils.stringSplit(description, this.lineLength)). - glow(glow). - clickHandler(clickHandler). - build(); + icon(icon). + name(name). + description(GuiUtils.stringSplit(description, this.lineLength)). + glow(glow). + clickHandler(clickHandler). + build(); } @@ -1023,6 +1046,198 @@ public class EditChallengeGUI extends CommonGUI } + /** + * Creates a button for statistic requirements. + * @param button Button that must be created. + * @return PanelItem button. + */ + private PanelItem createStatisticRequirementButton(RequirementButton button) + { + ItemStack icon; + String name; + List description; + boolean glow; + PanelItem.ClickHandler clickHandler; + + final StatisticRequirements requirements = this.challenge.getRequirements(); + + switch (button) + { + case STATISTIC: + { + name = this.user.getTranslation("challenges.gui.buttons.admin.required-statistic"); + description = new ArrayList<>(2); + description.add(this.user.getTranslation("challenges.gui.descriptions.admin.required-statistic")); + description.add(this.user.getTranslation("challenges.gui.descriptions.current-value", + "[value]", String.valueOf(requirements.getStatistic()))); + + icon = new ItemStack(Material.PAPER); + clickHandler = (panel, user, clickType, slot) -> { + new SelectStatisticGUI(this.user, (status, statistic) -> { + if (status) + { + requirements.setStatistic(statistic); + } + + this.build(); + }); + return true; + }; + glow = false; + break; + } + case STATISTIC_AMOUNT: + { + name = this.user.getTranslation("challenges.gui.buttons.admin.required-amount"); + description = new ArrayList<>(2); + description.add(this.user.getTranslation("challenges.gui.descriptions.admin.required-amount")); + description.add(this.user.getTranslation("challenges.gui.descriptions.current-value", + "[value]", Integer.toString(requirements.getAmount()))); + + icon = new ItemStack(Material.CHEST); + clickHandler = (panel, user, clickType, slot) -> { + new NumberGUI(this.user, + requirements.getAmount(), + 0, + this.lineLength, + (status, value) -> { + if (status) + { + requirements.setAmount(value); + } + + this.build(); + }); + return true; + }; + glow = false; + break; + } + case REMOVE_STATISTIC: + { + name = this.user.getTranslation("challenges.gui.buttons.admin.remove-statistic"); + description = new ArrayList<>(2); + description.add(this.user.getTranslation("challenges.gui.descriptions.admin.remove-statistic")); + description.add(this.user.getTranslation("challenges.gui.descriptions.current-value", + "[value]", + requirements.isReduceStatistic() ? + this.user.getTranslation("challenges.gui.descriptions.enabled") : + this.user.getTranslation("challenges.gui.descriptions.disabled"))); + + icon = new ItemStack(Material.LEVER); + clickHandler = (panel, user, clickType, slot) -> { + requirements.setReduceStatistic(!requirements.isReduceStatistic()); + + this.build(); + return true; + }; + glow = requirements.isReduceStatistic(); + break; + } + case STATISTIC_BLOCKS: + { + name = this.user.getTranslation("challenges.gui.buttons.admin.statistic-block"); + + description = new ArrayList<>(2); + description.add(this.user.getTranslation("challenges.gui.descriptions.admin.statistic-block")); + description.add(this.user.getTranslation("challenges.gui.descriptions.statistic-block", + "[material]", String.valueOf(requirements.getMaterial()))); + + icon = requirements.getMaterial() == null ? + new ItemStack(Material.BARRIER) : + new ItemStack(requirements.getMaterial()); + + clickHandler = (panel, user, clickType, slot) -> { + new SelectBlocksGUI(this.user, + true, + Collections.emptySet(), + (status, block) -> { + if (status) + { + requirements.setMaterial(block.iterator().next()); + } + + this.build(); + }); + + return true; + }; + + glow = false; + break; + } + case STATISTIC_ITEMS: + { + name = this.user.getTranslation("challenges.gui.buttons.admin.statistic-item"); + + description = new ArrayList<>(2); + description.add(this.user.getTranslation("challenges.gui.descriptions.admin.statistic-item")); + description.add(this.user.getTranslation("challenges.gui.descriptions.statistic-item", + "[material]", String.valueOf(requirements.getMaterial()))); + + icon = requirements.getMaterial() == null ? + new ItemStack(Material.BARRIER) : + new ItemStack(requirements.getMaterial()); + + clickHandler = (panel, user, clickType, slot) -> { + new SelectBlocksGUI(this.user, + true, + (status, block) -> { + if (status) + { + requirements.setMaterial(block.iterator().next()); + } + + this.build(); + }); + + return true; + }; + glow = false; + break; + } + case STATISTIC_ENTITIES: + { + name = this.user.getTranslation("challenges.gui.buttons.admin.statistic-entity"); + + description = new ArrayList<>(2); + description.add(this.user.getTranslation("challenges.gui.descriptions.admin.statistic-entity")); + description.add(this.user.getTranslation("challenges.gui.descriptions.statistic-entity", + "[entity]", String.valueOf(requirements.getEntity()))); + + icon = requirements.getEntity() == null ? + new ItemStack(Material.BARRIER) : + new ItemStack(GuiUtils.getEntityEgg(requirements.getEntity())); + + clickHandler = (panel, user, clickType, slot) -> { + new SelectEntityGUI(this.user, Collections.emptySet(), true, (status, entities) -> { + if (status) + { + requirements.setEntity(entities.iterator().next()); + } + + this.build(); + }); + + return true; + }; + glow = false; + break; + } + default: + return null; + } + + return new PanelItemBuilder(). + icon(icon). + name(name). + description(GuiUtils.stringSplit(description, this.lineLength)). + glow(glow). + clickHandler(clickHandler). + build(); + } + + /** * This method creates buttons for rewards menu. * @param button Button which panel item must be created. @@ -1506,6 +1721,12 @@ public class EditChallengeGUI extends CommonGUI REQUIRED_LEVEL, REQUIRED_MONEY, REMOVE_MONEY, + STATISTIC, + STATISTIC_BLOCKS, + STATISTIC_ITEMS, + STATISTIC_ENTITIES, + STATISTIC_AMOUNT, + REMOVE_STATISTIC, } diff --git a/src/main/java/world/bentobox/challenges/panel/util/ChallengeTypeGUI.java b/src/main/java/world/bentobox/challenges/panel/util/ChallengeTypeGUI.java index 68bf018..60a3a2b 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/ChallengeTypeGUI.java +++ b/src/main/java/world/bentobox/challenges/panel/util/ChallengeTypeGUI.java @@ -20,10 +20,7 @@ import world.bentobox.bentobox.api.panels.builders.PanelBuilder; import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; import world.bentobox.bentobox.api.user.User; import world.bentobox.challenges.database.object.Challenge; -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.*; import world.bentobox.challenges.utils.GuiUtils; @@ -71,6 +68,7 @@ public class ChallengeTypeGUI panelBuilder.item(0, this.getButton(Challenge.ChallengeType.INVENTORY)); panelBuilder.item(1, this.getButton(Challenge.ChallengeType.ISLAND)); panelBuilder.item(2, this.getButton(Challenge.ChallengeType.OTHER)); + panelBuilder.item(3, this.getButton(Challenge.ChallengeType.STATISTIC)); panelBuilder.build(); } @@ -112,6 +110,13 @@ public class ChallengeTypeGUI return true; }); break; + case STATISTIC: + icon = new ItemStack(Material.BOOK); + clickHandler = ((panel, user1, clickType, slot) -> { + this.consumer.accept(type, new StatisticRequirements()); + return true; + }); + break; default: return null; } diff --git a/src/main/java/world/bentobox/challenges/panel/util/SelectStatisticGUI.java b/src/main/java/world/bentobox/challenges/panel/util/SelectStatisticGUI.java new file mode 100644 index 0000000..c261855 --- /dev/null +++ b/src/main/java/world/bentobox/challenges/panel/util/SelectStatisticGUI.java @@ -0,0 +1,168 @@ +package world.bentobox.challenges.panel.util; + + +import org.apache.commons.lang.WordUtils; +import org.bukkit.Material; +import org.bukkit.Statistic; +import org.bukkit.inventory.ItemStack; +import java.util.*; +import java.util.function.BiConsumer; + +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.GuiUtils; + + +/** + * This class contains all necessary things that allows to select single statistic. Selected + * stats will be returned via BiConsumer. + */ +public class SelectStatisticGUI +{ + public SelectStatisticGUI(User user, BiConsumer consumer) + { + this.consumer = consumer; + this.user = user; + + this.elements = new ArrayList<>(); + this.elements.addAll(Arrays.asList(Statistic.values())); + + this.build(0); + } + + +// --------------------------------------------------------------------- +// Section: Methods +// --------------------------------------------------------------------- + + + /** + * This method builds all necessary elements in GUI panel. + */ + public void build(int pageIndex) + { + PanelBuilder panelBuilder = new PanelBuilder().user(this.user). + name(this.user.getTranslation("challenges.gui.title.admin.select-statistic")); + + GuiUtils.fillBorder(panelBuilder, Material.BLUE_STAINED_GLASS_PANE); + + final int MAX_ELEMENTS = 21; + final int correctPage; + + if (pageIndex < 0) + { + correctPage = this.elements.size() / MAX_ELEMENTS; + } + else if (pageIndex > (this.elements.size() / MAX_ELEMENTS)) + { + correctPage = 0; + } + else + { + correctPage = pageIndex; + } + + int entitiesIndex = MAX_ELEMENTS * correctPage; + + // I want first row to be only for navigation and return button. + int index = 10; + + while (entitiesIndex < ((correctPage + 1) * MAX_ELEMENTS) && + entitiesIndex < this.elements.size()) + { + if (!panelBuilder.slotOccupied(index)) + { + panelBuilder.item(index, this.createStatisticButton(this.elements.get(entitiesIndex++))); + } + + index++; + } + + panelBuilder.item(3, + new PanelItemBuilder(). + icon(Material.RED_STAINED_GLASS_PANE). + name(this.user.getTranslation("challenges.gui.buttons.admin.cancel")). + clickHandler( (panel, user1, clickType, slot) -> { + this.consumer.accept(false, null); + return true; + }).build()); + + if (this.elements.size() > MAX_ELEMENTS) + { + // Navigation buttons if necessary + + panelBuilder.item(18, + new PanelItemBuilder(). + icon(Material.OAK_SIGN). + name(this.user.getTranslation("challenges.gui.buttons.previous")). + clickHandler((panel, user1, clickType, slot) -> { + this.build(correctPage - 1); + return true; + }).build()); + + panelBuilder.item(26, + new PanelItemBuilder(). + icon(Material.OAK_SIGN). + name(this.user.getTranslation("challenges.gui.buttons.next")). + clickHandler((panel, user1, clickType, slot) -> { + this.build(correctPage + 1); + return true; + }).build()); + } + + panelBuilder.item(44, + new PanelItemBuilder(). + icon(Material.OAK_DOOR). + name(this.user.getTranslation("challenges.gui.buttons.return")). + clickHandler( (panel, user1, clickType, slot) -> { + this.consumer.accept(false, null); + return true; + }).build()); + + panelBuilder.build(); + } + + + /** + * This method creates PanelItem that represents given statistic. + * Some materials is not displayable in Inventory GUI, so they are replaced with "placeholder" items. + * @param statistic Material which icon must be created. + * @return PanelItem that represents given statistic. + */ + private PanelItem createStatisticButton(Statistic statistic) + { + ItemStack itemStack = new ItemStack(Material.PAPER); + + return new PanelItemBuilder(). + name(WordUtils.capitalize(statistic.name().toLowerCase().replace("_", " "))). + icon(itemStack). + clickHandler((panel, user1, clickType, slot) -> { + this.consumer.accept(true, statistic); + return true; + }). + glow(false). + build(); + } + + +// --------------------------------------------------------------------- +// Section: Variables +// --------------------------------------------------------------------- + + /** + * List with elements that will be displayed in current GUI. + */ + private List elements; + + /** + * This variable stores consumer. + */ + private BiConsumer consumer; + + /** + * User who runs GUI. + */ + private User user; +} diff --git a/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java b/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java index 1b29ace..91e17f8 100644 --- a/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java +++ b/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java @@ -2,6 +2,7 @@ package world.bentobox.challenges.tasks; +import com.google.common.collect.UnmodifiableIterator; import java.util.Arrays; import java.util.Collections; import java.util.EnumMap; @@ -11,6 +12,7 @@ import java.util.Map; import java.util.Objects; import java.util.PriorityQueue; import java.util.Queue; +import java.util.UUID; import java.util.stream.Collectors; import org.bukkit.Bukkit; @@ -38,6 +40,7 @@ import world.bentobox.challenges.database.object.ChallengeLevel; 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.StatisticRequirements; import world.bentobox.challenges.utils.Utils; @@ -447,63 +450,196 @@ public class TryToComplete */ private void fullFillRequirements(ChallengeResult result) { - if (this.challenge.getChallengeType().equals(ChallengeType.ISLAND)) + switch (this.challenge.getChallengeType()) { - IslandRequirements requirements = this.challenge.getRequirements(); + case ISLAND -> { + IslandRequirements requirements = this.challenge.getRequirements(); - if (result.meetsRequirements && + if (result.meetsRequirements && requirements.isRemoveEntities() && !requirements.getRequiredEntities().isEmpty()) - { - this.removeEntities(result.entities, result.getFactor()); - } + { + this.removeEntities(result.entities, result.getFactor()); + } - if (result.meetsRequirements && + if (result.meetsRequirements && requirements.isRemoveBlocks() && !requirements.getRequiredBlocks().isEmpty()) - { - this.removeBlocks(result.blocks, result.getFactor()); + { + this.removeBlocks(result.blocks, result.getFactor()); + } } - } - else if (this.challenge.getChallengeType().equals(ChallengeType.INVENTORY)) - { - // If remove items, then remove them - if (this.getInventoryRequirements().isTakeItems()) - { - int sumEverything = result.requiredItems.stream(). + case INVENTORY -> { + // If remove items, then remove them + if (this.getInventoryRequirements().isTakeItems()) + { + int sumEverything = result.requiredItems.stream(). mapToInt(itemStack -> itemStack.getAmount() * result.getFactor()). sum(); - Map removedItems = + Map removedItems = this.removeItems(result.requiredItems, result.getFactor()); - int removedAmount = removedItems.values().stream().mapToInt(num -> num).sum(); + int removedAmount = removedItems.values().stream().mapToInt(num -> num).sum(); - // Something is not removed. - if (sumEverything != removedAmount) - { - this.user.sendMessage("challenges.errors.cannot-remove-items"); + // Something is not removed. + if (sumEverything != removedAmount) + { + this.user.sendMessage("challenges.errors.cannot-remove-items"); - result.removedItems = removedItems; - result.meetsRequirements = false; + result.removedItems = removedItems; + result.meetsRequirements = false; + } } } - } - else if (this.challenge.getChallengeType().equals(ChallengeType.OTHER)) - { - OtherRequirements requirements = this.challenge.getRequirements(); + case OTHER -> { + OtherRequirements requirements = this.challenge.getRequirements(); - if (this.addon.isEconomyProvided() && requirements.isTakeMoney()) - { - this.addon.getEconomyProvider().withdraw(this.user, requirements.getRequiredMoney()); - } + if (this.addon.isEconomyProvided() && requirements.isTakeMoney()) + { + this.addon.getEconomyProvider().withdraw(this.user, requirements.getRequiredMoney()); + } - if (requirements.isTakeExperience() && + if (requirements.isTakeExperience() && this.user.getPlayer().getGameMode() != GameMode.CREATIVE) - { - // Cannot take anything from creative game mode. - this.user.getPlayer().setTotalExperience( + { + // Cannot take anything from creative game mode. + this.user.getPlayer().setTotalExperience( this.user.getPlayer().getTotalExperience() - requirements.getRequiredExperience()); + } + } + case STATISTIC -> { + StatisticRequirements requirements = this.challenge.getRequirements(); + + if (requirements.isReduceStatistic()) + { + int removeAmount = result.getFactor() * requirements.getAmount(); + + // Start to remove from player who called the completion. + switch (requirements.getStatistic().getType()) + { + case UNTYPED -> { + int statistic = this.user.getPlayer().getStatistic(requirements.getStatistic()); + + if (removeAmount >= statistic) + { + this.user.getPlayer().setStatistic(requirements.getStatistic(), 0); + removeAmount -= statistic; + } + else + { + this.user.getPlayer().setStatistic(requirements.getStatistic(), statistic - removeAmount); + removeAmount = 0; + } + } + case ITEM, BLOCK -> { + int statistic = this.user.getPlayer().getStatistic(requirements.getStatistic()); + + if (removeAmount >= statistic) + { + this.user.getPlayer().setStatistic(requirements.getStatistic(), requirements.getMaterial(), 0); + removeAmount -= statistic; + } + else + { + this.user.getPlayer().setStatistic(requirements.getStatistic(), + requirements.getMaterial(), + statistic - removeAmount); + removeAmount = 0; + } + } + case ENTITY -> { + int statistic = this.user.getPlayer().getStatistic(requirements.getStatistic()); + + if (removeAmount >= statistic) + { + this.user.getPlayer().setStatistic(requirements.getStatistic(), requirements.getEntity(), 0); + removeAmount -= statistic; + } + else + { + this.user.getPlayer().setStatistic(requirements.getStatistic(), + requirements.getEntity(), + statistic - removeAmount); + removeAmount = 0; + } + } + } + + // If challenges are in sync with all island members, then punish others too. + if (this.addon.getChallengesSettings().isStoreAsIslandData()) + { + Island island = this.addon.getIslands().getIsland(this.world, this.user); + + if (island == null) + { + // hmm + return; + } + + for (UnmodifiableIterator iterator = island.getMemberSet().iterator(); + iterator.hasNext() && removeAmount > 0; ) + { + Player player = Bukkit.getPlayer(iterator.next()); + + if (player == null || player == this.user.getPlayer()) + { + // cannot punish null or player who already was punished. + continue; + } + + switch (requirements.getStatistic().getType()) + { + case UNTYPED -> { + int statistic = player.getStatistic(requirements.getStatistic()); + + if (removeAmount >= statistic) + { + removeAmount -= statistic; + player.setStatistic(requirements.getStatistic(), 0); + } + else + { + player.setStatistic(requirements.getStatistic(), statistic - removeAmount); + removeAmount = 0; + } + } + case ITEM, BLOCK -> { + int statistic = player.getStatistic(requirements.getStatistic()); + + if (removeAmount >= statistic) + { + removeAmount -= statistic; + player.setStatistic(requirements.getStatistic(), requirements.getMaterial(), 0); + } + else + { + player.setStatistic(requirements.getStatistic(), + requirements.getMaterial(), + statistic - removeAmount); + removeAmount = 0; + } + } + case ENTITY -> { + int statistic = player.getStatistic(requirements.getStatistic()); + + if (removeAmount >= statistic) + { + removeAmount -= statistic; + player.setStatistic(requirements.getStatistic(), requirements.getEntity(), 0); + } + else + { + player.setStatistic(requirements.getStatistic(), + requirements.getEntity(), + statistic - removeAmount); + removeAmount = 0; + } + } + } + } + } + } } } } @@ -596,6 +732,10 @@ public class TryToComplete { result = this.checkOthers(this.getAvailableCompletionTimes(maxTimes)); } + else if (type.equals(ChallengeType.STATISTIC)) + { + result = this.checkStatistic(this.getAvailableCompletionTimes(maxTimes)); + } else { result = EMPTY_RESULT; @@ -1233,6 +1373,50 @@ public class TryToComplete } + // --------------------------------------------------------------------- + // Section: Statistic Challenge + // --------------------------------------------------------------------- + + + /** + * Checks if a statistic challenge can be completed or not + * It returns ChallengeResult. + * @param factor - times that user wanted to complete + */ + private ChallengeResult checkStatistic(int factor) + { + StatisticRequirements requirements = this.challenge.getRequirements(); + + int currentValue; + + switch (requirements.getStatistic().getType()) + { + case UNTYPED -> currentValue = + this.manager.getStatisticData(this.user, this.world, requirements.getStatistic()); + case ITEM, BLOCK -> currentValue = + this.manager.getStatisticData(this.user, this.world, requirements.getStatistic(), requirements.getMaterial()); + case ENTITY -> currentValue = + this.manager.getStatisticData(this.user, this.world, requirements.getStatistic(), requirements.getEntity()); + default -> currentValue = 0; + } + + if (currentValue < requirements.getAmount()) + { + this.user.sendMessage("challenges.errors.requirement-not-met", + TextVariables.NUMBER, String.valueOf(requirements.getAmount()), + "[value]", String.valueOf(currentValue)); + } + else + { + factor = requirements.getAmount() == 0 ? factor : Math.min(factor, currentValue / requirements.getAmount()); + + return new ChallengeResult().setMeetsRequirements().setCompleteFactor(factor); + } + + return EMPTY_RESULT; + } + + // --------------------------------------------------------------------- // Section: Title parsings // --------------------------------------------------------------------- diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index d7a812a..fe27871 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -79,6 +79,7 @@ challenges: lore-edit: '&a Edit Lore' type-select: "&a Choose Challenge Type" + select-statistic: "&a Select Statistic Type" challenges: '&6 Challenges' game-modes: '&6 Choose GameMode' @@ -176,6 +177,13 @@ challenges: challenge-wipe: 'Wipe challenges database' players-wipe: 'Wipe user database' + required-statistic: 'Required Statistic' + required-amount: 'Required amount' + remove-statistic: 'Reduce statistic value' + statistic-block: 'Statistic Block' + statistic-item: 'Statistic Item' + statistic-entity: 'Statistic Entity' + library: 'Web Library' download: 'Download Libraries' @@ -183,6 +191,7 @@ challenges: island: '&6 Island Type' inventory: '&6 Inventory Type' other: '&6 Other Type' + statistic: '&6 Statistic Type' next: 'Next' previous: 'Previous' return: 'Return' @@ -349,6 +358,13 @@ challenges: Right click to enable cache clearing.' download-disabled: 'GitHub data downloader is disabled in BentoBox. Without it, you cannot use Libraries!' + required-statistic: 'Type of statistic that are checked.' + required-amount: 'Amount that need to be reached for the statistic.' + remove-statistic: 'Reduce statistic value from player stats data.' + statistic-block: 'Block which stats must be checked.' + statistic-item: 'Item which stats must be checked.' + statistic-entity: 'Entity which stats must be checked.' + lore: level: |- Level string. @@ -432,6 +448,7 @@ challenges: island: '&a require blocks or mobs around player' inventory: '&a require items in the player"s inventory' other: '&a require things from other plugins/addons' + statistic: '&a require statistic data for player' the-end: '- The End' nether: '- Nether' normal: '- Overworld' @@ -453,6 +470,10 @@ challenges: hidden: "Only Deployed challenges are visible." toggleable: "Toggle if undeployed challenges should be displayed" + statistic-entity: "Current entity: [entity]" + statistic-item: "Current item: [material]" + statistic-block: "Current block: [material]" + challenge-description: level: '&f Level: [level]' completed: '&b Completed' @@ -476,6 +497,11 @@ challenges: required-items: 'Required Items:' required-entities: 'Required Entities:' required-blocks: 'Required Blocks:' + required-stats: 'Statistic: [stat]' + stat-item: 'Item: [amount] x [material]' + stat-block: 'Block: [amount] x [material]' + stat-entity: 'Entity: [amount] x [entity]' + stat-amount: 'Amount: [amount]' level-description: completed: '&b Completed' completed-challenges-of: '&3 You have completed [number] out of [max] challenges in this level.' diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index e590117..2a6738d 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -1,4 +1,4 @@ -name: Challenges +name: Pladdon main: world.bentobox.challenges.ChallengesPladdon version: ${version} api-version: 1.17