From efc6b62b2fe37c3ad17c594dbb9b0269d61bc3de Mon Sep 17 00:00:00 2001 From: destro174 <40720638+destro174@users.noreply.github.com> Date: Sun, 25 Feb 2024 14:02:51 +0100 Subject: [PATCH] Add a base layer for challenges. Implement a challenge system. This allows players to work towards them and add some goals to reach. Demo challenges are included in file challenges.yml. This should provide details on how to create your own challenges. --------- Co-authored-by: Teriuihi --- .../api/challenges/ChallengeDifficulty.java | 9 + .../api/challenges/ChallengeType.java | 28 +++ plugin/build.gradle.kts | 11 +- .../cometskyblock/CometSkyBlockPlugin.java | 17 +- .../cometskyblock/challenges/Challenge.java | 17 +- .../challenges/ChallengeDifficulty.java | 4 - .../challenges/ChallengeHandler.java | 59 +++++ .../challenges/ChallengeLoader.java | 212 +++++++++++++++++ .../challenges/ChallengeType.java | 12 - .../challenges/ChallengesGUI.java | 161 ++++++++++++- .../commands/admin/ReloadCommand.java | 30 +++ .../commands/admin/SkyBlockCommand.java | 24 ++ .../commands/challenges/ChallengeCommand.java | 16 +- .../ChallengesConfiguration.java | 16 -- .../alttd/cometskyblock/gui/GUIInventory.java | 16 +- .../alttd/cometskyblock/island/Island.java | 10 + .../cometskyblock/island/gui/IslandGUI.java | 2 +- .../cometskyblock/island/gui/MembersGUI.java | 2 +- .../cometskyblock/island/gui/SettingsGUI.java | 2 +- .../cometskyblock/island/gui/UpgradesGUI.java | 2 +- .../alttd/cometskyblock/util/PlayerUtils.java | 115 +++++++++ .../alttd/cometskyblock/util/StringUtil.java | 109 +++++++++ plugin/src/main/resources/challenges.yml | 219 ++++++++++++++++++ plugin/src/test/java/StringUtilTest.java | 91 ++++++++ 24 files changed, 1128 insertions(+), 56 deletions(-) create mode 100644 api/src/main/java/com/alttd/cometskyblock/api/challenges/ChallengeDifficulty.java create mode 100644 api/src/main/java/com/alttd/cometskyblock/api/challenges/ChallengeType.java delete mode 100644 plugin/src/main/java/com/alttd/cometskyblock/challenges/ChallengeDifficulty.java create mode 100644 plugin/src/main/java/com/alttd/cometskyblock/challenges/ChallengeHandler.java create mode 100644 plugin/src/main/java/com/alttd/cometskyblock/challenges/ChallengeLoader.java delete mode 100644 plugin/src/main/java/com/alttd/cometskyblock/challenges/ChallengeType.java create mode 100644 plugin/src/main/java/com/alttd/cometskyblock/commands/admin/ReloadCommand.java create mode 100644 plugin/src/main/java/com/alttd/cometskyblock/commands/admin/SkyBlockCommand.java delete mode 100644 plugin/src/main/java/com/alttd/cometskyblock/configuration/ChallengesConfiguration.java create mode 100644 plugin/src/main/java/com/alttd/cometskyblock/util/PlayerUtils.java create mode 100644 plugin/src/main/java/com/alttd/cometskyblock/util/StringUtil.java create mode 100644 plugin/src/main/resources/challenges.yml create mode 100644 plugin/src/test/java/StringUtilTest.java diff --git a/api/src/main/java/com/alttd/cometskyblock/api/challenges/ChallengeDifficulty.java b/api/src/main/java/com/alttd/cometskyblock/api/challenges/ChallengeDifficulty.java new file mode 100644 index 0000000..c2345df --- /dev/null +++ b/api/src/main/java/com/alttd/cometskyblock/api/challenges/ChallengeDifficulty.java @@ -0,0 +1,9 @@ +package com.alttd.cometskyblock.api.challenges; + +public enum ChallengeDifficulty { + EASY, + MEDIUM, + HARD, + LEGENDARY, + EXOTIC +} diff --git a/api/src/main/java/com/alttd/cometskyblock/api/challenges/ChallengeType.java b/api/src/main/java/com/alttd/cometskyblock/api/challenges/ChallengeType.java new file mode 100644 index 0000000..c60fdeb --- /dev/null +++ b/api/src/main/java/com/alttd/cometskyblock/api/challenges/ChallengeType.java @@ -0,0 +1,28 @@ +package com.alttd.cometskyblock.api.challenges; + +public enum ChallengeType { + /** + * Player must have the items to turn in + */ + ON_PLAYER, + /** + * Requirements must be on the island + */ + ON_ISLAND, + /** + * Island level requirement + */ + ISLAND_LEVEL; + + public static ChallengeType of(String challengeType) { + if (challengeType == null || challengeType.isEmpty()) + return null; + return switch (challengeType.toUpperCase()) { + case "ON_PLAYER" -> ChallengeType.ON_PLAYER; + case "ON_ISLAND" -> ChallengeType.ON_ISLAND; + case "ISLAND_LEVEL" -> ChallengeType.ISLAND_LEVEL; + default -> null; + }; + } + +} diff --git a/plugin/build.gradle.kts b/plugin/build.gradle.kts index a318b45..cd74e45 100644 --- a/plugin/build.gradle.kts +++ b/plugin/build.gradle.kts @@ -9,12 +9,19 @@ dependencies { compileOnly("org.projectlombok:lombok:1.18.24") annotationProcessor("org.projectlombok:lombok:1.18.24") + + testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") } tasks { jar { archiveFileName.set("${rootProject.name}-${project.name}.jar") } + + test { + useJUnitPlatform() + } } bukkit { @@ -32,12 +39,12 @@ bukkit { permission = "${rootProject.name}.command.island" } - register("Challenges") { + register("challenges") { description = "Opens the challenges menu." permission = "${rootProject.name}.command.island" } - register("cometskyblock") { + register("skyblock") { description = "${rootProject.name} admin command." permission = "${rootProject.name}.command.admin" } diff --git a/plugin/src/main/java/com/alttd/cometskyblock/CometSkyBlockPlugin.java b/plugin/src/main/java/com/alttd/cometskyblock/CometSkyBlockPlugin.java index 98b4b2b..aaf75f4 100644 --- a/plugin/src/main/java/com/alttd/cometskyblock/CometSkyBlockPlugin.java +++ b/plugin/src/main/java/com/alttd/cometskyblock/CometSkyBlockPlugin.java @@ -1,5 +1,8 @@ package com.alttd.cometskyblock; +import com.alttd.cometskyblock.challenges.ChallengeHandler; +import com.alttd.cometskyblock.challenges.ChallengeLoader; +import com.alttd.cometskyblock.commands.admin.SkyBlockCommand; import com.alttd.cometskyblock.commands.challenges.ChallengeCommand; import com.alttd.cometskyblock.commands.island.IslandCommand; import com.alttd.cometskyblock.configuration.*; @@ -25,12 +28,13 @@ public class CometSkyBlockPlugin extends JavaPlugin implements CometSkyBlockAPI @Getter private ConfigurationContainer pluginConfiguration; @Getter private ConfigurationContainer databaseConfiguration; @Getter private ConfigurationContainer messagesConfiguration; - @Getter private ConfigurationContainer challengesConfiguration; @Getter private ConfigurationContainer cobblestoneGeneratorConfiguration; @Getter private ConfigurationContainer worldBorderConfiguration; @Getter private IslandManager islandManager; @Getter private MasterWorldGenerator worldGenerator; + @Getter private ChallengeHandler challengeHandler; + @Getter private ChallengeLoader challengeLoader; @Override public void onLoad() { @@ -60,6 +64,8 @@ public class CometSkyBlockPlugin extends JavaPlugin implements CometSkyBlockAPI // load worlds & manager islandManager = new IslandManager(this); worldGenerator = new MasterWorldGenerator(this); + challengeHandler = new ChallengeHandler(this); + loadChallenges(); worldGenerator.checkMasterIslandWorld(); } @@ -72,13 +78,18 @@ public class CometSkyBlockPlugin extends JavaPlugin implements CometSkyBlockAPI // close data connection } + public void loadChallenges() { + challengeHandler.clearChallenges(); + challengeLoader = new ChallengeLoader(this); + challengeLoader.loadAllChallenges(); + } + public void loadConfiguration() { Path path = this.getDataFolder().toPath(); Logger logger = this.getSLF4JLogger(); pluginConfiguration = ConfigurationContainer.load(logger, path, PluginConfiguration.class, "config"); databaseConfiguration = ConfigurationContainer.load(logger, path, DatabaseConfiguration.class, "database"); messagesConfiguration = ConfigurationContainer.load(logger, path, MessageConfiguration.class, "messages"); - challengesConfiguration = ConfigurationContainer.load(logger, path, ChallengesConfiguration.class, "challenges"); cobblestoneGeneratorConfiguration = ConfigurationContainer.load(logger, path, CobblestoneGeneratorConfiguration.class, "coblestonegenerator"); worldBorderConfiguration = ConfigurationContainer.load(logger, path, WorldBorderConfiguration.class, "worldborder"); } @@ -86,7 +97,7 @@ public class CometSkyBlockPlugin extends JavaPlugin implements CometSkyBlockAPI public void loadCommands() { getCommand("island").setExecutor(new IslandCommand(this)); getCommand("challenges").setExecutor(new ChallengeCommand(this)); -// getCommand("cometskyblock").setExecutor( new AdminCommands(this)); + getCommand("skyblock").setExecutor( new SkyBlockCommand(this)); } public void loadEventListeners() { diff --git a/plugin/src/main/java/com/alttd/cometskyblock/challenges/Challenge.java b/plugin/src/main/java/com/alttd/cometskyblock/challenges/Challenge.java index dc34678..a7ec41d 100644 --- a/plugin/src/main/java/com/alttd/cometskyblock/challenges/Challenge.java +++ b/plugin/src/main/java/com/alttd/cometskyblock/challenges/Challenge.java @@ -1,29 +1,30 @@ package com.alttd.cometskyblock.challenges; +import com.alttd.cometskyblock.api.challenges.ChallengeDifficulty; +import com.alttd.cometskyblock.api.challenges.ChallengeType; import lombok.Getter; +import lombok.Setter; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; -import org.spongepowered.configurate.objectmapping.ConfigSerializable; import java.util.List; -@Getter -@ConfigSerializable +@Getter @Setter public class Challenge { private String challengeName; - private String mySQLKey; + private String key; private Material shownItem; private ChallengeDifficulty difficulty; private ChallengeType challengeType; private String description; - private String neededText; + private String requiredText; private String rewardText; private String repeatRewardText; private List rewards; private List repeatRewards; - private List neededItems; - private Integer neededLevel; - private Integer slot; + private List requiredItems; + private Integer requiredLevel; + private Integer radius; } diff --git a/plugin/src/main/java/com/alttd/cometskyblock/challenges/ChallengeDifficulty.java b/plugin/src/main/java/com/alttd/cometskyblock/challenges/ChallengeDifficulty.java deleted file mode 100644 index ff6cba7..0000000 --- a/plugin/src/main/java/com/alttd/cometskyblock/challenges/ChallengeDifficulty.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.alttd.cometskyblock.challenges; - -public class ChallengeDifficulty { -} diff --git a/plugin/src/main/java/com/alttd/cometskyblock/challenges/ChallengeHandler.java b/plugin/src/main/java/com/alttd/cometskyblock/challenges/ChallengeHandler.java new file mode 100644 index 0000000..c141927 --- /dev/null +++ b/plugin/src/main/java/com/alttd/cometskyblock/challenges/ChallengeHandler.java @@ -0,0 +1,59 @@ +package com.alttd.cometskyblock.challenges; + +import com.alttd.cometskyblock.CometSkyBlockPlugin; +import com.alttd.cometskyblock.api.challenges.ChallengeDifficulty; + +import java.util.HashMap; +import java.util.Map; + +public class ChallengeHandler { + + private final Map easyChallenges = new HashMap<>(); + private final Map mediumChallenges = new HashMap<>(); + private final Map hardChallenges = new HashMap<>(); + private final Map impossibleChallenges = new HashMap<>(); + private final Map exoticChallenges = new HashMap<>(); + + private final CometSkyBlockPlugin plugin; + + public ChallengeHandler(CometSkyBlockPlugin plugin) { + this.plugin = plugin; + } + + public void addChallenge(Challenge challenge) { + switch (challenge.difficulty()) { + case EASY -> easyChallenges.put(challenge.key(), challenge); + case MEDIUM -> mediumChallenges.put(challenge.key(), challenge); + case HARD -> hardChallenges.put(challenge.key(), challenge); + case LEGENDARY -> impossibleChallenges.put(challenge.key(), challenge); + case EXOTIC -> exoticChallenges.put(challenge.key(), challenge); + } + } + + public void clearChallenges() { + for (ChallengeDifficulty difficulty : ChallengeDifficulty.values()) { + getChallenges(difficulty).clear(); + } + } + + public Map getChallenges(ChallengeDifficulty challengeDifficulty) { + switch (challengeDifficulty) { + case MEDIUM -> { + return mediumChallenges; + } + case HARD -> { + return hardChallenges; + } + case LEGENDARY -> { + return impossibleChallenges; + } + case EXOTIC -> { + return exoticChallenges; + } + default -> { + return easyChallenges; + } + } + } + +} \ No newline at end of file diff --git a/plugin/src/main/java/com/alttd/cometskyblock/challenges/ChallengeLoader.java b/plugin/src/main/java/com/alttd/cometskyblock/challenges/ChallengeLoader.java new file mode 100644 index 0000000..69ab722 --- /dev/null +++ b/plugin/src/main/java/com/alttd/cometskyblock/challenges/ChallengeLoader.java @@ -0,0 +1,212 @@ +package com.alttd.cometskyblock.challenges; + +import com.alttd.cometskyblock.CometSkyBlockPlugin; +import com.alttd.cometskyblock.api.challenges.ChallengeDifficulty; +import com.alttd.cometskyblock.api.challenges.ChallengeType; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.inventory.ItemStack; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * Data loader for challenges + * Parse the data and build the challenges to be used by the plugin. + */ +public class ChallengeLoader extends YamlConfiguration { + + private final CometSkyBlockPlugin plugin; + private final File file; + private final Object saveLock = new Object(); + private final Object2IntMap difficultyLevels = new Object2IntOpenHashMap<>(); + + public ChallengeLoader(CometSkyBlockPlugin plugin) { + super(); + this.plugin = plugin; + this.file = new File(plugin.getDataFolder(), "challenges.yml"); + difficultyLevels.defaultReturnValue(0); + reload(); + } + + private void reload() { + synchronized (saveLock) { + try { + load(file); + } catch (Exception ignore) { + } + } + } + + // Todo - do we continue loading or end the loading procedure? + public void loadAllChallenges() { + for (ChallengeDifficulty difficulty : ChallengeDifficulty.values()) { + if (!loadChallenges(difficulty)) { + plugin.getLogger().warning(String.format("could not load %s challenges", difficulty.toString())); + } + loadDifficultyLevel(difficulty); + } + } + + private void loadDifficultyLevel(ChallengeDifficulty challengeDifficulty) { + if (challengeDifficulty == null) { + plugin.getLogger().warning("Could not load challenges!"); + return; + } + String difficulty = challengeDifficulty.toString().toLowerCase(); + int level = getInt("level." + difficulty, 0); + if (level > 0) + difficultyLevels.put(challengeDifficulty, level); + } + + private boolean loadChallenges(ChallengeDifficulty challengeDifficulty) { + if (challengeDifficulty == null) { + plugin.getLogger().warning("Could not load challenges!"); + return false; + } + String difficulty = challengeDifficulty.toString().toLowerCase(); + ConfigurationSection section = getConfigurationSection(difficulty); + if (section == null) { + plugin.getLogger().warning(String.format("could not load challenges for difficulty %s", difficulty)); + return false; + } + Set challenges = section.getKeys(false); + for (String currentChallenge : challenges) { + String challengeName = section.getString(currentChallenge + ".name"); + String key = section.getString(currentChallenge + ".key"); + String shownItem = section.getString(currentChallenge + ".shown-item"); + if (shownItem == null || shownItem.isEmpty() || isNotMaterial(shownItem)) { + plugin.getLogger().warning(String.format("could not load challenge %s material data", currentChallenge)); + continue; + } + Material shownItemMaterial = Material.getMaterial(shownItem.toUpperCase()); + ChallengeType challengeType = ChallengeType.of(section.getString(currentChallenge + ".challenge-type")); + String description = section.getString(currentChallenge + ".description"); + String requiredText = section.getString(currentChallenge + ".required-text"); + String rewardText = section.getString(currentChallenge + ".reward-text"); + String repeatRewardText = section.getString(currentChallenge + ".repeat-reward-text"); + List rewards = section.getStringList(currentChallenge + ".reward-items"); + List repeatRewards = section.getStringList(currentChallenge + ".repeat-reward-items"); + List requiredItems = section.getStringList(currentChallenge + ".required-items"); + Integer requiredLevel = section.getInt(currentChallenge + ".required-level", 0); + Integer radius = section.getInt(currentChallenge + ".radius", 50); + // Build the challenge + Challenge challenge = new Challenge() + .challengeName(challengeName) + .difficulty(challengeDifficulty) + .key(key) + .shownItem(shownItemMaterial) + .description(description) + .requiredText(requiredText) + .rewardText(rewardText) + .repeatRewardText(repeatRewardText) + .requiredLevel(requiredLevel) + .challengeType(challengeType) + .rewards(createItems(rewards)) + .repeatRewards(createItems(repeatRewards)) + .requiredItems(createItems(requiredItems)) + .radius(radius); + if (!validateChallenge(challenge)) { + plugin.getLogger().warning(String.format("Invalid %s challenge: %s", difficulty, currentChallenge)); + continue; + } + plugin.challengeHandler().addChallenge(challenge); + } + return true; + } + + private boolean isNotMaterial(String material) { + return Material.matchMaterial(material.toUpperCase()) == null; + } + + private List createItems(List items) { + List itemStacks = new ArrayList<>(); + for (String currentItem : items) { + String material = currentItem; + int amount = 1; + if (currentItem.contains(";")) { + String[] strings = currentItem.split(";"); + if (strings.length != 2) { + plugin.getLogger().warning(String.format("Could not make itemstack for item %s, invalid separator", currentItem)); + continue; + } + if (!isNumber(strings[1])) { + plugin.getLogger().warning(String.format("Could not make itemstack for item %s, %s is not an int", currentItem, strings[1])); + continue; + } + material = strings[0]; + amount = Integer.parseInt(strings[1]); + } + ItemStack itemStack = makeItemStack(material, amount); + if (itemStack == null) { + plugin.getLogger().warning(String.format("Could not make itemstack for item %s", currentItem)); + continue; + } + itemStacks.add(itemStack); + } + return itemStacks; + } + + private ItemStack makeItemStack(String materialString, int amount) { + if (materialString == null || materialString.isEmpty() || isNotMaterial(materialString)) { + return null; + } + Material material = Material.getMaterial(materialString.toUpperCase()); + if (material == null) { + return null; + } + return new ItemStack(material, amount); + } + + private boolean validateChallenge(Challenge challenge) { + if (challenge.challengeName() != null && + challenge.key() != null && + challenge.challengeType() != null && + challenge.description() != null && + challenge.difficulty() != null && + challenge.shownItem() != null && + challenge.requiredText() != null && + challenge.rewardText() != null && + challenge.rewards() != null && + !challenge.rewards().isEmpty() + ) { + switch (challenge.challengeType()) { + case ISLAND_LEVEL -> { + if (challenge.requiredLevel() != null) + return true; + } + case ON_ISLAND -> { + if (challenge.requiredItems() != null && !challenge.requiredItems().isEmpty() && challenge.radius() != null) + return true; + + } + case ON_PLAYER -> { + if (challenge.requiredItems() != null && !challenge.requiredItems().isEmpty() && challenge.repeatRewards() != null && !challenge.repeatRewards().isEmpty() && challenge.repeatRewardText() != null) + return true; + } + default -> { + return false; + } + } + } + return false; + } + + public int requiredLevel(ChallengeDifficulty challengeDifficulty) { + return difficultyLevels.getInt(challengeDifficulty); + } + + private boolean isNumber(String s) { + try { + Integer.parseInt(s); + } catch (NumberFormatException nfe) { + return false; + } + return true; + } +} diff --git a/plugin/src/main/java/com/alttd/cometskyblock/challenges/ChallengeType.java b/plugin/src/main/java/com/alttd/cometskyblock/challenges/ChallengeType.java deleted file mode 100644 index 12687a4..0000000 --- a/plugin/src/main/java/com/alttd/cometskyblock/challenges/ChallengeType.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.alttd.cometskyblock.challenges; - -import org.spongepowered.configurate.objectmapping.ConfigSerializable; - -@ConfigSerializable -public enum ChallengeType { - - onPlayer, - onIsland, - islandLevel - -} diff --git a/plugin/src/main/java/com/alttd/cometskyblock/challenges/ChallengesGUI.java b/plugin/src/main/java/com/alttd/cometskyblock/challenges/ChallengesGUI.java index 35b0dbf..b83450c 100644 --- a/plugin/src/main/java/com/alttd/cometskyblock/challenges/ChallengesGUI.java +++ b/plugin/src/main/java/com/alttd/cometskyblock/challenges/ChallengesGUI.java @@ -1,27 +1,186 @@ package com.alttd.cometskyblock.challenges; +import com.alttd.cometskyblock.CometSkyBlockPlugin; +import com.alttd.cometskyblock.api.challenges.ChallengeDifficulty; +import com.alttd.cometskyblock.api.challenges.ChallengeType; +import com.alttd.cometskyblock.gui.GUIButton; import com.alttd.cometskyblock.gui.GUIInventory; import com.alttd.cometskyblock.island.Island; +import com.alttd.cometskyblock.util.PlayerUtils; +import com.alttd.cometskyblock.util.StringUtil; +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder; +import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver; import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.Player; +import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemFlag; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; public class ChallengesGUI extends GUIInventory { + protected ChallengeDifficulty challengeDifficulty = ChallengeDifficulty.EASY; + public ChallengesGUI(Island island) { super(island); } @Override protected Inventory createInventory() { // TODO - config - return Bukkit.createInventory(this, 54, MiniMessage.miniMessage().deserialize("GUI")); + return Bukkit.createInventory(this, 36, MiniMessage.miniMessage().deserialize("Island - challenges menu")); } @Override public void decorate(Player player) { + resetButtons(); makeMenuBar(player); + // Add buttons to change difficulty + if (!challengeDifficulty.equals(ChallengeDifficulty.EASY)) + addButton(getInventory().getSize() - 5, createMenuButton(Material.WHITE_BANNER, "Easy Challenges", new ArrayList<>(), event -> { + changeDifficulty(player, ChallengeDifficulty.EASY); + })); + if (!challengeDifficulty.equals(ChallengeDifficulty.MEDIUM)) + addButton(getInventory().getSize() - 4, createMenuButton(Material.GREEN_BANNER, "Medium Challenges", new ArrayList<>(), event -> { + changeDifficulty(player, ChallengeDifficulty.MEDIUM); + })); + if (!challengeDifficulty.equals(ChallengeDifficulty.HARD)) + addButton(getInventory().getSize() - 3, createMenuButton(Material.BLUE_BANNER, "Hard Challenges", new ArrayList<>(), event -> { + changeDifficulty(player, ChallengeDifficulty.HARD); + })); + if (!challengeDifficulty.equals(ChallengeDifficulty.LEGENDARY)) + addButton(getInventory().getSize() - 2, createMenuButton(Material.PURPLE_BANNER, "Legendary Challenges", new ArrayList<>(), event -> { + changeDifficulty(player, ChallengeDifficulty.LEGENDARY); + })); + if (!challengeDifficulty.equals(ChallengeDifficulty.EXOTIC)) + addButton(getInventory().getSize() - 1, createMenuButton(Material.YELLOW_BANNER, "Exotic Challenges", new ArrayList<>(), event -> { + changeDifficulty(player, ChallengeDifficulty.EXOTIC); + })); + + currentSlot = 0; + int startIndex = pageIndex * (getInventory().getSize() - 9); + List challenges = CometSkyBlockPlugin.instance().challengeHandler().getChallenges(challengeDifficulty).values().stream().toList(); + for (int i = startIndex; i < challenges.size(); i++) { + Challenge challenge = challenges.get(i); + int challengeCount = island.challengeCount(challenge.key()); + GUIButton guiButton = createChallengeButton(challenge, event -> { + // TODO -- Clean up + boolean repeat = challengeCount != 0; + List rewards = repeat ? challenge.repeatRewards() : challenge.rewards(); + boolean hasRequirements = false; + boolean takeItems = false; + switch (challenge.challengeType()) { + case ON_PLAYER -> { + hasRequirements = PlayerUtils.hasItems(player.getInventory(), challenge.requiredItems()); + takeItems = true; + } + case ON_ISLAND -> { + if (!player.getWorld().getUID().equals(island.islandUUID())) { + player.sendRichMessage(CometSkyBlockPlugin.instance().messagesConfiguration().get().island().notOnIsland()); + return; + } + Object2IntMap blocks = PlayerUtils.getBlocks(player, challenge.radius()); + for (ItemStack itemStack : challenge.requiredItems()) { + if (blocks.getInt(itemStack.getType()) < itemStack.getAmount()) { + hasRequirements = false; + break; + } + + hasRequirements = true; + } + } + case ISLAND_LEVEL -> { + hasRequirements = island.level() >= challenge.requiredLevel(); + if (!hasRequirements) { + player.sendRichMessage("You do no have the required island level."); + return; + } + } + default -> { + return; + } + } + if (!hasRequirements) { + player.sendRichMessage("You do not have all the required items."); + return; + } + if (!PlayerUtils.hasRoom(player.getInventory(), rewards)) { + player.sendRichMessage("You do not have room for the reward items."); + return; + } + island.challengeCount(challenge.key(), challengeCount + 1); + TagResolver placeholders = TagResolver.resolver( + Placeholder.component("player", player.displayName()), + Placeholder.unparsed("challengename", challenge.challengeName()) + ); + island.broadCast(" has completed challenge .", placeholders); + PlayerUtils.giveItems(player.getInventory(), rewards); + if (takeItems) { + PlayerUtils.takeItems(player.getInventory(), challenge.requiredItems()); + } + this.decorate(player); + }, challengeCount); + if (!addItem(guiButton)) { + createNextPageButton(player); + break; + } + } + if (pageIndex > 0) { + createPrevPageButton(player); + } super.decorate(player); } + protected void changeDifficulty(Player player, ChallengeDifficulty challengeDifficulty) { + int requiredLevel = CometSkyBlockPlugin.instance().challengeLoader().requiredLevel(challengeDifficulty); + if (!(island.level() >= requiredLevel)) { + TagResolver placeholders = TagResolver.resolver( + Placeholder.unparsed("requiredlevel", requiredLevel + ""), + Placeholder.unparsed("difficulty", challengeDifficulty.toString().toLowerCase()) + ); + player.sendRichMessage("You need island level to access challenges", placeholders); + return; + } + this.challengeDifficulty = challengeDifficulty; + this.decorate(player); + } + + private GUIButton createChallengeButton(Challenge challenge, Consumer eventConsumer, int challengeCount) { + return new GUIButton() + .creator(player -> { + ItemStack itemStack = new ItemStack(challenge.shownItem(), Math.min(Math.max(1, challengeCount), 64)); + itemStack.editMeta(meta -> { + MiniMessage miniMessage = MiniMessage.miniMessage(); + meta.displayName(miniMessage.deserialize(challenge.challengeName())); + List lore = new ArrayList<>(StringUtil.splitMiniMessageString(challenge.description()).stream().map(miniMessage::deserialize).toList()); + String prefix = switch (challenge.challengeType()) { + case ON_PLAYER -> "Required items:"; + case ON_ISLAND -> "Build:"; + case ISLAND_LEVEL -> "Requires"; + }; + lore.addAll(StringUtil.splitMiniMessageString(prefix + " " + challenge.requiredText()).stream().map(miniMessage::deserialize).toList()); + lore.add(miniMessage.deserialize("Reward: " + StringUtil.splitMiniMessageString( + (challenge.challengeType().equals(ChallengeType.ON_PLAYER) && challengeCount != 0) ? + challenge.repeatRewardText() : challenge.rewardText()))); + // TODO -- Add glow for challenges that are completed +// if (challengeCount > 0 || challenge.repeatRewards() != null && !challenge.repeatRewards().isEmpty()) { +// meta.addEnchant(Enchantment.MENDING, 1, true); +// } + meta.addItemFlags(ItemFlag.values()); + + meta.lore(lore); + }); + return itemStack; + }) + .consumer(eventConsumer); + } } diff --git a/plugin/src/main/java/com/alttd/cometskyblock/commands/admin/ReloadCommand.java b/plugin/src/main/java/com/alttd/cometskyblock/commands/admin/ReloadCommand.java new file mode 100644 index 0000000..06314f8 --- /dev/null +++ b/plugin/src/main/java/com/alttd/cometskyblock/commands/admin/ReloadCommand.java @@ -0,0 +1,30 @@ +package com.alttd.cometskyblock.commands.admin; + +import com.alttd.cometskyblock.CometSkyBlockPlugin; +import com.alttd.cometskyblock.commands.SubCommand; +import org.bukkit.command.CommandSender; + +public class ReloadCommand extends SubCommand { + + public ReloadCommand(CometSkyBlockPlugin plugin) { + super(plugin, "reload"); + } + + @Override + public boolean execute(CommandSender sender, String... args) { + if (args.length < 1) { + sender.sendRichMessage("Wrong usage try /skyblock reload config|challenges|all"); + return true; + } + switch (args[0].toLowerCase()) { + case "all" -> { + plugin.loadConfiguration(); + plugin.loadChallenges(); + } + case "config" -> plugin.loadConfiguration(); + case "challenges" -> plugin.loadChallenges(); + default -> sender.sendRichMessage("Wrong usage try /skyblock reload config|challenges|all"); + } + return true; + } +} diff --git a/plugin/src/main/java/com/alttd/cometskyblock/commands/admin/SkyBlockCommand.java b/plugin/src/main/java/com/alttd/cometskyblock/commands/admin/SkyBlockCommand.java new file mode 100644 index 0000000..6097d75 --- /dev/null +++ b/plugin/src/main/java/com/alttd/cometskyblock/commands/admin/SkyBlockCommand.java @@ -0,0 +1,24 @@ +package com.alttd.cometskyblock.commands.admin; + +import com.alttd.cometskyblock.CometSkyBlockPlugin; +import com.alttd.cometskyblock.commands.SubCommand; +import org.bukkit.command.CommandSender; + +public class SkyBlockCommand extends SubCommand { + + private final CometSkyBlockPlugin plugin; + + public SkyBlockCommand(CometSkyBlockPlugin plugin) { + super(plugin, "skyblock"); + this.plugin = plugin; + + registerSubCommand(new ReloadCommand(plugin)); + } + + @Override + public boolean execute(CommandSender sender, String... args) { + sender.sendRichMessage("Wrong usage try /skyblock reload config|challenges"); + return true; + } + +} diff --git a/plugin/src/main/java/com/alttd/cometskyblock/commands/challenges/ChallengeCommand.java b/plugin/src/main/java/com/alttd/cometskyblock/commands/challenges/ChallengeCommand.java index 478b182..6120996 100644 --- a/plugin/src/main/java/com/alttd/cometskyblock/commands/challenges/ChallengeCommand.java +++ b/plugin/src/main/java/com/alttd/cometskyblock/commands/challenges/ChallengeCommand.java @@ -1,7 +1,9 @@ package com.alttd.cometskyblock.commands.challenges; import com.alttd.cometskyblock.CometSkyBlockPlugin; +import com.alttd.cometskyblock.challenges.ChallengesGUI; import com.alttd.cometskyblock.commands.PlayerSubCommand; +import com.alttd.cometskyblock.island.Island; import com.alttd.cometskyblock.island.IslandPlayer; import org.bukkit.entity.Player; @@ -13,11 +15,19 @@ public class ChallengeCommand extends PlayerSubCommand { super(plugin, "challenge"); this.plugin = plugin; } - // TODO - Challenges, ChallengesGUI and ChallengesCommand + @Override public boolean execute(Player player, IslandPlayer islandPlayer, String[] args) { - // open challenge inventory - not implemented yet -- TODO - player.sendRichMessage("Not implemented yet, please wait for a future update."); + if (islandPlayer.islandId() == 0) { + player.sendRichMessage("You should create an island before doing this. Do /island go"); + return true; + } + Island island = Island.getIsland(islandPlayer.islandUUID()); + if (island == null) { + player.sendRichMessage("Could not load your island. Contact an administrator"); + return true; + } + new ChallengesGUI(island).open(player); return true; } diff --git a/plugin/src/main/java/com/alttd/cometskyblock/configuration/ChallengesConfiguration.java b/plugin/src/main/java/com/alttd/cometskyblock/configuration/ChallengesConfiguration.java deleted file mode 100644 index 4343ebe..0000000 --- a/plugin/src/main/java/com/alttd/cometskyblock/configuration/ChallengesConfiguration.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.alttd.cometskyblock.configuration; - -import com.alttd.cometskyblock.challenges.Challenge; -import lombok.Getter; -import org.spongepowered.configurate.objectmapping.ConfigSerializable; - -import java.util.ArrayList; -import java.util.List; - -@ConfigSerializable -@Getter -@SuppressWarnings({"CanBeFinal", "FieldMayBeFinal"}) -public class ChallengesConfiguration implements Configuration { - - private List challenges = new ArrayList<>(); -} diff --git a/plugin/src/main/java/com/alttd/cometskyblock/gui/GUIInventory.java b/plugin/src/main/java/com/alttd/cometskyblock/gui/GUIInventory.java index d4cb94d..dd2ce2e 100644 --- a/plugin/src/main/java/com/alttd/cometskyblock/gui/GUIInventory.java +++ b/plugin/src/main/java/com/alttd/cometskyblock/gui/GUIInventory.java @@ -65,11 +65,16 @@ public abstract class GUIInventory implements GUI, InventoryHolder { } + public void resetButtons() { + this.buttons.clear(); + } + public void addButton(int slot, GUIButton button) { this.buttons.put(slot, button); } protected void decorate(Player player) { + this.inventory.clear(); this.buttons.forEach((slot, button) -> { ItemStack icon = button.iconCreator().apply(player); this.inventory.setItem(slot, icon); @@ -99,11 +104,16 @@ public abstract class GUIInventory implements GUI, InventoryHolder { } protected void makeMenuBar(Player player) { + makeMenuBar(player, false); + } + + protected void makeMenuBar(Player player, boolean makeTopBar) { for (int i = inventory.getSize() - 9; i < inventory.getSize(); ++i) { addButton(i, createMenuButton(Material.BLACK_STAINED_GLASS_PANE, "", new ArrayList<>(), event -> {})); } createMainMenuButton(player, inventory.getSize() - 9); - makeTopBar(); + if (makeTopBar) + makeTopBar(); } protected void makeTopBar() { @@ -129,14 +139,14 @@ public abstract class GUIInventory implements GUI, InventoryHolder { } protected void createPrevPageButton(Player player) { - addButton(48, createMenuButton(Material.PAPER, "<< Previous Page", new ArrayList<>(), event -> { + addButton(inventory.getSize() - 8, createMenuButton(Material.PAPER, "<< Previous Page", new ArrayList<>(), event -> { --pageIndex; decorate(player); })); } protected void createNextPageButton(Player player) { - addButton(50, createMenuButton(Material.PAPER, "Next Page >>", new ArrayList<>(), event -> { + addButton(inventory.getSize() - 7, createMenuButton(Material.PAPER, "Next Page >>", new ArrayList<>(), event -> { ++pageIndex; decorate(player); })); diff --git a/plugin/src/main/java/com/alttd/cometskyblock/island/Island.java b/plugin/src/main/java/com/alttd/cometskyblock/island/Island.java index 77213f9..1cd42aa 100644 --- a/plugin/src/main/java/com/alttd/cometskyblock/island/Island.java +++ b/plugin/src/main/java/com/alttd/cometskyblock/island/Island.java @@ -251,4 +251,14 @@ public class Island extends YamlConfiguration { public IslandRecord toRecord() { return new IslandRecord(islandId(), islandName(), level()); } + + + public int challengeCount(String key) { + return getInt("challenges." + key, 0); + } + + public void challengeCount(String key, int amount) { + set("challenges." + key, amount); + save(); + } } diff --git a/plugin/src/main/java/com/alttd/cometskyblock/island/gui/IslandGUI.java b/plugin/src/main/java/com/alttd/cometskyblock/island/gui/IslandGUI.java index 9b92a47..d7e6c7e 100644 --- a/plugin/src/main/java/com/alttd/cometskyblock/island/gui/IslandGUI.java +++ b/plugin/src/main/java/com/alttd/cometskyblock/island/gui/IslandGUI.java @@ -24,7 +24,7 @@ public class IslandGUI extends GUIInventory { @Override public void decorate(Player player) { - makeMenuBar(player); + makeMenuBar(player, true); // Island GO addButton(10, createMenuButton(Material.GRASS_BLOCK, "Visit your island!", new ArrayList<>(), event -> { diff --git a/plugin/src/main/java/com/alttd/cometskyblock/island/gui/MembersGUI.java b/plugin/src/main/java/com/alttd/cometskyblock/island/gui/MembersGUI.java index 6544c66..63b80df 100644 --- a/plugin/src/main/java/com/alttd/cometskyblock/island/gui/MembersGUI.java +++ b/plugin/src/main/java/com/alttd/cometskyblock/island/gui/MembersGUI.java @@ -33,7 +33,7 @@ public class MembersGUI extends GUIInventory { @Override public void decorate(Player player) { currentSlot = 9; - makeMenuBar(player); + makeMenuBar(player, true); int startIndex = pageIndex * 45; for (int i = startIndex; i < island.members().size(); i++) { diff --git a/plugin/src/main/java/com/alttd/cometskyblock/island/gui/SettingsGUI.java b/plugin/src/main/java/com/alttd/cometskyblock/island/gui/SettingsGUI.java index 87f9527..6394dec 100644 --- a/plugin/src/main/java/com/alttd/cometskyblock/island/gui/SettingsGUI.java +++ b/plugin/src/main/java/com/alttd/cometskyblock/island/gui/SettingsGUI.java @@ -27,7 +27,7 @@ public class SettingsGUI extends GUIInventory { @Override public void decorate(Player player) { - makeMenuBar(player); + makeMenuBar(player, true); // setHome addButton(10, createMenuButton(Material.GRASS_BLOCK, "Set your IslandSpawn location!", List.of( "This sets your island spawn location to your current location." diff --git a/plugin/src/main/java/com/alttd/cometskyblock/island/gui/UpgradesGUI.java b/plugin/src/main/java/com/alttd/cometskyblock/island/gui/UpgradesGUI.java index 9f5cfc6..229886d 100644 --- a/plugin/src/main/java/com/alttd/cometskyblock/island/gui/UpgradesGUI.java +++ b/plugin/src/main/java/com/alttd/cometskyblock/island/gui/UpgradesGUI.java @@ -32,7 +32,7 @@ public class UpgradesGUI extends GUIInventory { @Override public void decorate(Player player) { - makeMenuBar(player); + makeMenuBar(player, true); MessageConfiguration.Island islandMessages = CometSkyBlockPlugin.instance().messagesConfiguration().get().island(); // Todo - move to handlers, add costs, validation ... // WorldBorder diff --git a/plugin/src/main/java/com/alttd/cometskyblock/util/PlayerUtils.java b/plugin/src/main/java/com/alttd/cometskyblock/util/PlayerUtils.java new file mode 100644 index 0000000..df594e6 --- /dev/null +++ b/plugin/src/main/java/com/alttd/cometskyblock/util/PlayerUtils.java @@ -0,0 +1,115 @@ +package com.alttd.cometskyblock.util; + +import it.unimi.dsi.fastutil.objects.Object2IntMap; +import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap; +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import java.util.List; +import java.util.Map; + +public class PlayerUtils { + + public static boolean hasItems(Inventory inventory, List items) { + if (inventory == null || items == null) + return false; + + for (ItemStack itemStack : items) { + int requiredAmount = itemStack.getAmount(); + for (int slot = 0; slot < inventory.getSize(); slot++) { + if (inventory.getItem(slot) != null) { + if (inventory.getItem(slot).getType().equals(itemStack.getType())) { + requiredAmount = requiredAmount - inventory.getItem(slot).getAmount(); + } + } + } + if (requiredAmount > 0) { + return false; + } + } + return true; + } + + public static boolean hasRoom(Inventory inventory, List items) { + for (ItemStack itemStack : items) { + if (!hasRoom(inventory, itemStack)) + return false; + } + return true; + } + + public static boolean hasRoom(Inventory inventory, ItemStack itemStack) { + if (inventory == null) + return false; + + if (itemStack.getAmount() <= 0) + return true; + + Inventory test = Bukkit.createInventory(null, 36); + test.setContents(inventory.getContents()); + Map remainingItems = test.addItem(itemStack); + + return remainingItems.isEmpty(); + } + + public static void giveItems(Inventory inventory, List items) { + for (ItemStack current : items) { + inventory.addItem(current.clone()); + } + } + + public static Object2IntMap getBlocks(Player player, Integer radius) { + Object2IntMap blocks = new Object2IntOpenHashMap<>(); + blocks.defaultReturnValue(0); + + Location loc = player.getLocation(); + int blockX = loc.getBlockX(); + int blockY = loc.getBlockY(); + int blockZ = loc.getBlockZ(); + + for (int x = blockX - radius; x < blockX + radius; x++ ) { + for (int z = blockZ - radius; z < blockZ + radius; z++) { + if (player.getWorld().isChunkLoaded(x >> 4, z >> 4)) { + for (int y = blockY - radius; y < blockY + radius; y++) { + Material blockType = player.getWorld().getBlockAt(x, y, z).getType(); + if (!blockType.isAir()) { + blocks.put(blockType, blocks.getInt(blockType) + 1); + } + } + } + } + } + return blocks; + } + + public static void takeItems(Inventory inventory, List items) { + if (inventory == null || items == null) + return; + + for (ItemStack itemStack : items) { + int amount = itemStack.getAmount(); + for (int slot = 0; slot < inventory.getSize(); slot++) { + ItemStack inventoryItem = inventory.getItem(slot); + if (inventoryItem != null) { + if (inventoryItem.getType().equals(itemStack.getType())) { + int newAmount = inventoryItem.getAmount() - amount; + if (newAmount > 0) { + inventoryItem.setAmount(newAmount); + break; + } else { + inventory.clear(slot); + amount = amount - inventoryItem.getAmount(); + if (amount == 0) { + break; + } + } + } + } + } + } + } +} diff --git a/plugin/src/main/java/com/alttd/cometskyblock/util/StringUtil.java b/plugin/src/main/java/com/alttd/cometskyblock/util/StringUtil.java new file mode 100644 index 0000000..94d61c2 --- /dev/null +++ b/plugin/src/main/java/com/alttd/cometskyblock/util/StringUtil.java @@ -0,0 +1,109 @@ +package com.alttd.cometskyblock.util; + +import java.util.*; +import java.util.stream.Collectors; + +public class StringUtil { + + /** + * Splits a mini message string into smaller chunks. It re-opens any unclosed tags it encounters for + * each part of the string added to the list + * + * @param string the mini message string to split + * @return a list of the split strings + */ + public static List splitMiniMessageString(String string) { + Set tags = findTags(string); + List splitString = new ArrayList<>(); + Stack openTags = new Stack<>(); + Stack previousOpenTags = new Stack<>(); + int startCurString = 0; + int len = 0; + int i = 0; + for (; i < string.length(); i++) { + while (string.charAt(i) == '<') { + i = skipTags(i, string, openTags, tags); + } + len++; + if (len != 30) { + continue; + } + while (i < string.length() && !Character.isWhitespace(string.charAt(i))) { + while (string.charAt(i) == '<') { + i = skipTags(i, string, openTags, tags); + } + if (Character.isWhitespace(string.charAt(i))) { + break; + } + i++; + } + String prependTags = previousOpenTags.stream().map(str -> "<" + str + ">").collect(Collectors.joining("")); + splitString.add(prependTags + string.substring(startCurString, i)); + previousOpenTags.clear(); + previousOpenTags.addAll(openTags); + startCurString = ++i; + len = 0; + } + if (startCurString < string.length()) { + String prependTags = previousOpenTags.stream().map(str -> "<" + str + ">").collect(Collectors.joining("")); + splitString.add(prependTags + string.substring(startCurString)); + } + return splitString; + } + + private static int skipTags(int i, String string, Stack openTags, Set tags) { + StringBuilder tagBuilder = new StringBuilder(); + i++; + for (; i < string.length() && string.charAt(i) != '>'; i++) { + tagBuilder.append(string.charAt(i)); + } + String tagString = tagBuilder.toString(); + if (!tagString.isEmpty() && tagString.charAt(0) == '/') { + if (!openTags.isEmpty() && openTags.peek().contentEquals(tagString.substring(1))) { + openTags.pop(); + } + } else if (tags.contains(tagString)) { + openTags.add(tagString); + } + tagBuilder.setLength(0); + return Math.min(++i, string.length() - 1); + } + + public static Set findTags(String string) { + List tags = new ArrayList<>(); + HashSet closedTags = new HashSet<>(); + StringBuilder tag = new StringBuilder(); + boolean tagFound = false; + + for (int i = 0; i < string.length(); i++) { + if (string.charAt(i) == '<') { + tagFound = true; + continue; + } + + if (string.charAt(i) == '>') { + tagFound = false; + + if (tag.charAt(0) != '/') { + tags.add(tag.toString()); + } else { + String closingTag = tag.substring(1); + if (tags.contains(closingTag)) { + tags.remove(closingTag); + closedTags.add(closingTag); + } + } + + tag.setLength(0); + continue; + } + + if (tagFound) { + tag.append(string.charAt(i)); + } + } + + return closedTags; + } + +} diff --git a/plugin/src/main/resources/challenges.yml b/plugin/src/main/resources/challenges.yml new file mode 100644 index 0000000..153802f --- /dev/null +++ b/plugin/src/main/resources/challenges.yml @@ -0,0 +1,219 @@ +level: + easy: 0 + medium: 20 + hard: 50 + legendary: 75 + exotic: 100 +easy: + CobblestoneGenerator: + name: Cobblestone Generator + key: easy_cobblestone_generator + shown-item: Cobblestone + challenge-type: ON_PLAYER + description: "Build a cobblestone generator and farm 64 cobblestone." + required-text: "64 cobblestone" + required-items: + - Cobblestone;64 + reward-text: "3 Leather" + reward-items: + - Leather;3 + repeat-reward-text: "1 Leather" + repeat-reward-items: + - Leather;1 + CropFarmer: + name: Wheat Farmer + key: easy_crop_farmer + shown-item: Wheat + challenge-type: ON_PLAYER + description: "Collect 64 wheat." + required-text: "64 wheat" + required-items: + - Wheat;64 + reward-text: "variety seed pack." + reward-items: + - Melon_seeds;1 + - pumpkin_seeds;1 + repeat-reward-text: "variety seed pack." + repeat-reward-items: + - Melon_seeds;1 + - pumpkin_seeds;1 + CactusFarmer: + name: Cactus Farmer + key: easy_cactus_farmer + shown-item: Cactus + challenge-type: ON_PLAYER + description: "Collect 64 cactus." + required-text: "64 cactus" + required-items: + - Cactus;64 + reward-text: "5 sand" + reward-items: + - Sand;5 + repeat-reward-text: "1 sand" + repeat-reward-items: + - Sand;1 + AppleFarmer: + name: Apple Farmer + key: medium_apple_farmer + shown-item: Apple + challenge-type: ON_PLAYER + description: "Collect 8 apples." + required-text: "8 apples" + required-items: + - Apple;8 + reward-text: "One sapling from each type." + reward-items: + - Oak_Sapling;1 + - Spruce_Sapling;1 + - Birch_Sapling;1 + - Jungle_Sapling;1 + - Acacia_Sapling;1 + - Dark_Oak_Sapling;1 + repeat-reward-text: "One sapling from each type." + repeat-reward-items: + - Oak_Sapling;1 + - Spruce_Sapling;1 + - Birch_Sapling;1 + - Jungle_Sapling;1 + - Acacia_Sapling;1 + - Dark_Oak_Sapling;1 +medium: + Lumberjack: + name: Lumberjack + key: medium_lumberjack + shown-item: Diamond_axe + challenge-type: ON_PLAYER + description: "Obtain 32 oak-, spruce-, birch-, jungle-, acacia-, and dark oak logs." + required-text: "32 oak logs, 32 spruce logs, 32 birch logs, 32 jungle logs, 32 acacia logs, 32 dark oak logs" + required-items: + - Oak_Log;32 + - Spruce_Log;32 + - Birch_Log;32 + - Jungle_Log;32 + - Acacia_Log;32 + - Dark_Oak_Log;32 + reward-text: "16 gilded blackstone" + reward-items: + - Gilded_Blackstone;16 + repeat-reward-text: "8 gilded blackstone" + repeat-reward-items: + - Gilded_Blackstone;8 + AppleFarmer: + name: Apple Farmer + key: medium_apple_farmer + shown-item: Apple + challenge-type: ON_PLAYER + description: "Collect 8 apples." + required-text: "8 apples" + required-items: + - Apple;8 + reward-text: "One sapling from each type." + reward-items: + - Oak_Sapling;1 + - Spruce_Sapling;1 + - Birch_Sapling;1 + - Jungle_Sapling;1 + - Acacia_Sapling;1 + - Dark_Oak_Sapling;1 + repeat-reward-text: "One sapling from each type." + repeat-reward-items: + - Oak_Sapling;1 + - Spruce_Sapling;1 + - Birch_Sapling;1 + - Jungle_Sapling;1 + - Acacia_Sapling;1 + - Dark_Oak_Sapling;1 + MonsterHunter: + name: Monster Hunter + key: medium_monster_hunter + shown-item: Bone + challenge-type: ON_PLAYER + description: "Kill monsters and collect 64 rotten flesh, bones and arrows." + required-text: "64 rotten flesh, bones and arrows." + required-items: + - Rotten_Flesh;64 + - Bone;64 + - Arrow;64 + reward-text: "2 experience bottles!" + reward-items: + - experience_bottle;2 + repeat-reward-text: "One experience bottles!" + repeat-reward-items: + - experience_bottle + Exterminator: + name: Exterminator + key: medium_exterminator + shown-item: Cobweb + challenge-type: ON_PLAYER + description: "Kill spiders and collect their drops." + required-text: "64 strings, 32 spider eyes" + required-items: + - String;64 + - Spider_Eye;32 + reward-text: "2 Cobwebs" + reward-items: + - Cobweb;8 + repeat-reward-text: "One cobweb" + repeat-reward-items: + - Cobweb + FisherMan: + name: Fisherman + key: medium_fisherman + shown-item: Fishing_rod + challenge-type: ON_PLAYER + description: "This could have been sushi" + required-text: "10 cooked cod and 10 cooked salmon" + required-items: + - Cooked_Cod;10 + - Cooked_Salmon;10 + reward-text: "2 experience bottles!" + reward-items: + - experience_bottle;2 + repeat-reward-text: "One experience bottles!" + repeat-reward-items: + - experience_bottle +hard: + NetherLumberjack: + name: Nether Lumberjack + key: hard_nether_lumberjack + shown-item: Netherite_axe + challenge-type: ON_PLAYER + description: "Obtain 64 crimson stems and 64 warped stems" + required-text: "64 crimson stems, 64 warped stems" + required-items: + - Crimson_Stem;64 + - Warped_Stem;64 + reward-text: "16 gilded blackstone" + reward-items: + - Gilded_Blackstone;16 + repeat-reward-text: "8 gilded blackstone" + repeat-reward-items: + - Gilded_Blackstone;8 +legendary: + BeastSlayer: + name: Beast Slayer + key: legendary_beast_slayer + shown-item: Beacon + challenge-type: ON_ISLAND + description: "Place a beacon on your island." + required-text: "One beacon" + required-items: + - Beacon;1 + reward-text: "One Nether Star" + reward-items: + - Nether_star + repeat-reward-text: "1 Diamond_Block" + repeat-reward-items: + - Diamond_Block;1 +exotic: + Babylon: + name: Babylon + key: exotic_babylon + shown-item: bedrock + challenge-type: ISLAND_LEVEL + description: "Open the gates of Babylon!" + required-text: "Island level 1000" + reward-text: "1 bedrock" + reward-items: + - Bedrock;1 + required-level: 1000 diff --git a/plugin/src/test/java/StringUtilTest.java b/plugin/src/test/java/StringUtilTest.java new file mode 100644 index 0000000..a1cb8fd --- /dev/null +++ b/plugin/src/test/java/StringUtilTest.java @@ -0,0 +1,91 @@ +import com.alttd.cometskyblock.util.StringUtil; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.*; + +public class StringUtilTest { + + /** + * StringUtilTest tests the splitString method in the StringUtil class. + * The splitString method is designed to split a string, making them shorter and more suitable + * for use in minecraft lore. + */ + + @Test + public void testSplitString() { + String testString = "This is a long string that needs to be split into multiple smaller chunks."; + List expectedOutput = List.of( + "This is a long string that needs", + "to be split into multiple smaller", + "chunks."); + assertEquals(expectedOutput, StringUtil.splitMiniMessageString(testString)); + } + + @Test + public void testSplitStringWithMarkups() { + String testString = "This is a long tag string that needs to be split into smaller chunks."; + List expectedOutput = List.of( + "This is a long tag string that", + "needs to be split into", + "smaller chunks."); + assertEquals(expectedOutput, StringUtil.splitMiniMessageString(testString)); + } + + @Test + public void testUsedString() { + String testString = "Required items: 64 Cobblestone"; + List expectedOutput = List.of("Required items: 64 Cobblestone"); + assertEquals(expectedOutput, StringUtil.splitMiniMessageString(testString)); + } + + @Test + public void testSplitStringWithShortString() { + String testString = "Short string."; + List expectedOutput = List.of("Short string."); + assertEquals(expectedOutput, StringUtil.splitMiniMessageString(testString)); + } + + @Test + public void testSplitStringWithManyTagsString() { + String testString = "This is a long string with a and a lot of text"; + List expectedOutput = List.of("This is a long string with a ", + "and a lot of text"); + assertEquals(expectedOutput, StringUtil.splitMiniMessageString(testString)); + } + + @Test + public void testFindTagsWithCorrectTags() { + String input = "This is a test string."; + Set result = StringUtil.findTags(input); + assertTrue(result.containsAll(List.of("red", "blue"))); + assertEquals(2, result.size()); + } + + @Test + public void testFindTagsWithUnclosedTag() { + String input = "This is a test string."; + Set result = StringUtil.findTags(input); + assertTrue(result.contains("red")); + assertFalse(result.contains("placeholder")); + assertEquals(1, result.size()); + } + + @Test + public void testFindTagsWithNoTags() { + String input = "This is a test string."; + Set result = StringUtil.findTags(input); + assertTrue(result.isEmpty()); + } + + @Test + public void testFindTagsWithManyTags() { + String input = "This is a test string."; + Set result = StringUtil.findTags(input); + assertTrue(result.containsAll(List.of("red", "blue", "green", "orange"))); + assertFalse(result.contains("not_a_color")); + assertEquals(4, result.size()); + } +} \ No newline at end of file