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 <teriuihi@alttd.com>
This commit is contained in:
destro174 2024-02-25 14:02:51 +01:00 committed by GitHub
parent 53d4549573
commit efc6b62b2f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
24 changed files with 1128 additions and 56 deletions

View File

@ -0,0 +1,9 @@
package com.alttd.cometskyblock.api.challenges;
public enum ChallengeDifficulty {
EASY,
MEDIUM,
HARD,
LEGENDARY,
EXOTIC
}

View File

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

View File

@ -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"
}

View File

@ -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> pluginConfiguration;
@Getter private ConfigurationContainer<DatabaseConfiguration> databaseConfiguration;
@Getter private ConfigurationContainer<MessageConfiguration> messagesConfiguration;
@Getter private ConfigurationContainer<ChallengesConfiguration> challengesConfiguration;
@Getter private ConfigurationContainer<CobblestoneGeneratorConfiguration> cobblestoneGeneratorConfiguration;
@Getter private ConfigurationContainer<WorldBorderConfiguration> 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() {

View File

@ -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<ItemStack> rewards;
private List<ItemStack> repeatRewards;
private List<ItemStack> neededItems;
private Integer neededLevel;
private Integer slot;
private List<ItemStack> requiredItems;
private Integer requiredLevel;
private Integer radius;
}

View File

@ -1,4 +0,0 @@
package com.alttd.cometskyblock.challenges;
public class ChallengeDifficulty {
}

View File

@ -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<String, Challenge> easyChallenges = new HashMap<>();
private final Map<String, Challenge> mediumChallenges = new HashMap<>();
private final Map<String, Challenge> hardChallenges = new HashMap<>();
private final Map<String, Challenge> impossibleChallenges = new HashMap<>();
private final Map<String, Challenge> 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<String, Challenge> getChallenges(ChallengeDifficulty challengeDifficulty) {
switch (challengeDifficulty) {
case MEDIUM -> {
return mediumChallenges;
}
case HARD -> {
return hardChallenges;
}
case LEGENDARY -> {
return impossibleChallenges;
}
case EXOTIC -> {
return exoticChallenges;
}
default -> {
return easyChallenges;
}
}
}
}

View File

@ -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<ChallengeDifficulty> 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<String> 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<String> rewards = section.getStringList(currentChallenge + ".reward-items");
List<String> repeatRewards = section.getStringList(currentChallenge + ".repeat-reward-items");
List<String> 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<ItemStack> createItems(List<String> items) {
List<ItemStack> 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;
}
}

View File

@ -1,12 +0,0 @@
package com.alttd.cometskyblock.challenges;
import org.spongepowered.configurate.objectmapping.ConfigSerializable;
@ConfigSerializable
public enum ChallengeType {
onPlayer,
onIsland,
islandLevel
}

View File

@ -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("<red>GUI"));
return Bukkit.createInventory(this, 36, MiniMessage.miniMessage().deserialize("<yellow>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<Challenge> 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<ItemStack> 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<Material> 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("<red>You do no have the required island level.");
return;
}
}
default -> {
return;
}
}
if (!hasRequirements) {
player.sendRichMessage("<red>You do not have all the required items.");
return;
}
if (!PlayerUtils.hasRoom(player.getInventory(), rewards)) {
player.sendRichMessage("<red>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("<player> has completed challenge <challengename>.", 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("<red>You need island level <requiredlevel> to access <difficulty> challenges", placeholders);
return;
}
this.challengeDifficulty = challengeDifficulty;
this.decorate(player);
}
private GUIButton createChallengeButton(Challenge challenge, Consumer<InventoryClickEvent> 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<Component> lore = new ArrayList<>(StringUtil.splitMiniMessageString(challenge.description()).stream().map(miniMessage::deserialize).toList());
String prefix = switch (challenge.challengeType()) {
case ON_PLAYER -> "<yellow>Required items:</yellow>";
case ON_ISLAND -> "<yellow>Build:</yellow>";
case ISLAND_LEVEL -> "<yellow>Requires</yellow>";
};
lore.addAll(StringUtil.splitMiniMessageString(prefix + " " + challenge.requiredText()).stream().map(miniMessage::deserialize).toList());
lore.add(miniMessage.deserialize("<yellow>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);
}
}

View File

@ -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("<red>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("<red>Wrong usage try /skyblock reload config|challenges|all");
}
return true;
}
}

View File

@ -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("<red>Wrong usage try /skyblock reload config|challenges");
return true;
}
}

View File

