diff --git a/README.md b/README.md index 99cd3ff..0d38f40 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Add-on for BentoBox to provide challenges for any BentoBox GameMode. ## Where to find Currently Challenges Addon is in **Beta stage**, so it may or may not contain bugs... a lot of bugs. Also it means, that some features are not working or implemented. -Latest official **Beta Release is 0.6.5-SNAPSHOT**, and you can download it from [Release tab](https://github.com/BentoBoxWorld/Challenges/releases) +Latest official **Beta Release is 0.7.0**, and you can download it from [Release tab](https://github.com/BentoBoxWorld/Challenges/releases) Or you can try **nightly builds** where you can check and test new features that will be implemented in next release from [Jenkins Server](https://ci.codemc.org/job/BentoBoxWorld/job/Challenges/lastStableBuild/). @@ -18,16 +18,21 @@ If you like this addon but something is missing or is not working as you want, y 1. Place the addon jar in the addons folder of the BentoBox plugin 2. Restart the server 3. The addon will create a data folder and inside the folder will be a config.yml and an example challenges.yml -4. Edit the config.yml and challenges.yml files how you want. Note that unlike ASkyBlock, the challenges.yml is for *importing only* and faster start. +4. Edit the config.yml how you want. 5. Restart the server -6. To import challenges into GameMode, you must run admin command and attach `challenges import` at the end. Or you can use challenges admin GUI to do the same. + +#### Challenges + +By default, challenges addon comes without any challenge or level. On first runtime only Admin GUI will be accessible. +Admins can create their own challenges or use challenges from ASkyBlock, by importing them via Admin GUI. This requires challenges.yml file in `./plugins/BentoBox/addons/Challenges/` folder. +There exist also some default challenges, which importing also are available via Admin GUI. ## Compatibility - [x] BentoBox - 1.4.0 version -- [x] BSkyBlock - 1.4.0-SNAPSHO version -- [x] AcidIsland - 1.4.0-SNAPSHO version -- [x] SkyGrid - 1.4.0-SNAPSHOT version +- [x] BSkyBlock - 1.4.0 version +- [x] AcidIsland - 1.4.0 version +- [x] SkyGrid - 1.4.0 version - [x] CaveBlock - 1.4.0 version ## Config.yml @@ -35,40 +40,26 @@ If you like this addon but something is missing or is not working as you want, y As most of BenotBox addons, config can be edited only when server is stopped. Otherwise all changes will be overwritten by server. The config.yml has the following sections: -* Reset Challenges - if this is true, player's challenges will reset when they reset an island or if they are kicked or leave a team. Prevents exploiting the challenges by doing them repeatedly. Default is true -* Broadcast 1st time challenge completion messages to all players. Change to false if the spam becomes too much. Default is true. -* Remove non-repeatable challenges from the challenge GUI when complete. Default is false. -* Add enchanted glow to completed challenges. Default is true -* Free challenges location - You can decide, either free challenges will be at the top, or at the bottom. -* Description line length - allows to specify maximal line length in GUI icon descriptions. -* Challenge Description structure - allows to modify structure of challenge description. -* Level Description structure - allows to modify structure of Level description. -* Disabled GameModes - specify Game Modes where challenges will not work. +* **Commands** - ability to enable */challenges* command. This option change is possible only via configuration and requires server restart. + To enable, you should change `single-gui` to `true`. +* **History** - ability to enable completion history storing in player data object. + To enable, you should change `store-history-data` to `true`. + It is possible to change life-span of history data in days. (0 means that data will not be removed) +* **GUI Settings** - ability to change some options that are visible only in challenges GUI. + * Remove non-repeatable challenges from the challenge GUI when complete. Default is false. + * Add enchanted glow to completed challenges. Default is true. + * Locked level icon is displayed for locked levels. + * Free challenges location - You can decide, either free challenges will be at the top, or at the bottom. + * Description line length - allows to specify maximal line length in GUI icon descriptions. + * Challenge Description structure - allows to modify structure of challenge description. + * Level Description structure - allows to modify structure of Level description. +* **Store mode** - ability to store challenges completion per island or per player. + To enable storing challenges data per island change `store-island-data` to `true`. ATTENTION: progress will be lost on this option change. +* **Reset Challenges** - if this is true, player's challenges will reset when they reset an island or if they are kicked or leave a team. Prevents exploiting the challenges by doing them repeatedly. Default is true +* **Broadcast** - ability to broadcast 1st time challenge completion messages to all players. Change to false if the spam becomes too much. Default is true. +* **Title** - ability to enable showing Title screen on first challenge completion or level completion. +* **Disabled GameModes** - specify Game Modes where challenges will not work. -## Challenges.yml +## Information -This file is just to facilitate importing of old ASkyBlock or AcidIsland challenges and is not used during the normal operation of the game. it is meant to just enable you to jump start your challenge collection. - -This file format is very similar to the ASkyBlock file but not exactly the same because it is designed for 1.13.x servers and higher. If you try to import ASkyBlock challenges, they may or may not completely import, so check for errors in the console. - -Once you have imported challenges, the *real* challenge files are actually in two folders in the BentoBox database folder. One folder is for the challenges and the other is for the challenge levels. They are all defined in .yml files in these locations: - -``` -plugins/BentoBox/database/Challenges -plugins/BentoBox/database/ChallengeLevels -``` - -If you edit a file, then you should reload the challenge database by using the admin reload command, e.g. **/bsb challenges reload** or **/acid challenges reload**. - -If you want to force an overwrite of challenges via an import, add the **overwrite** option to the end of the import command. - -Note that you must import challenges into both BSkyBlock and AcidIsland separately. - - -## Admin commands - -There are a few admin commands and more being written. The main challenge admin command is **/bsb challenges** or **/acid challenges**. Use - -* /bsbadmin challenges help : Show help for all the commands -* /bsbadmin challenges import [overwrite]: import challenges from challenges.yml -* /bsbadmin challenges reload : reload challenges from the database +More information can be found in [Wiki Pages](https://github.com/BentoBoxWorld/Challenges/wiki). diff --git a/pom.xml b/pom.xml index 7798513..436efc6 100644 --- a/pom.xml +++ b/pom.xml @@ -56,7 +56,7 @@ - -#${env.BUILD_NUMBER} + -#${env.BUILD_NUMBER} diff --git a/src/main/java/world/bentobox/challenges/commands/CompleteChallengeCommand.java b/src/main/java/world/bentobox/challenges/commands/CompleteChallengeCommand.java index b9f7d5a..4837dc9 100644 --- a/src/main/java/world/bentobox/challenges/commands/CompleteChallengeCommand.java +++ b/src/main/java/world/bentobox/challenges/commands/CompleteChallengeCommand.java @@ -112,15 +112,14 @@ public class CompleteChallengeCommand extends CompositeCommand }); break; -// TODO: not implemented YET -// case 4: -// // Suggest a number of completions. -// if (lastString.isEmpty() || lastString.matches("[0-9]*")) -// { -// returnList.addAll(Util.tabLimit(Collections.singletonList(""), lastString)); -// } -// -// break; + case 4: + // Suggest a number of completions. + if (lastString.isEmpty() || lastString.matches("[0-9]*")) + { + returnList.addAll(Util.tabLimit(Collections.singletonList(""), lastString)); + } + + break; default: { returnList.addAll(Util.tabLimit(Collections.singletonList("help"), lastString)); diff --git a/src/main/java/world/bentobox/challenges/panel/CommonGUI.java b/src/main/java/world/bentobox/challenges/panel/CommonGUI.java index 2f2a2ae..a061962 100644 --- a/src/main/java/world/bentobox/challenges/panel/CommonGUI.java +++ b/src/main/java/world/bentobox/challenges/panel/CommonGUI.java @@ -102,7 +102,7 @@ public abstract class CommonGUI protected static final String IMPORT = "import"; - protected static final String DEFAULT = "default"; + protected static final String DEFAULT = "defaults"; protected static final String GENERATE = "generate"; diff --git a/src/main/java/world/bentobox/challenges/panel/admin/AdminGUI.java b/src/main/java/world/bentobox/challenges/panel/admin/AdminGUI.java index 08d43d8..dc9e410 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/AdminGUI.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/AdminGUI.java @@ -384,16 +384,9 @@ public class AdminGUI extends CommonGUI description = this.user.getTranslation("challenges.gui.descriptions.admin.default-import"); icon = new ItemStack(Material.HOPPER); clickHandler = (panel, user, clickType, slot) -> { - if (clickType.isRightClick()) - { - this.overwriteMode = !this.overwriteMode; - this.build(); - } - else - { - // Run import command. - this.user.performCommand(this.topLabel + " " + CHALLENGES + " " + DEFAULT + " " + IMPORT); - } + // Run import command. + this.user.performCommand(this.topLabel + " " + CHALLENGES + " " + DEFAULT + " " + IMPORT); + return true; }; glow = false; diff --git a/src/main/java/world/bentobox/challenges/panel/admin/EditLevelGUI.java b/src/main/java/world/bentobox/challenges/panel/admin/EditLevelGUI.java index 175c2a3..6f5a3f7 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/EditLevelGUI.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/EditLevelGUI.java @@ -1,6 +1,7 @@ package world.bentobox.challenges.panel.admin; +import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.enchantments.Enchantment; @@ -275,7 +276,7 @@ public class EditLevelGUI extends CommonGUI private PanelItem createChallengeIcon(Challenge challenge) { return new PanelItemBuilder(). - name(challenge.getFriendlyName()). + name(ChatColor.translateAlternateColorCodes('&', challenge.getFriendlyName())). description(GuiUtils.stringSplit( challenge.getDescription(), this.addon.getChallengesSettings().getLoreLineLength())). diff --git a/src/main/java/world/bentobox/challenges/panel/admin/ListChallengesGUI.java b/src/main/java/world/bentobox/challenges/panel/admin/ListChallengesGUI.java index 6771147..1c21908 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/ListChallengesGUI.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/ListChallengesGUI.java @@ -1,6 +1,7 @@ package world.bentobox.challenges.panel.admin; +import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.World; import java.util.List; @@ -133,7 +134,7 @@ public class ListChallengesGUI extends CommonGUI private PanelItem createChallengeIcon(Challenge challenge) { PanelItemBuilder itemBuilder = new PanelItemBuilder(). - name(challenge.getFriendlyName()). + name(ChatColor.translateAlternateColorCodes('&', challenge.getFriendlyName())). description(GuiUtils.stringSplit(this.generateChallengeDescription(challenge, this.user.getPlayer()), this.addon.getChallengesSettings().getLoreLineLength())). icon(challenge.getIcon()). diff --git a/src/main/java/world/bentobox/challenges/panel/admin/ListLevelsGUI.java b/src/main/java/world/bentobox/challenges/panel/admin/ListLevelsGUI.java index 83a0cf5..b7fbc94 100644 --- a/src/main/java/world/bentobox/challenges/panel/admin/ListLevelsGUI.java +++ b/src/main/java/world/bentobox/challenges/panel/admin/ListLevelsGUI.java @@ -1,6 +1,7 @@ package world.bentobox.challenges.panel.admin; +import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.World; import java.util.List; @@ -133,7 +134,7 @@ public class ListLevelsGUI extends CommonGUI private PanelItem createLevelIcon(ChallengeLevel challengeLevel) { PanelItemBuilder itemBuilder = new PanelItemBuilder(). - name(challengeLevel.getFriendlyName()). + name(ChatColor.translateAlternateColorCodes('&', challengeLevel.getFriendlyName())). description(GuiUtils.stringSplit( this.generateLevelDescription(challengeLevel, this.user.getPlayer()), this.addon.getChallengesSettings().getLoreLineLength())). diff --git a/src/main/java/world/bentobox/challenges/panel/user/ChallengesGUI.java b/src/main/java/world/bentobox/challenges/panel/user/ChallengesGUI.java index 4f1f4e7..29d52ff 100644 --- a/src/main/java/world/bentobox/challenges/panel/user/ChallengesGUI.java +++ b/src/main/java/world/bentobox/challenges/panel/user/ChallengesGUI.java @@ -1,11 +1,13 @@ package world.bentobox.challenges.panel.user; +import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.inventory.ItemStack; import java.util.List; +import net.wesjd.anvilgui.AnvilGUI; import world.bentobox.bentobox.api.panels.PanelItem; import world.bentobox.bentobox.api.panels.builders.PanelBuilder; import world.bentobox.bentobox.api.panels.builders.PanelItemBuilder; @@ -349,18 +351,53 @@ public class ChallengesGUI extends CommonGUI { return new PanelItemBuilder(). icon(challenge.getIcon()). - name(challenge.getFriendlyName().isEmpty() ? challenge.getUniqueId() : challenge.getFriendlyName()). + name(challenge.getFriendlyName().isEmpty() ? + challenge.getUniqueId() : + ChatColor.translateAlternateColorCodes('&', challenge.getFriendlyName())). description(GuiUtils.stringSplit(this.generateChallengeDescription(challenge, this.user.getPlayer()), this.addon.getChallengesSettings().getLoreLineLength())). clickHandler((panel, user1, clickType, slot) -> { - if (TryToComplete.complete(this.addon, - this.user, - challenge, - this.world, - this.topLabel, - this.permissionPrefix)) + + // Add ability to input how many repeats player should do. + // Do not open if challenge is not repeatable. + if (clickType.isRightClick() && challenge.isRepeatable()) { - panel.getInventory().setItem(slot, this.getChallengeButton(challenge).getItem()); + new AnvilGUI(this.addon.getPlugin(), + this.user.getPlayer(), + "1", + (player, reply) -> { + try + { + if (TryToComplete.complete(this.addon, + this.user, + challenge, + this.world, + this.topLabel, + this.permissionPrefix, + Integer.parseInt(reply))) + { + panel.getInventory().setItem(slot, this.getChallengeButton(challenge).getItem()); + } + } + catch (Exception e) + { + this.user.sendMessage("challenges.errors.not-a-integer", "[value]", reply); + } + + return reply; + }); + } + else + { + if (TryToComplete.complete(this.addon, + this.user, + challenge, + this.world, + this.topLabel, + this.permissionPrefix)) + { + panel.getInventory().setItem(slot, this.getChallengeButton(challenge).getItem()); + } } return true; @@ -440,7 +477,7 @@ public class ChallengesGUI extends CommonGUI return new PanelItemBuilder(). icon(icon). - name(name). + name(ChatColor.translateAlternateColorCodes('&', name)). description(description). glow(glow). clickHandler(clickHandler). diff --git a/src/main/java/world/bentobox/challenges/panel/util/SelectChallengeGUI.java b/src/main/java/world/bentobox/challenges/panel/util/SelectChallengeGUI.java index d47082c..b348663 100644 --- a/src/main/java/world/bentobox/challenges/panel/util/SelectChallengeGUI.java +++ b/src/main/java/world/bentobox/challenges/panel/util/SelectChallengeGUI.java @@ -1,6 +1,7 @@ package world.bentobox.challenges.panel.util; +import org.bukkit.ChatColor; import org.bukkit.Material; import org.bukkit.event.inventory.ClickType; import java.util.*; @@ -144,7 +145,7 @@ public class SelectChallengeGUI return new PanelItemBuilder(). - name(challenge.getFriendlyName()). + name(ChatColor.translateAlternateColorCodes('&', challenge.getFriendlyName())). description(GuiUtils.stringSplit(description, this.lineLength)). icon(challenge.getIcon()). clickHandler((panel, user1, clickType, slot) -> { diff --git a/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java b/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java index 4cd50fc..cf7e060 100644 --- a/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java +++ b/src/main/java/world/bentobox/challenges/tasks/TryToComplete.java @@ -4,9 +4,8 @@ package world.bentobox.challenges.tasks; -import org.bukkit.GameMode; -import org.bukkit.Material; -import org.bukkit.World; + +import org.bukkit.*; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.entity.Entity; @@ -234,11 +233,23 @@ public class TryToComplete this.fullFillRequirements(result); - // Validation to avoid rewarding if something goes wrong in removing requrements. + // Validation to avoid rewarding if something goes wrong in removing requirements. if (!result.isMeetsRequirements()) { - // TODO: need to return removed things! + if (result.removedItems != null) + { + result.removedItems.forEach((item, amount) -> + { + ItemStack returnItem = item.clone(); + returnItem.setAmount(amount); + + this.user.getInventory().addItem(returnItem).forEach((k, v) -> + this.user.getWorld().dropItem(this.user.getLocation(), v)); + }); + } + + // Entities and blocks will not be restored. return result; } @@ -805,16 +816,19 @@ public class TryToComplete { if (amountToBeRemoved > 0) { + ItemStack dummy = itemStack.clone(); + dummy.setAmount(1); + // Remove either the full amount or the remaining amount if (itemStack.getAmount() >= amountToBeRemoved) { itemStack.setAmount(itemStack.getAmount() - amountToBeRemoved); - removed.merge(itemStack.clone(), amountToBeRemoved, Integer::sum); + removed.merge(dummy, amountToBeRemoved, Integer::sum); amountToBeRemoved = 0; } else { - removed.merge(itemStack.clone(), itemStack.getAmount(), Integer::sum); + removed.merge(dummy, itemStack.getAmount(), Integer::sum); amountToBeRemoved -= itemStack.getAmount(); itemStack.setAmount(0); } @@ -1235,7 +1249,7 @@ public class TryToComplete outputMessage = outputMessage.replace("[rewardText]", challenge.getRewardText()); } - return outputMessage; + return ChatColor.translateAlternateColorCodes('&', outputMessage); } @@ -1255,7 +1269,7 @@ public class TryToComplete outputMessage = outputMessage.replace("[rewardText]", level.getRewardText()); } - return outputMessage; + return ChatColor.translateAlternateColorCodes('&', outputMessage); } @@ -1410,7 +1424,7 @@ public class TryToComplete /** * Map that contains removed items and their removed count. */ - private Map removedItems; + private Map removedItems = null; /** * Queue of blocks that contains all blocks with the same type as requiredBlock from diff --git a/src/main/java/world/bentobox/challenges/utils/HeadLib.java b/src/main/java/world/bentobox/challenges/utils/HeadLib.java index 3492d0a..0e27d48 100644 --- a/src/main/java/world/bentobox/challenges/utils/HeadLib.java +++ b/src/main/java/world/bentobox/challenges/utils/HeadLib.java @@ -15,16 +15,16 @@ package world.bentobox.challenges.utils; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; import org.bukkit.ChatColor; import org.bukkit.Material; -import org.bukkit.craftbukkit.v1_13_R2.inventory.CraftItemStack; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import java.lang.reflect.Field; import java.util.*; -import net.minecraft.server.v1_13_R2.NBTBase; -import net.minecraft.server.v1_13_R2.NBTTagCompound; -import net.minecraft.server.v1_13_R2.NBTTagList; +import world.bentobox.bentobox.BentoBox; /** @@ -191,10 +191,11 @@ public enum HeadLib public ItemStack toItemStack(int amount, String displayName, String... loreLines) { ItemStack item = new ItemStack(Material.PLAYER_HEAD, amount); + ItemMeta meta = item.getItemMeta(); - if (displayName != null) + // Set Lora and DisplayName + if (meta != null && displayName != null) { - ItemMeta meta = item.getItemMeta(); meta.setDisplayName(ChatColor.translateAlternateColorCodes('&', displayName)); if (loreLines.length != 0) @@ -207,64 +208,26 @@ public enum HeadLib item.setItemMeta(meta); } - return this.setSkullOwner(item, this.getSkullOwner()); - } - - -// --------------------------------------------------------------------- -// Section: Private inner methods -// --------------------------------------------------------------------- - - - /** - * This method returns SkullOwner or create new one if it is not created yet. - * SkullOwner is NBTTagCompound object that contains information about player_head skin. - * @return skullOwner object. - */ - private Object getSkullOwner() - { - if (this.skullOwner == null) + // Set correct Skull texture + if (meta != null && this.textureValue != null && !this.textureValue.isEmpty()) { - this.skullOwner = this.createOwnerCompound(this.uuid, this.textureValue); + GameProfile profile = new GameProfile(UUID.fromString(this.uuid), null); + profile.getProperties().put("textures", new Property("textures", this.textureValue)); + + try + { + Field profileField = meta.getClass().getDeclaredField("profile"); + profileField.setAccessible(true); + profileField.set(meta, profile); + item.setItemMeta(meta); + } + catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException e) + { + BentoBox.getInstance().log("Error while creating Skull Icon"); + } } - return this.skullOwner; - } - - - /** - * This method creates new NBTTagCompound object that contains UUID and texture link. - * @param id - UUID of user. - * @param textureValue - Encoded texture string. - * @return NBTTagCompound object that contains all necessary information about player_head skin. - */ - private NBTTagCompound createOwnerCompound(String id, String textureValue) - { - NBTTagCompound skullOwner = new NBTTagCompound(); - skullOwner.setString("Id", id); - NBTTagCompound properties = new NBTTagCompound(); - NBTTagList textures = new NBTTagList(); - NBTTagCompound value = new NBTTagCompound(); - value.setString("Value", textureValue); - textures.add(value); - properties.set("textures", textures); - skullOwner.set("Properties", properties); - - return skullOwner; - } - - - /** - * This method adds SkullOwner tag to given item. - * @param item Item whom SkullOwner tag must be added. - * @param compound NBTTagCompound object that contains UUID and texture link. - * @return new copy of given item with SkullOwner tag. - */ - private ItemStack setSkullOwner(ItemStack item, Object compound) - { - net.minecraft.server.v1_13_R2.ItemStack nmsStack = CraftItemStack.asNMSCopy(item); - nmsStack.getOrCreateTag().set("SkullOwner", (NBTBase) compound); - return CraftItemStack.asBukkitCopy(nmsStack); + return item; } diff --git a/src/main/resources/locales/en-US.yml b/src/main/resources/locales/en-US.yml index 6b38ad0..22706b0 100755 --- a/src/main/resources/locales/en-US.yml +++ b/src/main/resources/locales/en-US.yml @@ -319,13 +319,13 @@ challenges: admin: hit-things: 'Hit things to add them to the list of things required. Right click when done.' you-added: 'You added one [thing] to the challenge' - challenge-created: '[challenge] created!' - you-completed-challenge: '&2You completed the [value] challenge!' - you-repeated-challenge: '&2You repeated the [value] challenge!' - you-repeated-challenge-multiple: '&2You repeated the [value] challenge [count] times!' - you-completed-level: '&2You completed the [value] level!' - name-has-completed-challenge: '&5[name] has completed the [value] challenge!' - name-has-completed-level: '&5[name] has completed the [value] level!' + challenge-created: '[challenge]&r created!' + you-completed-challenge: '&2You completed the [value] &r&2challenge!' + you-repeated-challenge: '&2You repeated the [value] &r&2challenge!' + you-repeated-challenge-multiple: '&2You repeated the [value] &r&2challenge [count] times!' + you-completed-level: '&2You completed the [value] &r&2level!' + name-has-completed-challenge: '&5[name] has completed the [value] &r&5challenge!' + name-has-completed-level: '&5[name] has completed the [value] &r&5level!' import-levels: 'Start importing Levels' import-challenges: 'Start importing Challenges' no-levels: 'Warning: No levels defined in challenges.yml' diff --git a/src/test/java/world/bentobox/challenges/tasks/TryToCompleteTest.java b/src/test/java/world/bentobox/challenges/tasks/TryToCompleteTest.java new file mode 100644 index 0000000..b60aff7 --- /dev/null +++ b/src/test/java/world/bentobox/challenges/tasks/TryToCompleteTest.java @@ -0,0 +1,230 @@ +package world.bentobox.challenges.tasks; + +import static org.junit.Assert.*; +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.logging.Logger; + +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.inventory.ItemFactory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mockito; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +import world.bentobox.challenges.ChallengesAddon; +import world.bentobox.bentobox.api.user.User; + +/** + * @author tastybento + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({ Bukkit.class}) +public class TryToCompleteTest { + + private User user; + ItemStack[] stacks = { new ItemStack(Material.PAPER, 32), + new ItemStack(Material.ACACIA_BOAT), + null, + null, + new ItemStack(Material.CACTUS, 32), + new ItemStack(Material.CACTUS, 32), + new ItemStack(Material.CACTUS, 32), + new ItemStack(Material.BRICK_STAIRS, 64), + new ItemStack(Material.BRICK_STAIRS, 64), + new ItemStack(Material.BRICK_STAIRS, 5), + new ItemStack(Material.GOLD_BLOCK, 32) + }; + List required; + private ChallengesAddon addon; + private PlayerInventory inv; + + /** + * @throws java.lang.Exception + */ + @Before + public void setUp() throws Exception { + user = mock(User.class); + inv = mock(PlayerInventory.class); + when(inv.getContents()).thenReturn(stacks); + when(user.getInventory()).thenReturn(inv); + addon = mock(ChallengesAddon.class); + required = new ArrayList<>(); + + Server server = mock(Server.class); + ItemFactory itemFactory = mock(ItemFactory.class); + when(server.getItemFactory()).thenReturn(itemFactory); + + // Test will not work with items that has meta data. + when(itemFactory.getItemMeta(any())).thenReturn(null); + when(itemFactory.equals(null, null)).thenReturn(true); + + PowerMockito.mockStatic(Bukkit.class); + when(Bukkit.getServer()).thenReturn(server); + + when(Bukkit.getItemFactory()).thenReturn(itemFactory); + when(Bukkit.getLogger()).thenReturn(Logger.getAnonymousLogger()); + } + + /** + * Test method for {@link TryToComplete#removeItems(java.util.List, int)}. + */ + @Test + public void testRemoveItemsSuccess() { + Material requiredMaterial = Material.PAPER; + int requiredQuantity = 21; + + this.required.add(new ItemStack(requiredMaterial, requiredQuantity)); + TryToComplete x = new TryToComplete(this.addon); + x.user(this.user); + Map removed = x.removeItems(this.required, 1); + + assertEquals((int) removed.getOrDefault(new ItemStack(requiredMaterial, 1), 0), requiredQuantity); + } + + /** + * Test method for {@link TryToComplete#removeItems(java.util.List, int)}. + */ + @Test + public void testRemoveItemsMax() { + Material requiredMaterial = Material.PAPER; + int requiredQuantity = 50; + + this.required.add(new ItemStack(requiredMaterial, requiredQuantity)); + TryToComplete x = new TryToComplete(this.addon); + x.user(this.user); + Map removed = x.removeItems(this.required, 1); + + assertNotEquals((int) removed.getOrDefault(new ItemStack(requiredMaterial, 1), 0), requiredQuantity); + } + + /** + * Test method for {@link TryToComplete#removeItems(java.util.List, int)}. + */ + @Test + public void testRemoveItemsZero() { + Material requiredMaterial = Material.PAPER; + int requiredQuantity = 0; + + this.required.add(new ItemStack(requiredMaterial, requiredQuantity)); + TryToComplete x = new TryToComplete(this.addon); + x.user(this.user); + Map removed = x.removeItems(this.required, 1); + + assertTrue(removed.isEmpty()); + } + + /** + * Test method for {@link TryToComplete#removeItems(java.util.List, int)}. + */ + @Test + public void testRemoveItemsSuccessMultiple() { + required.add(new ItemStack(Material.PAPER, 11)); + required.add(new ItemStack(Material.PAPER, 5)); + required.add(new ItemStack(Material.PAPER, 5)); + TryToComplete x = new TryToComplete(addon); + x.user(user); + Map removed = x.removeItems(required, 1); + + assertEquals((int) removed.getOrDefault(new ItemStack(Material.PAPER, 1), 0), 21); + } + + /** + * Test method for {@link TryToComplete#removeItems(java.util.List, int)}. + */ + @Test + public void testRemoveItemsSuccessMultipleOther() { + required.add(new ItemStack(Material.CACTUS, 5)); + required.add(new ItemStack(Material.PAPER, 11)); + required.add(new ItemStack(Material.PAPER, 5)); + required.add(new ItemStack(Material.PAPER, 5)); + required.add(new ItemStack(Material.CACTUS, 5)); + TryToComplete x = new TryToComplete(addon); + x.user(user); + Map removed = x.removeItems(required, 1); + + assertEquals((int) removed.getOrDefault(new ItemStack(Material.PAPER, 1), 0), 21); + assertEquals((int) removed.getOrDefault(new ItemStack(Material.CACTUS, 1), 0), 10); + } + + /** + * Test method for {@link TryToComplete#removeItems(java.util.List, int)}. + */ + @Test + public void testRemoveItemsMultipleOtherFail() { + required.add(new ItemStack(Material.ACACIA_FENCE, 5)); + required.add(new ItemStack(Material.ARROW, 11)); + required.add(new ItemStack(Material.STONE, 5)); + required.add(new ItemStack(Material.BAKED_POTATO, 5)); + required.add(new ItemStack(Material.GHAST_SPAWN_EGG, 5)); + TryToComplete x = new TryToComplete(addon); + x.user(user); + Map removed = x.removeItems(required, 1); + assertTrue(removed.isEmpty()); + } + + /** + * Test method for {@link TryToComplete#removeItems(java.util.List, int)}. + */ + @Test + public void testRemoveItemsFail() { + ItemStack input = new ItemStack(Material.GOLD_BLOCK, 55); + required.add(input); + TryToComplete x = new TryToComplete(addon); + x.user(user); + Map removed = x.removeItems(required, 1); + + // It will remove 32, but not any more + assertEquals((int) removed.getOrDefault(new ItemStack(Material.GOLD_BLOCK, 1), 0), 32); + + // An error will be thrown + Mockito.verify(addon, Mockito.times(1)).logError(Mockito.anyString()); + } + + + + /** + * Test method for {@link TryToComplete#removeItems(java.util.List, int)}. + */ + @Test + public void testRequireTwoStacks() { + required.add(new ItemStack(Material.BRICK_STAIRS, 64)); + required.add(new ItemStack(Material.BRICK_STAIRS, 64)); + + TryToComplete x = new TryToComplete(addon); + x.user(user); + Map removed = x.removeItems(required, 1); + + // It should remove both stacks + assertEquals((int) removed.getOrDefault(new ItemStack(Material.BRICK_STAIRS, 1), 0), 128); + } + + + /** + * Test method for {@link TryToComplete#removeItems(java.util.List, int)}. + */ + @Test + public void testFactorStacks() { + required.add(new ItemStack(Material.BRICK_STAIRS, 32)); + + TryToComplete x = new TryToComplete(addon); + x.user(user); + Map removed = x.removeItems(required, 4); + + // It should remove both stacks + assertEquals((int) removed.getOrDefault(new ItemStack(Material.BRICK_STAIRS, 1), 0), 128); + } +} +