@ -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("<red>Not implemented yet, please wait for a future update.");
if (islandPlayer.islandId() == 0) {
player.sendRichMessage("<red>You should create an island before doing this. Do /island go");
return true;
}
Island island = Island.getIsland(islandPlayer.islandUUID());
if (island == null) {
player.sendRichMessage("<red>Could not load your island. Contact an administrator");
return true;
}
new ChallengesGUI(island).open(player);
return true;
}

View File

@ -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<Challenge> challenges = new ArrayList<>();
}

View File

@ -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, "<yellow><< Previous Page", new ArrayList<>(), event -> {
addButton(inventory.getSize() - 8, createMenuButton(Material.PAPER, "<yellow><< Previous Page", new ArrayList<>(), event -> {
--pageIndex;
decorate(player);
}));
}
protected void createNextPageButton(Player player) {
addButton(50, createMenuButton(Material.PAPER, "<yellow>Next Page >>", new ArrayList<>(), event -> {
addButton(inventory.getSize() - 7, createMenuButton(Material.PAPER, "<yellow>Next Page >>", new ArrayList<>(), event -> {
++pageIndex;
decorate(player);
}));

View File

@ -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();
}
}

View File

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

View File

@ -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++) {

View File

@ -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(
"<white>This sets your island spawn location to your current location."

View File

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

View File

@ -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<ItemStack> 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<ItemStack> 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<Integer, ItemStack> remainingItems = test.addItem(itemStack);
return remainingItems.isEmpty();
}
public static void giveItems(Inventory inventory, List<ItemStack> items) {
for (ItemStack current : items) {
inventory.addItem(current.clone());
}
}
public static Object2IntMap<Material> getBlocks(Player player, Integer radius) {
Object2IntMap<Material> 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<ItemStack> 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;
}
}
}
}
}
}
}
}

View File

@ -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<String> splitMiniMessageString(String string) {
Set<String> tags = findTags(string);
List<String> splitString = new ArrayList<>();
Stack<String> openTags = new Stack<>();
Stack<String> 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<String> openTags, Set<String> 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<String> findTags(String string) {
List<String> tags = new ArrayList<>();
HashSet<String> 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;
}
}

View File

@ -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 <rainbow>Babylon<rainbow>!"
required-text: "Island level 1000"
reward-text: "1 bedrock"
reward-items:
- Bedrock;1
required-level: 1000

View File

@ -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<String> 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 = "<blue>This is a <red>long tag string</red> <green>that</green> <orange>needs</orange> <rainbow>to be</rainbow> split into smaller chunks.</blue>";
List<String> expectedOutput = List.of(
"<blue>This is a <red>long tag string</red> <green>that</green>",
"<blue><orange>needs</orange> <rainbow>to be</rainbow> split into",
"<blue>smaller chunks.</blue>");
assertEquals(expectedOutput, StringUtil.splitMiniMessageString(testString));
}
@Test
public void testUsedString() {
String testString = "<yellow>Required items:</yellow> 64 Cobblestone";
List<String> expectedOutput = List.of("<yellow>Required items:</yellow> 64 Cobblestone");
assertEquals(expectedOutput, StringUtil.splitMiniMessageString(testString));
}
@Test
public void testSplitStringWithShortString() {
String testString = "Short string.";
List<String> expectedOutput = List.of("Short string.");
assertEquals(expectedOutput, StringUtil.splitMiniMessageString(testString));
}
@Test
public void testSplitStringWithManyTagsString() {
String testString = "<red><blue><green><not_a_color><orange>This is a long string with a <placeholder> and a lot of text</orange></green></blue></red>";
List<String> expectedOutput = List.of("<red><blue><green><not_a_color><orange>This is a long string with a <placeholder>",
"<red><blue><green><orange>and a lot of text</orange></green></blue></red>");
assertEquals(expectedOutput, StringUtil.splitMiniMessageString(testString));
}
@Test
public void testFindTagsWithCorrectTags() {
String input = "<red>This is a <blue>test</blue> string.</red>";
Set<String> result = StringUtil.findTags(input);
assertTrue(result.containsAll(List.of("red", "blue")));
assertEquals(2, result.size());
}
@Test
public void testFindTagsWithUnclosedTag() {
String input = "<red>This is a <placeholder> test string.</red>";
Set<String> 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<String> result = StringUtil.findTags(input);
assertTrue(result.isEmpty());
}
@Test
public void testFindTagsWithManyTags() {
String input = "<red><blue><green><not_a_color><orange>This is a test string.</orange></green></blue></red>";
Set<String> result = StringUtil.findTags(input);
assertTrue(result.containsAll(List.of("red", "blue", "green", "orange")));
assertFalse(result.contains("not_a_color"));
assertEquals(4, result.size());
}
}