diff --git a/README.md b/README.md index 4a05745..9b260c8 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,10 @@ Claims rewards for a specific player. Rewards are directly given to the user Clear all rewards for a specific player. Do not specify a player name if you want to clear rewards for all players. +### /cq history + +Opens a UI with historical quest information and data + ## Configuration The config.yml is used to configure quests in the plugin. Once the quests have been created you can start them using the /cq start command. @@ -147,6 +151,17 @@ objectives: description: Oak Saplings ``` +### Objective filters + +The quest above uses the `materials` filter so the first objective looks for `ACACIA_SAPLINGS` on a blockplace event and then second objective looks for `OAK_SAPLING`. Both objectives run concurrently. For block related events, materials is the only valid filter but there are six more filters you can use. If no constraints are provided (or you provide an invalid material name!), the quest will count all events of that type! + +- **materials** - use to specify the material type of blocks or items +- **entities** - use to specify the entity type of mobs for quests like mobKill, projectileKill and tameevent. It's also used in the catchfish event and the mythicmob event. +- **customNames** - use to specify the custom name of both mobs and items. This can be used for custom mobs or renamed items. +- **modelIds** - use to specify the model id of items. This can be used for custom items with a specific model id in custom model plugins like oraxen. +- **potions** - use to specify the potion type for the brewpotion event. For example, `AWKWARD` or `FIRE_RESISTANCE`. +- **enchants** - use to specify the enchantment type for the enchantitem event. For example, `DAMAGE_ALL` or `PROTECTION_ENVIRONMENTAL`. + The quest below requires you to kill 15 zombified piglins and 10 zombie pigmen. For mob related quests you can use the customNames field to specify the name of the mob you want to kill. If you want to kill a specific mob type you can use the entities field and specify the mob type from the [entity list](https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/entity/EntityType.html). ```yaml @@ -227,6 +242,10 @@ Rewards example ```yaml rewards: rewardsLimit: 5 # only top 5 contributors get the rewards, if not set or 0 then everyone gets rewards + rewardDisplay: # If this is set it will show the rewards in the quest view + - "REWARDS!" + - "1 Iron Sword" + - "50 Experience" rankedRewards: "1": # The key is the rank of the player experience: 100 @@ -275,26 +294,26 @@ rewards: displayName: "&capples" ``` -## Competitive vs. Cooperative +## Quest Types -There are two quest types to chose from: competitive and cooperative. If you're using the GUI which is opened from the /sp start command, after choosing the quest you'd like to run you're presented with a -GUI to pick the type. You can also do /cq start [QuestId] [coop/comp]. +There are three quest types to chose from: competitive, cooperative and collective. If you're using the GUI which is opened from the /sp start command, after choosing the quest you'd like to run you're presented with a +GUI to pick the type. You can also do /cq start [QuestId] [coop/comp/coll]. ### Cooperative -Cooperative quests involve everyone on the server working together to complete the goal. For example in the quest created in the configuration section, the quest will be complete once 100 zombies, pigs, and zombie pigmen have been killed. Note: This means 100 total of -any combination of zombies/pigs/zombie pigman kills that add to 100. If questDuration is set then the player's must complete the quest in the given time limit. -If the quest is not completed no rewards will be given out. +In cooperative quests player's work together to reach a goal. For example, if the quest has 3 objectives: kill 100 zombies, kill 100 pigmen and kill 200 skeletons. Players will need to reach all 3 of those objectives together to win the rewards. If questDuration is set, the player's must complete the quest before the time limit is reached to get the rewards. -Player's are rewarded based on how much they contribute to the quest. If the reward is 1000 -money and PlayerX kills 50 of the 100 zombies, PlayerX will receive 500. Item and command rewards are given to every player who contributes to the quest. You can use the **_rewardsLimit_** field in the rewards section of the config if you only want the top x number of players to get a reward. +Player's are rewarded based on how much they contribute to the quest. If the reward is 1000 money and PlayerX kills 50 of the 100 zombies, PlayerX will receive 500. Item and command rewards are given to every player who contributes to the quest. You can use the **_rewardsLimit_** field in the rewards section of the config if you only want the top x number of players to get a reward. You can also set rewards for specific ranks using the **_rankedRewards_** field. ### Competitive -Competitive quests put the players against each other to see who can complete a goal first. Using our zombie/pig example -from above, the quest will end once a single player gets 100 kills for the correct mob types. If a time limit is set and the quest ends before the goal is reached the top players will still get rewards based on their contributions. +In competitive quests, players compete to see who can reach the objective first. If goals are set on the objectives then the quest ends when the first player completes them. If a time limit is set then a goal is no required, and once the timer runs out players will be rewarded based on their ranking. -Money/experience awards work the same way as cooperative quests. If the money reward is 1000 the winner of the competition will get 1000. If Player1 came in second with 70 kills, he or she will get 700. The winner is the only person who will get items and commands in a competitive quest. Think of it as more of a challenge than a quest. +Money/experience rewards work the same way as cooperative quests. If the money reward is 1000 the winner of the competition will get 1000. If Player1 came in second with 70 kills, he or she will get 700. The winner is the only person who will get items and commands in a competitive quest, unless rewards are specified for specific rankings. + +### Collective + +In a collective quest, all player's are working toward the same goal. Like competitive quests, the goal is tracked individually for each player, but when a player reaches the goal the quest doesn't end. The quest must have a duration and will end when the timer runs out. All player's who reach the goal will be eligible for rewards, you can use ranked rewards to reward player's based on who finishes the quest first. ## Objectives @@ -320,7 +339,6 @@ Fishing: experience: 100 ``` -- **playerkill**: kill other players - **blockbreak**: break a block specified in the materials list in the configuration - **blockplace**: place a block specified in the materials list in the configuration - **projectilekill**: kill entities with a projectile specify entities in the configuration @@ -332,6 +350,10 @@ Fishing: - **enchantitem**: enchant something - **money**: players can contribute money with /cq deposit - **experience**: players must gather Minecraft experience +- **level**: players must levelup +- **furnace**: use furnace to smelt or cook an item +- **brewpotion**: brew potions +- **playerkill**: kill other players - **carvepumpkin**: use shears on a pumpkin - **mythicmob**: Kill mobs from the mythicmob plugin (requires MythicMobs to be installed) - **movement**: travel a certain distance in blocks @@ -486,7 +508,7 @@ You can directly configure schedules in `schedules.yml`: ```yaml 12345678-example: questId: "mining" # Quest identifier - mode: "coop" # Mode: "coop" or "comp" + mode: "coop" # Mode: "coop", "comp" or "coll" time: "14:30" # 24-hour format (HH:mm) scheduleType: "DAILY" # DAILY, WEEKLY, or CUSTOM_DAYS interval: 1 # Days between runs (for CUSTOM_DAYS only) diff --git a/pom.xml b/pom.xml index 403af17..8335666 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ me.wonka01 CommunityQuests - 2.11.5 + 2.12.0 diff --git a/src/main/java/me/knighthat/apis/files/Getters.java b/src/main/java/me/knighthat/apis/files/Getters.java index 5b5de82..04f5f16 100644 --- a/src/main/java/me/knighthat/apis/files/Getters.java +++ b/src/main/java/me/knighthat/apis/files/Getters.java @@ -1,8 +1,8 @@ package me.knighthat.apis.files; import lombok.NonNull; -import me.knighthat.apis.utils.Colorization; import me.wonka01.ServerQuests.ServerQuests; +import me.wonka01.ServerQuests.utils.Colorization; public abstract class Getters extends PluginFiles implements Colorization { diff --git a/src/main/java/me/knighthat/apis/menus/Menu.java b/src/main/java/me/knighthat/apis/menus/Menu.java index c8e10bb..2cb81dd 100644 --- a/src/main/java/me/knighthat/apis/menus/Menu.java +++ b/src/main/java/me/knighthat/apis/menus/Menu.java @@ -8,7 +8,8 @@ import me.wonka01.ServerQuests.questcomponents.QuestController; import me.wonka01.ServerQuests.questcomponents.QuestData; import me.wonka01.ServerQuests.questcomponents.schedulers.ParseDurationString; -import me.knighthat.apis.utils.Colorization; +import me.wonka01.ServerQuests.utils.Colorization; + import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.entity.Player; @@ -65,10 +66,15 @@ protected void onClose(@NonNull final InventoryCloseEvent event) { } public void open() { - setBorder(); - setButtons(); - setContents(); - owner.openInventory(inventory); + try { + setBorder(); + setButtons(); + setContents(); + owner.openInventory(inventory); + } catch (Exception exception) { + Bukkit.getLogger().warning("Error opening menu: " + exception.getMessage()); + exception.printStackTrace(); + } } protected @NonNull ItemStack createItemStack(@NonNull Material m, @NonNull String n) { diff --git a/src/main/java/me/wonka01/ServerQuests/ServerQuests.java b/src/main/java/me/wonka01/ServerQuests/ServerQuests.java index 188457c..0832bd8 100644 --- a/src/main/java/me/wonka01/ServerQuests/ServerQuests.java +++ b/src/main/java/me/wonka01/ServerQuests/ServerQuests.java @@ -2,21 +2,27 @@ import lombok.Getter; import lombok.NonNull; -import me.knighthat.apis.files.Config; import me.knighthat.apis.files.Messages; import me.knighthat.apis.menus.MenuEvents; import me.wonka01.ServerQuests.commands.CommandManager; +import me.wonka01.ServerQuests.configuration.Config; import me.wonka01.ServerQuests.configuration.JsonQuestSave; +import me.wonka01.ServerQuests.configuration.QuestHistoryManager; import me.wonka01.ServerQuests.events.*; import me.wonka01.ServerQuests.questcomponents.ActiveQuests; import me.wonka01.ServerQuests.questcomponents.bossbar.BarManager; import me.wonka01.ServerQuests.questcomponents.bossbar.BossbarPlayerInfo; import me.wonka01.ServerQuests.questcomponents.hologram.DecentHologramsDisplay; +import me.wonka01.ServerQuests.questcomponents.rewards.RewardJoinListener; import me.wonka01.ServerQuests.questcomponents.rewards.RewardManager; +import me.wonka01.bStats.Metrics; import me.wonka01.placeholders.CommunityQuestsPlaceholders; import net.milkbowl.vault.economy.Economy; +import java.util.concurrent.Callable; + import org.bukkit.Bukkit; +import org.bukkit.command.PluginCommand; import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.java.JavaPlugin; @@ -36,10 +42,15 @@ public class ServerQuests extends JavaPlugin { private boolean isPlaceholderApiEnabled; @Getter private CommandManager commandManager; + @Getter + private QuestHistoryManager questHistoryManager; @Override public void onEnable() { this.commandManager = new CommandManager(this); + PluginCommand pluginCommand = getCommand("communityquests"); + pluginCommand.setExecutor(commandManager); + pluginCommand.setTabCompleter(commandManager); loadSaveData(); @@ -60,9 +71,10 @@ public void onEnable() { } registerPlaceholders(); - if (!setupDecentHologram() && getConfig().getBoolean("hologram.enabled")) { - getLogger().info("Warning! DecentHolograms not found, holograms will not work."); - } else { + if (!setupDecentHologram()) { + getLogger() + .info("Warning! DecentHolograms not found or no placeholder api found, holograms will not work."); + } else if (getConfig().getBoolean("hologram.enabled")) { hologram = new DecentHologramsDisplay(this); hologram.displayHologram(); } @@ -71,6 +83,21 @@ public void onEnable() { registerQuestEvents(); RewardManager.getInstance().populateFromJsonFile(getDataFolder(), getLogger()); BossbarPlayerInfo.getInstance().loadFromJsonFile(getDataFolder()); + questHistoryManager = new QuestHistoryManager(this, getDataFolder()); + + int pluginId = 24062; + + try { + Metrics metrics = new Metrics(this, pluginId); + metrics.addCustomChart(new Metrics.SingleLineChart("active_quests", new Callable() { + @Override + public Integer call() throws Exception { + return ActiveQuests.getActiveQuestsInstance().getActiveQuestsList().size(); + } + })); + } catch (Exception e) { + getLogger().info("[Community Quests] Failed to submit metrics data for bstats"); + } getLogger().info("Plugin is enabled"); } @@ -121,7 +148,9 @@ private boolean setupMythicMobs() { } private boolean setupDecentHologram() { - return Bukkit.getPluginManager().getPlugin("DecentHolograms") != null; + boolean isEnabled = Bukkit.getPluginManager().getPlugin("DecentHolograms") != null && isPlaceholderApiEnabled; + getLogger().info("DecentHolograms enabled: " + isEnabled); + return isEnabled; } private void registerQuestEvents() { @@ -140,6 +169,8 @@ private void registerQuestEvents() { getServer().getPluginManager().registerEvents(new ConsumeItemQuestEvent(activeQuests), this); getServer().getPluginManager().registerEvents(new EnchantItemQuestEvent(activeQuests), this); getServer().getPluginManager().registerEvents(new DistanceTraveled(activeQuests), this); + getServer().getPluginManager().registerEvents(new InventoryClickEvents(activeQuests, this), this); + getServer().getPluginManager().registerEvents(new RewardJoinListener(true), this); try { getServer().getPluginManager().registerEvents(new ExperienceEvent(activeQuests), this); getServer().getPluginManager().registerEvents(new HarvestEvent(activeQuests), this); diff --git a/src/main/java/me/wonka01/ServerQuests/commands/ClaimRewards.java b/src/main/java/me/wonka01/ServerQuests/commands/ClaimRewards.java index c5657f8..397f5ff 100644 --- a/src/main/java/me/wonka01/ServerQuests/commands/ClaimRewards.java +++ b/src/main/java/me/wonka01/ServerQuests/commands/ClaimRewards.java @@ -5,7 +5,10 @@ import me.wonka01.ServerQuests.questcomponents.rewards.RewardEntry; import me.wonka01.ServerQuests.questcomponents.rewards.RewardManager; +import java.util.List; + import org.bukkit.Bukkit; +import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -75,4 +78,10 @@ public void execute(@NonNull CommandSender sender, @NotNull @NonNull String[] ar rewards.removeRewards(player.getUniqueId()); } } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + // TODO Auto-generated method stub + return null; + } } diff --git a/src/main/java/me/wonka01/ServerQuests/commands/ClearRewardsCommand.java b/src/main/java/me/wonka01/ServerQuests/commands/ClearRewardsCommand.java index 580bd4e..144385b 100644 --- a/src/main/java/me/wonka01/ServerQuests/commands/ClearRewardsCommand.java +++ b/src/main/java/me/wonka01/ServerQuests/commands/ClearRewardsCommand.java @@ -1,6 +1,9 @@ package me.wonka01.ServerQuests.commands; +import java.util.List; + import org.bukkit.Bukkit; +import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; @@ -57,4 +60,9 @@ public void execute(CommandSender sender, String[] args) { } } + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + // TODO Auto-generated method stub + return null; + } } diff --git a/src/main/java/me/wonka01/ServerQuests/commands/CommandManager.java b/src/main/java/me/wonka01/ServerQuests/commands/CommandManager.java index de96d50..f03499a 100644 --- a/src/main/java/me/wonka01/ServerQuests/commands/CommandManager.java +++ b/src/main/java/me/wonka01/ServerQuests/commands/CommandManager.java @@ -6,13 +6,14 @@ import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; +import org.bukkit.command.TabCompleter; import org.bukkit.entity.Player; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; -public class CommandManager implements CommandExecutor { +public class CommandManager implements CommandExecutor, TabCompleter { private final ServerQuests plugin; @@ -35,11 +36,11 @@ public CommandManager(ServerQuests plugin) { commands.add(new DonateCommand(plugin)); commands.add(new MoneyCommand(plugin)); commands.add(new ToggleBarCommand(plugin)); - commands.add(new ToggleMessageCommand(plugin)); commands.add(new RewardsCommand(plugin)); commands.add(new EndAllCommand(plugin)); commands.add(new ClaimRewards(plugin)); commands.add(new ClearRewardsCommand(plugin)); + commands.add(new HistoryCommand(plugin)); this.questScheduler = new QuestScheduler(plugin); commands.add(this.questScheduler); } @@ -57,11 +58,40 @@ public boolean onCommand(CommandSender sender, Command command, String label, St return true; } + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + List completions = new ArrayList<>(); + if (args.length == 1) { + for (PluginCommand cmd : commands) { + if (cmd.getName().startsWith(args[0].toLowerCase())) { + if (cmd.isPlayerCommand() && !(sender instanceof Player)) + break; + + if (cmd.getPermission().isEmpty() || sender.hasPermission(cmd.getPermission())) + completions.add(cmd.getName()); + } + } + } + + if (args.length > 1) { + for (PluginCommand cmd : commands) { + if (cmd.getName().equalsIgnoreCase(args[0])) { + // Check if the command implements a tab complete method + List comps = cmd.onTabComplete(sender, command, alias, args); + if (comps != null) + return comps; + break; + } + } + } + return completions; + } + private @Nullable PluginCommand getCommand(@NonNull CommandSender sender, @NonNull String arg) { for (PluginCommand cmd : commands) if (cmd.getName().equalsIgnoreCase(arg)) { - if (cmd.isRequiresPlayer() && !(sender instanceof Player)) + if (cmd.isPlayerCommand() && !(sender instanceof Player)) break; if (cmd.getPermission().isEmpty() || sender.hasPermission(cmd.getPermission())) diff --git a/src/main/java/me/wonka01/ServerQuests/commands/DonateCommand.java b/src/main/java/me/wonka01/ServerQuests/commands/DonateCommand.java index 16260df..1504146 100644 --- a/src/main/java/me/wonka01/ServerQuests/commands/DonateCommand.java +++ b/src/main/java/me/wonka01/ServerQuests/commands/DonateCommand.java @@ -8,6 +8,7 @@ import me.wonka01.ServerQuests.questcomponents.QuestController; import org.bukkit.Bukkit; +import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -71,4 +72,10 @@ public void execute(@NonNull CommandSender sender, @NotNull @NonNull String[] ar player.sendMessage(noActiveQuest); } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + // TODO Auto-generated method stub + return null; + } } diff --git a/src/main/java/me/wonka01/ServerQuests/commands/EndAllCommand.java b/src/main/java/me/wonka01/ServerQuests/commands/EndAllCommand.java index ac0e144..b877f5a 100644 --- a/src/main/java/me/wonka01/ServerQuests/commands/EndAllCommand.java +++ b/src/main/java/me/wonka01/ServerQuests/commands/EndAllCommand.java @@ -4,6 +4,9 @@ import me.wonka01.ServerQuests.ServerQuests; import me.wonka01.ServerQuests.questcomponents.ActiveQuests; +import java.util.List; + +import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; @@ -28,4 +31,10 @@ public void execute(@NonNull CommandSender sender, @NotNull @NonNull String[] ar ActiveQuests.getActiveQuestsInstance().endAllQuests(); sender.sendMessage(getPlugin().messages().message("endAllQuestsMessage")); } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + // TODO Auto-generated method stub + return null; + } } diff --git a/src/main/java/me/wonka01/ServerQuests/commands/HelpCommand.java b/src/main/java/me/wonka01/ServerQuests/commands/HelpCommand.java index 7a1e483..be49b96 100644 --- a/src/main/java/me/wonka01/ServerQuests/commands/HelpCommand.java +++ b/src/main/java/me/wonka01/ServerQuests/commands/HelpCommand.java @@ -2,6 +2,10 @@ import lombok.NonNull; import me.wonka01.ServerQuests.ServerQuests; + +import java.util.List; + +import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; @@ -25,4 +29,10 @@ public HelpCommand(ServerQuests plugin) { public void execute(@NonNull CommandSender sender, @NotNull @NonNull String[] args) { sender.sendMessage(getPlugin().messages().message("helpMessage")); } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + // TODO Auto-generated method stub + return null; + } } diff --git a/src/main/java/me/wonka01/ServerQuests/commands/HistoryCommand.java b/src/main/java/me/wonka01/ServerQuests/commands/HistoryCommand.java new file mode 100644 index 0000000..cc6cec7 --- /dev/null +++ b/src/main/java/me/wonka01/ServerQuests/commands/HistoryCommand.java @@ -0,0 +1,40 @@ +package me.wonka01.ServerQuests.commands; + +import lombok.NonNull; +import me.wonka01.ServerQuests.ServerQuests; +import me.wonka01.ServerQuests.gui.ViewHistoryMenu; + +import java.util.List; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; + +public class HistoryCommand extends PluginCommand { + public HistoryCommand(ServerQuests plugin) { + super(plugin, true); + } + + @Override + public @NonNull String getName() { + return "history"; + } + + @Override + public @NonNull String getPermission() { + return "communityquests.view"; + } + + @Override + public void execute(@NonNull CommandSender sender, @NotNull @NonNull String[] args) { + Player player = (Player) sender; + new ViewHistoryMenu(getPlugin(), player).open(); + } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + // TODO Auto-generated method stub + return null; + } +} diff --git a/src/main/java/me/wonka01/ServerQuests/commands/MoneyCommand.java b/src/main/java/me/wonka01/ServerQuests/commands/MoneyCommand.java index 9f1f75a..8f8ecd6 100644 --- a/src/main/java/me/wonka01/ServerQuests/commands/MoneyCommand.java +++ b/src/main/java/me/wonka01/ServerQuests/commands/MoneyCommand.java @@ -4,6 +4,10 @@ import me.wonka01.ServerQuests.ServerQuests; import me.wonka01.ServerQuests.events.MoneyQuest; import me.wonka01.ServerQuests.questcomponents.ActiveQuests; + +import java.util.List; + +import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -47,4 +51,9 @@ public void execute(@NonNull CommandSender sender, @NotNull @NonNull String[] ar player.sendMessage(message); } } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + return null; + } } diff --git a/src/main/java/me/wonka01/ServerQuests/commands/PluginCommand.java b/src/main/java/me/wonka01/ServerQuests/commands/PluginCommand.java index db18e52..a39be05 100644 --- a/src/main/java/me/wonka01/ServerQuests/commands/PluginCommand.java +++ b/src/main/java/me/wonka01/ServerQuests/commands/PluginCommand.java @@ -2,19 +2,23 @@ import lombok.Getter; import lombok.NonNull; -import me.knighthat.apis.utils.Colorization; import me.wonka01.ServerQuests.ServerQuests; +import me.wonka01.ServerQuests.utils.Colorization; + +import java.util.List; + +import org.bukkit.command.Command; import org.bukkit.command.CommandSender; @Getter public abstract class PluginCommand implements Colorization { private final ServerQuests plugin; - private final boolean requiresPlayer; + private final boolean playerCommand; - protected PluginCommand(ServerQuests plugin, boolean requiresPlayer) { + protected PluginCommand(ServerQuests plugin, boolean playerCommand) { this.plugin = plugin; - this.requiresPlayer = requiresPlayer; + this.playerCommand = playerCommand; } public abstract @NonNull String getName(); @@ -22,4 +26,7 @@ protected PluginCommand(ServerQuests plugin, boolean requiresPlayer) { public abstract @NonNull String getPermission(); public abstract void execute(@NonNull CommandSender sender, @NonNull String[] args); + + public abstract List onTabComplete(CommandSender sender, Command command, String alias, String[] args); + } diff --git a/src/main/java/me/wonka01/ServerQuests/commands/QuestScheduler.java b/src/main/java/me/wonka01/ServerQuests/commands/QuestScheduler.java index 8a95dc4..49ff52e 100644 --- a/src/main/java/me/wonka01/ServerQuests/commands/QuestScheduler.java +++ b/src/main/java/me/wonka01/ServerQuests/commands/QuestScheduler.java @@ -1,6 +1,7 @@ package me.wonka01.ServerQuests.commands; import org.bukkit.Bukkit; +import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; @@ -9,6 +10,7 @@ import lombok.NonNull; import me.wonka01.ServerQuests.ServerQuests; +import me.wonka01.ServerQuests.configuration.QuestModel; import java.io.File; import java.io.IOException; @@ -17,9 +19,13 @@ import java.time.temporal.ChronoUnit; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.stream.Collectors; public class QuestScheduler extends PluginCommand { private final ServerQuests plugin; @@ -100,8 +106,8 @@ public void execute(@NonNull CommandSender sender, @NotNull @NonNull String[] ar return; } - if (!mode.equals("coop") && !mode.equals("comp")) { - sender.sendMessage("§cMode must be either 'coop' or 'comp'!"); + if (!mode.equalsIgnoreCase("coop") && !mode.equalsIgnoreCase("comp") && !mode.equalsIgnoreCase("coll")) { + sender.sendMessage("§cMode must be either 'coop', 'comp' or 'coll'!"); return; } @@ -271,7 +277,11 @@ public void run() { } }; - // Schedule the task + // reset if exists + if (activeSchedules.containsKey(scheduleId)) { + activeSchedules.get(scheduleId).cancel(); + } + task.runTaskTimer(plugin, initialDelayTicks, intervalTicks); activeSchedules.put(scheduleId.toString(), task); } @@ -288,4 +298,82 @@ public void shutdown() { public @NonNull String getPermission() { return "communityquests.schedule"; } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + List completions = new ArrayList<>(); + + // Argument count determines the type of completions + switch (args.length) { + case 2: + // Quest ID completions + List questIds = new ArrayList<>(plugin.config().getQuestLibrary().getAllQuestModels() + .stream() + .map(QuestModel::getQuestId) + .collect(Collectors.toList())); + questIds.add("random"); // Add 'random' as a special option + completions = questIds.stream() + .filter(id -> id.toLowerCase().startsWith(args[1].toLowerCase())) + .collect(Collectors.toList()); + break; + + case 3: + // Mode completions + List modes = Arrays.asList("coop", "comp", "coll"); + completions = modes.stream() + .filter(mode -> mode.startsWith(args[2].toLowerCase())) + .collect(Collectors.toList()); + break; + + case 4: + // Time format suggestions + completions = Arrays.asList("00:00", "12:00", "14:30", "18:00", "20:00"); + completions = completions.stream() + .filter(time -> time.startsWith(args[3])) + .collect(Collectors.toList()); + break; + + case 5: + // Schedule type completions + List scheduleTypes = Arrays.stream(ScheduleType.values()) + .map(Enum::name) + .collect(Collectors.toList()); + completions = scheduleTypes.stream() + .filter(type -> type.toLowerCase().startsWith(args[4].toLowerCase())) + .collect(Collectors.toList()); + break; + + case 6: + // Action completions + List actions = Arrays.asList("add", "remove"); + completions = actions.stream() + .filter(action -> action.startsWith(args[5].toLowerCase())) + .collect(Collectors.toList()); + break; + + case 7: + // Conditional completions based on previous arguments + if (args[4].toUpperCase().equals("WEEKLY")) { + // Day of week suggestions + completions = Arrays.stream(DayOfWeek.values()) + .map(Enum::name) + .filter(day -> day.toLowerCase().startsWith(args[6].toLowerCase())) + .collect(Collectors.toList()); + } else if (args[4].toUpperCase().equals("CUSTOM_DAYS")) { + // Interval suggestions + completions = Arrays.asList("1", "2", "3", "7", "14", "30"); + completions = completions.stream() + .filter(interval -> interval.startsWith(args[6])) + .collect(Collectors.toList()); + } else if (args[5].toLowerCase().equals("remove")) { + // If removing, suggest existing schedule IDs + completions = new ArrayList<>(activeSchedules.keySet()).stream() + .filter(id -> id.toLowerCase().startsWith(args[6].toLowerCase())) + .collect(Collectors.toList()); + } + break; + } + + return completions; + } } \ No newline at end of file diff --git a/src/main/java/me/wonka01/ServerQuests/commands/ReloadCommand.java b/src/main/java/me/wonka01/ServerQuests/commands/ReloadCommand.java index 562641c..b9b8a83 100644 --- a/src/main/java/me/wonka01/ServerQuests/commands/ReloadCommand.java +++ b/src/main/java/me/wonka01/ServerQuests/commands/ReloadCommand.java @@ -2,6 +2,10 @@ import lombok.NonNull; import me.wonka01.ServerQuests.ServerQuests; + +import java.util.List; + +import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.jetbrains.annotations.NotNull; @@ -42,4 +46,10 @@ public void execute(@NonNull CommandSender sender, @NotNull @NonNull String[] ar String reloadMessage = getPlugin().messages().message("reloadCommand"); sender.sendMessage(reloadMessage); } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + // TODO Auto-generated method stub + return null; + } } diff --git a/src/main/java/me/wonka01/ServerQuests/commands/RewardsCommand.java b/src/main/java/me/wonka01/ServerQuests/commands/RewardsCommand.java index 3354c56..3e6e123 100644 --- a/src/main/java/me/wonka01/ServerQuests/commands/RewardsCommand.java +++ b/src/main/java/me/wonka01/ServerQuests/commands/RewardsCommand.java @@ -5,6 +5,9 @@ import me.wonka01.ServerQuests.gui.RewardsMenu; import me.wonka01.ServerQuests.questcomponents.rewards.RewardManager; +import java.util.List; + +import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -35,4 +38,10 @@ public void execute(@NonNull CommandSender sender, @NotNull @NonNull String[] ar } new RewardsMenu(getPlugin(), player).open(); } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + // TODO Auto-generated method stub + return null; + } } diff --git a/src/main/java/me/wonka01/ServerQuests/commands/StartCommand.java b/src/main/java/me/wonka01/ServerQuests/commands/StartCommand.java index bb24181..1e3f2cb 100644 --- a/src/main/java/me/wonka01/ServerQuests/commands/StartCommand.java +++ b/src/main/java/me/wonka01/ServerQuests/commands/StartCommand.java @@ -7,6 +7,12 @@ import me.wonka01.ServerQuests.gui.StartMenu; import me.wonka01.ServerQuests.questcomponents.ActiveQuests; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -45,6 +51,9 @@ public void execute(@NonNull CommandSender sender, @NotNull @NonNull String[] ar case "comp": type = EventType.COMPETITIVE; break; + case "coll": + type = EventType.COLLECTIVE; + break; default: String invalidQuestType = getPlugin().messages().message("invalidQuestType"); sender.sendMessage(invalidQuestType); @@ -62,4 +71,28 @@ public void execute(@NonNull CommandSender sender, @NotNull @NonNull String[] ar new StartMenu(getPlugin(), (Player) sender).open(); } } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + List completions = new ArrayList<>(); + + if (args.length == 2) { + // Fetch all available quest models and filter based on partial input + List questModels = getPlugin().config().getQuestLibrary().getAllQuestModels(); + completions = questModels.stream() + .map(QuestModel::getQuestId) + .filter(id -> id.toLowerCase().startsWith(args[1].toLowerCase())) + .collect(Collectors.toList()); + } + + // Handle quest type completions (third argument) + if (args.length == 3) { + List questTypes = Arrays.asList("coop", "comp", "coll"); + completions = questTypes.stream() + .filter(type -> type.startsWith(args[2].toLowerCase())) + .collect(Collectors.toList()); + } + + return completions; + } } diff --git a/src/main/java/me/wonka01/ServerQuests/commands/StopCommand.java b/src/main/java/me/wonka01/ServerQuests/commands/StopCommand.java index 697c743..fdfc73d 100644 --- a/src/main/java/me/wonka01/ServerQuests/commands/StopCommand.java +++ b/src/main/java/me/wonka01/ServerQuests/commands/StopCommand.java @@ -3,6 +3,10 @@ import lombok.NonNull; import me.wonka01.ServerQuests.ServerQuests; import me.wonka01.ServerQuests.gui.StopMenu; + +import java.util.List; + +import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -26,4 +30,9 @@ public StopCommand(ServerQuests plugin) { public void execute(@NonNull CommandSender sender, @NotNull @NonNull String[] args) { new StopMenu(getPlugin(), (Player) sender).open(); } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + return null; + } } diff --git a/src/main/java/me/wonka01/ServerQuests/commands/ToggleBarCommand.java b/src/main/java/me/wonka01/ServerQuests/commands/ToggleBarCommand.java index f5f8a52..6cc4c34 100644 --- a/src/main/java/me/wonka01/ServerQuests/commands/ToggleBarCommand.java +++ b/src/main/java/me/wonka01/ServerQuests/commands/ToggleBarCommand.java @@ -3,6 +3,10 @@ import lombok.NonNull; import me.wonka01.ServerQuests.ServerQuests; import me.wonka01.ServerQuests.questcomponents.bossbar.BarManager; + +import java.util.List; + +import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -27,4 +31,9 @@ public ToggleBarCommand(ServerQuests plugin) { public void execute(@NonNull CommandSender sender, @NotNull @NonNull String[] args) { BarManager.toggleShowPlayerBar((Player) sender); } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + return null; + } } diff --git a/src/main/java/me/wonka01/ServerQuests/commands/ToggleMessageCommand.java b/src/main/java/me/wonka01/ServerQuests/commands/ToggleMessageCommand.java deleted file mode 100644 index 7d980ad..0000000 --- a/src/main/java/me/wonka01/ServerQuests/commands/ToggleMessageCommand.java +++ /dev/null @@ -1,44 +0,0 @@ -package me.wonka01.ServerQuests.commands; - -import lombok.NonNull; -import me.wonka01.ServerQuests.ServerQuests; -import org.bukkit.command.CommandSender; -import org.jetbrains.annotations.NotNull; - -public class ToggleMessageCommand extends PluginCommand { - - // public HashMap map = new HashMap(); - - public ToggleMessageCommand(ServerQuests plugin) { - super(plugin, true); - } - - @Override - public @NonNull String getName() { - return "togglemessage"; - } - - @Override - public @NonNull String getPermission() { - return "communityquests.showmessages"; - } - - @Override - public void execute(@NonNull CommandSender sender, @NotNull @NonNull String[] args) { - - // PermissionAttachment attachment = - // player.addAttachment(JavaPlugin.getPlugin(ServerQuests.class)); - // if (player.hasPermission("serverquests.showmessages")) { - // attachment.setPermission("serverquests.showmessages", false); - // player.sendMessage(ChatColor.YELLOW + "You will no longer see server quest - // messages"); - // } else { - // attachment.setPermission("serverquests.showmessages", true); - // player.sendMessage(ChatColor.YELLOW + "You will now see server quest - // messages"); - // } - // - // map.put(player.getUniqueId(), attachment); - } -} diff --git a/src/main/java/me/wonka01/ServerQuests/commands/ViewCommand.java b/src/main/java/me/wonka01/ServerQuests/commands/ViewCommand.java index deebbed..06329b6 100644 --- a/src/main/java/me/wonka01/ServerQuests/commands/ViewCommand.java +++ b/src/main/java/me/wonka01/ServerQuests/commands/ViewCommand.java @@ -4,6 +4,10 @@ import me.wonka01.ServerQuests.ServerQuests; import me.wonka01.ServerQuests.gui.ViewMenu; import me.wonka01.ServerQuests.questcomponents.ActiveQuests; + +import java.util.List; + +import org.bukkit.command.Command; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -34,4 +38,9 @@ public void execute(@NonNull CommandSender sender, @NotNull @NonNull String[] ar } new ViewMenu(getPlugin(), player).open(); } + + @Override + public List onTabComplete(CommandSender sender, Command command, String alias, String[] args) { + return null; + } } diff --git a/src/main/java/me/knighthat/apis/files/Config.java b/src/main/java/me/wonka01/ServerQuests/configuration/Config.java similarity index 74% rename from src/main/java/me/knighthat/apis/files/Config.java rename to src/main/java/me/wonka01/ServerQuests/configuration/Config.java index bcfe5e5..5b6e64b 100644 --- a/src/main/java/me/knighthat/apis/files/Config.java +++ b/src/main/java/me/wonka01/ServerQuests/configuration/Config.java @@ -1,13 +1,15 @@ -package me.knighthat.apis.files; +package me.wonka01.ServerQuests.configuration; import lombok.Getter; import lombok.NonNull; +import me.knighthat.apis.files.Getters; import me.wonka01.ServerQuests.ServerQuests; -import me.wonka01.ServerQuests.configuration.QuestLibrary; +import me.wonka01.ServerQuests.events.BreakEvent; +import me.wonka01.ServerQuests.events.PlaceEvent; import me.wonka01.ServerQuests.gui.DonateMenu; import me.wonka01.ServerQuests.questcomponents.ActiveQuests; import me.wonka01.ServerQuests.questcomponents.bossbar.BarManager; -import me.wonka01.ServerQuests.questcomponents.players.BasePlayerComponent; +import me.wonka01.ServerQuests.questcomponents.players.PlayerContributionMap; import org.bukkit.Material; import org.bukkit.configuration.ConfigurationSection; @@ -30,7 +32,7 @@ public void initializeVariables() { ActiveQuests.setQuestLimit(limit); int leaderBoardSize = get().getInt("leaderBoardSize", 5); - BasePlayerComponent.setLeaderBoardSize(leaderBoardSize); + PlayerContributionMap.setLeaderBoardSize(leaderBoardSize); boolean disableBar = get().getBoolean("disableBossBar", false); BarManager.setDisableBossBar(disableBar); @@ -40,6 +42,9 @@ public void initializeVariables() { borderMaterial = Material.BLACK_STAINED_GLASS_PANE; } DonateMenu.setBorderItem(new ItemStack(borderMaterial)); + + PlaceEvent.setDisableDuplicatePlaces(get().getBoolean("disableDuplicatePlaces", false)); + BreakEvent.setDisableDuplicateBreaks(get().getBoolean("disableDuplicateBreaks", false)); } public void initializeQuests() { diff --git a/src/main/java/me/wonka01/ServerQuests/configuration/JsonQuestHistory.java b/src/main/java/me/wonka01/ServerQuests/configuration/JsonQuestHistory.java deleted file mode 100644 index 696b7c4..0000000 --- a/src/main/java/me/wonka01/ServerQuests/configuration/JsonQuestHistory.java +++ /dev/null @@ -1,31 +0,0 @@ -package me.wonka01.ServerQuests.configuration; - -import java.io.File; -import java.io.IOException; - -import org.bukkit.Bukkit; - -import me.wonka01.ServerQuests.ServerQuests; - -public class JsonQuestHistory { - private final ServerQuests plugin; - private final File path; - - public JsonQuestHistory(ServerQuests plugin, File path) { - this.plugin = plugin; - this.path = new File(path + "/questSave.json"); - } - - public boolean getOrCreateQuestFile() { - if (path.exists()) { - return true; - } else { - try { - path.createNewFile(); - } catch (IOException e) { - Bukkit.getServer().getConsoleSender().sendMessage(e.getMessage()); - } - } - return false; - } -} diff --git a/src/main/java/me/wonka01/ServerQuests/configuration/JsonQuestSave.java b/src/main/java/me/wonka01/ServerQuests/configuration/JsonQuestSave.java index 5d7b057..065e37d 100644 --- a/src/main/java/me/wonka01/ServerQuests/configuration/JsonQuestSave.java +++ b/src/main/java/me/wonka01/ServerQuests/configuration/JsonQuestSave.java @@ -1,6 +1,7 @@ package me.wonka01.ServerQuests.configuration; import me.wonka01.ServerQuests.ServerQuests; +import me.wonka01.ServerQuests.enums.EventType; import me.wonka01.ServerQuests.enums.ObjectiveType; import me.wonka01.ServerQuests.objectives.Objective; import me.wonka01.ServerQuests.questcomponents.ActiveQuests; @@ -22,12 +23,14 @@ import java.io.FileWriter; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.UUID; +import java.util.stream.Collectors; public class JsonQuestSave { @@ -67,8 +70,10 @@ public void saveQuestsInProgress() { } jObject.put("objectives", objectives); jObject.put("timeLeft", questController.getQuestData().getQuestDuration()); - if (questController.getQuestData() instanceof CompetitiveQuestData) { + if (questController.getQuestData().getEventType().equals(EventType.COMPETITIVE)) { jObject.put("type", "comp"); + } else if (questController.getQuestData().getEventType().equals(EventType.COLLECTIVE)) { + jObject.put("type", "coll"); } else { jObject.put("type", "coop"); } @@ -85,10 +90,20 @@ public void saveQuestsInProgress() { } } - private List convertJsonArrayToList(JSONArray arr) { - List list = new ArrayList<>(); + private List convertJsonArrayToList(JSONArray arr, Class clazz) { + List list = new ArrayList<>(); + if (arr == null) { + return list; + } for (Object o : arr) { - list.add((String) o); + if (clazz == Integer.class && o instanceof Long) { + list.add(clazz.cast(((Long) o).intValue())); // Convert Long to Integer + } else if (clazz.isInstance(o)) { + list.add(clazz.cast(o)); // Safe cast for other types + } else { + throw new IllegalArgumentException( + "Cannot convert " + o.getClass().getName() + " to " + clazz.getName()); + } } return list; } @@ -121,20 +136,25 @@ public void readAndInitializeQuests() { double amount = (double) obj.get("amountComplete"); JSONArray mobNames = (JSONArray) obj.get("mobNames"); JSONArray materials = (JSONArray) obj.get("materials"); - JSONArray customNames = (JSONArray) obj.get("customMobNames"); - List materialList = convertJsonArrayToList(materials).stream().map(materialName -> { - String capitalizedMaterialName = materialName.toUpperCase().replaceAll(" ", "_"); - Material material = Material.getMaterial(capitalizedMaterialName); - if (material == null) { - return Material.AIR; - } - return material; - }).collect(java.util.stream.Collectors.toList()); + JSONArray customNames = (JSONArray) obj.get("customNames"); + JSONArray customModelIds = (JSONArray) obj.get("modelIds"); + + List materialList = convertJsonArrayToList(materials, String.class).stream() + .map(materialName -> { + String capitalizedMaterialName = materialName.toUpperCase().replaceAll(" ", "_"); + Material material = Material.getMaterial(capitalizedMaterialName); + if (material == null) { + return Material.AIR; + } + return material; + }).collect(java.util.stream.Collectors.toList()); ObjectiveType objectiveType = ObjectiveType.match(type); - Objective objective = new Objective(objectiveType, goal, amount, convertJsonArrayToList(mobNames), - materialList, (String) obj.get("description"), convertJsonArrayToList(customNames), - dynamicGoal); + Objective objective = new Objective(objectiveType, goal, amount, + convertJsonArrayToList(mobNames, String.class), + materialList, (String) obj.get("description"), + convertJsonArrayToList(customNames, String.class), + dynamicGoal, convertJsonArrayToList(customModelIds, Integer.class)); objectives.add(objective); } long questDuration = (Long) questObject.getOrDefault("timeLeft", 0); @@ -149,6 +169,11 @@ public void readAndInitializeQuests() { UUID uuidKey = null; String playerName = (String) obj.get("name"); + Object lastUpdatedObj = obj.get("lastUpdated"); + long lastUpdated = System.currentTimeMillis(); + if (lastUpdatedObj != null) { + lastUpdated = (Long) lastUpdatedObj; + } while (keys.hasNext()) { String key = keys.next(); @@ -164,7 +189,8 @@ public void readAndInitializeQuests() { Type type = new com.google.gson.reflect.TypeToken>() { }.getType(); String jsonContributions = (String) obj.get(uuidKey.toString()); - playerMap.put(uuidKey, new PlayerData(playerName, uuidKey, gson.fromJson(jsonContributions, type))); + playerMap.put(uuidKey, + new PlayerData(playerName, uuidKey, gson.fromJson(jsonContributions, type), lastUpdated)); } QuestTypeHandler handler = new QuestTypeHandler(questType); diff --git a/src/main/java/me/wonka01/ServerQuests/configuration/QuestHistoryManager.java b/src/main/java/me/wonka01/ServerQuests/configuration/QuestHistoryManager.java new file mode 100644 index 0000000..ddb7164 --- /dev/null +++ b/src/main/java/me/wonka01/ServerQuests/configuration/QuestHistoryManager.java @@ -0,0 +1,148 @@ +package me.wonka01.ServerQuests.configuration; + +import me.wonka01.ServerQuests.ServerQuests; +import me.wonka01.ServerQuests.questcomponents.players.PlayerData; + +import org.bukkit.Material; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.*; +import java.util.logging.Logger; + +public class QuestHistoryManager { + private final File historyFile; + private final Logger logger; + + // TODO: move to config + private static final int MAX_HISTORY_ENTRIES = 50; + + public QuestHistoryManager(ServerQuests plugin, File dataFolder) { + this.historyFile = new File(dataFolder, "questHistory.json"); + this.logger = plugin.getLogger(); + createHistoryFileIfNotExists(); + } + + private void createHistoryFileIfNotExists() { + if (!historyFile.exists()) { + try { + historyFile.createNewFile(); + saveToFile(new JSONArray()); + } catch (IOException e) { + logger.warning("Failed to create quest history file: " + e.getMessage()); + } + } + } + + public void saveCompletedQuest(String questId, String questName, Map playerData, + Material displayItem) { + JSONArray historyArray = loadHistoryArray(); + + JSONObject questEntry = new JSONObject(); + questEntry.put("questId", questId); + questEntry.put("questName", questName); + questEntry.put("completionTime", System.currentTimeMillis()); + questEntry.put("displayItem", displayItem.toString()); + + JSONArray topPlayers = new JSONArray(); + playerData.entrySet().stream() + .sorted((e1, e2) -> { + double total1 = e1.getValue().getAmountContributed(); + double total2 = e2.getValue().getAmountContributed(); + return Double.compare(total2, total1); + }) + .limit(10) // TODO - read from leaderboard size config + .forEach(entry -> { + JSONObject playerInfo = new JSONObject(); + playerInfo.put("uuid", entry.getKey().toString()); + playerInfo.put("name", entry.getValue().getName()); + playerInfo.put("contribution", entry.getValue().getAmountContributed()); + topPlayers.add(playerInfo); + }); + + questEntry.put("topContributors", topPlayers); + + historyArray.add(0, questEntry); + while (historyArray.size() > MAX_HISTORY_ENTRIES) { + historyArray.remove(historyArray.size() - 1); + } + + saveToFile(historyArray); + } + + public List> getPlayerQuestHistory(UUID playerId) { + List> playerHistory = new ArrayList<>(); + JSONArray historyArray = loadHistoryArray(); + + for (Object entry : historyArray) { + JSONObject questEntry = (JSONObject) entry; + JSONArray topContributors = (JSONArray) questEntry.get("topContributors"); + + for (Object contributor : topContributors) { + JSONObject playerInfo = (JSONObject) contributor; + if (playerInfo.get("uuid").equals(playerId.toString())) { + Map historyEntry = new HashMap<>(); + historyEntry.put("questId", questEntry.get("questId")); + historyEntry.put("questName", questEntry.get("questName")); + historyEntry.put("completionTime", questEntry.get("completionTime")); + historyEntry.put("contribution", playerInfo.get("contribution")); + playerHistory.add(historyEntry); + break; + } + } + } + + return playerHistory; + } + + public List> getRecentQuests(int limit) { + List> recentQuests = new ArrayList<>(); + JSONArray historyArray = loadHistoryArray(); + + int count = 0; + for (Object entry : historyArray) { + if (count >= limit) + break; + + JSONObject questEntry = (JSONObject) entry; + Map questInfo = new HashMap<>(); + questInfo.put("questId", questEntry.get("questId")); + questInfo.put("questName", questEntry.get("questName")); + questInfo.put("completionTime", questEntry.get("completionTime")); + questInfo.put("topContributors", questEntry.get("topContributors")); + questInfo.put("displayItem", questEntry.get("displayItem")); + + recentQuests.add(questInfo); + count++; + } + + return recentQuests; + } + + private JSONArray loadHistoryArray() { + if (!historyFile.exists()) { + return new JSONArray(); + } + + try (FileReader reader = new FileReader(historyFile)) { + JSONParser parser = new JSONParser(); + return (JSONArray) parser.parse(reader); + } catch (Exception e) { + logger.warning("Failed to load quest history: " + e.getMessage()); + return new JSONArray(); + } + } + + private void saveToFile(JSONArray historyArray) { + try (FileWriter writer = new FileWriter(historyFile)) { + writer.write(historyArray.toJSONString()); + } catch (IOException e) { + logger.warning("Failed to save quest history: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/me/wonka01/ServerQuests/configuration/QuestLibrary.java b/src/main/java/me/wonka01/ServerQuests/configuration/QuestLibrary.java index 95f786e..2358d42 100644 --- a/src/main/java/me/wonka01/ServerQuests/configuration/QuestLibrary.java +++ b/src/main/java/me/wonka01/ServerQuests/configuration/QuestLibrary.java @@ -1,15 +1,18 @@ package me.wonka01.ServerQuests.configuration; -import me.wonka01.ServerQuests.ServerQuests; import me.wonka01.ServerQuests.enums.ObjectiveType; import me.wonka01.ServerQuests.objectives.Objective; import me.wonka01.ServerQuests.questcomponents.rewards.*; +import me.wonka01.ServerQuests.questcomponents.rewards.types.CommandReward; +import me.wonka01.ServerQuests.questcomponents.rewards.types.ExperienceReward; +import me.wonka01.ServerQuests.questcomponents.rewards.types.ItemReward; +import me.wonka01.ServerQuests.questcomponents.rewards.types.MoneyReward; +import me.wonka01.ServerQuests.questcomponents.rewards.types.Reward; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.boss.BarStyle; import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.plugin.java.JavaPlugin; import java.util.*; import java.util.stream.Collectors; @@ -75,8 +78,9 @@ private QuestModel loadQuestFromConfig(ConfigurationSection section) { BarStyle style = BarStyle.valueOf(barStyle.toUpperCase()); List objectives = null; - List mobNames = null; - List customMobNames = null; + List entityNames = null; + List customNames = null; + int goal = 0; ObjectiveType type = null; List materials = null; @@ -88,9 +92,10 @@ private QuestModel loadQuestFromConfig(ConfigurationSection section) { Bukkit.getServer().getConsoleSender().sendMessage( "[Community Quests] Using the legacy questing system for ID " + questId + ". Please check the docs and follow the new format for creating quests with the objectives option. This enables you to set multiple objectives per quest."); - mobNames = section.getStringList("entities"); + entityNames = section.getStringList("entities"); materials = section.getStringList("materials"); - customMobNames = section.getStringList("customMobNames"); + customNames = section.getStringList("customNames"); + type = ObjectiveType.match(section.getString("type")); goal = section.getInt("goal", -1); } else { @@ -107,7 +112,19 @@ private QuestModel loadQuestFromConfig(ConfigurationSection section) { String objDescription = (String) obj.get("description"); List objectiveMobs = (List) obj.get("entities"); List objectiveMaterials = (List) obj.get("materials"); - List objectiveCustomNames = (List) obj.get("customMobNames"); + List objectiveCustomNames = (List) obj.get("customNames"); + List objectiveModelIds = (List) obj.get("modelIds"); + List potionNames = (List) obj.get("potions"); + List enchantments = (List) obj.get("enchantments"); + + if (potionNames != null) { + objectiveCustomNames = potionNames; + } + + if (enchantments != null) { + objectiveCustomNames = enchantments; + } + ObjectiveType objectiveTypeEnum = ObjectiveType.match(objectiveType); List mats = new ArrayList<>(); if (objectiveMaterials != null) { @@ -115,6 +132,8 @@ private QuestModel loadQuestFromConfig(ConfigurationSection section) { String capitalizedMaterialName = itemName.toUpperCase().replaceAll(" ", "_"); Material material = Material.getMaterial(capitalizedMaterialName); if (material == null) { + Bukkit.getServer().getConsoleSender().sendMessage( + "[Community Quests] Invalid material name " + itemName + " for quest " + questId); return Material.AIR; } return material; @@ -129,19 +148,24 @@ private QuestModel loadQuestFromConfig(ConfigurationSection section) { if (objectiveCustomNames == null) { objectiveCustomNames = new ArrayList<>(); } + if (objectiveModelIds == null) { + objectiveModelIds = new ArrayList<>(); + } Objective objective = new Objective(objectiveTypeEnum, objectiveGoal, 0.0, objectiveMobs, - mats, objDescription, objectiveCustomNames, dynamicGoal); + mats, objDescription, objectiveCustomNames, dynamicGoal, objectiveModelIds); objectives.add(objective); } } ConfigurationSection rewardsSection = section.getConfigurationSection("rewards"); ConfigurationSection rankedRewards = null; + List rewardUiView = null; int rewardsLimit = 0; if (rewardsSection != null) { rewardsLimit = rewardsSection.getInt("rewardsLimit", 0); rankedRewards = rewardsSection.getConfigurationSection("rankedRewards"); + rewardUiView = rewardsSection.getStringList("rewardDisplay"); } Map> rankedRewardsMap = new HashMap<>(); @@ -160,10 +184,9 @@ private QuestModel loadQuestFromConfig(ConfigurationSection section) { } return new QuestModel(questId, displayName, description, goal, - type, mobNames, materials, displayItem, worlds, questDuration, rewardsLimit, afterQuestCommand, - beforeQuestCommand, objectives, questFailedCommand, customMobNames, barColor.toUpperCase(), - rankedRewardsMap, - rankedRewardMessages, style); + type, entityNames, materials, displayItem, worlds, questDuration, rewardsLimit, afterQuestCommand, + beforeQuestCommand, objectives, questFailedCommand, customNames, barColor.toUpperCase(), + rankedRewardsMap, rankedRewardMessages, style, rewardUiView); } private ArrayList getRewardsFromConfig(ConfigurationSection section) { @@ -208,9 +231,9 @@ private ArrayList getRewardsFromConfig(ConfigurationSection section) { reward = new ItemReward(amount, material, itemName); rewards.add(reward); } catch (Exception ex) { - JavaPlugin.getPlugin(ServerQuests.class).getLogger() + Bukkit.getLogger() .info("Item reward failed to load due to invalid configuration"); - JavaPlugin.getPlugin(ServerQuests.class).getLogger() + Bukkit.getLogger() .info(ex.getMessage()); } } @@ -223,4 +246,8 @@ private ArrayList getRewardsFromConfig(ConfigurationSection section) { public Set getAllQuestKeys() { return questList.keySet(); } + + public List getAllQuestModels() { + return new ArrayList<>(questList.values()); + } } diff --git a/src/main/java/me/wonka01/ServerQuests/configuration/QuestModel.java b/src/main/java/me/wonka01/ServerQuests/configuration/QuestModel.java index b54eef3..e705ac1 100644 --- a/src/main/java/me/wonka01/ServerQuests/configuration/QuestModel.java +++ b/src/main/java/me/wonka01/ServerQuests/configuration/QuestModel.java @@ -1,11 +1,11 @@ package me.wonka01.ServerQuests.configuration; import lombok.Getter; -import me.knighthat.apis.utils.Utils; import me.wonka01.ServerQuests.enums.ObjectiveType; import me.wonka01.ServerQuests.objectives.Objective; -import me.wonka01.ServerQuests.questcomponents.rewards.Reward; +import me.wonka01.ServerQuests.questcomponents.rewards.types.Reward; import me.wonka01.ServerQuests.questcomponents.schedulers.ParseDurationString; +import me.wonka01.ServerQuests.utils.Utils; import org.bukkit.Material; import org.bukkit.boss.BarStyle; @@ -36,6 +36,7 @@ public class QuestModel { private final Map> rankedRewards; private final Map rankedRewardMessages; private final BarStyle barStyle; + private final List rewardDisplay; public QuestModel(String questId, String displayName, String eventDescription, int questGoal, ObjectiveType objective, @@ -43,7 +44,7 @@ public QuestModel(String questId, String displayName, String eventDescription, List worlds, String questDuration, int rewardLimit, String afterQuestCommand, String beforeQuestCommand, List objectives, String questFailedCommand, List customNames, String barColor, Map> rankedRewards, - Map rankedRewardMessages, BarStyle barStyle) { + Map rankedRewardMessages, BarStyle barStyle, List rewardDisplay) { this.questId = questId; this.displayName = displayName; this.eventDescription = eventDescription; @@ -52,6 +53,7 @@ public QuestModel(String questId, String displayName, String eventDescription, this.questFailedCommand = questFailedCommand; this.barColor = barColor; this.barStyle = barStyle; + this.rewardDisplay = rewardDisplay; List materials = new ArrayList<>(); if (itemNames != null) { @@ -70,7 +72,7 @@ public QuestModel(String questId, String displayName, String eventDescription, } else { this.objectives = Arrays .asList(new Objective(objective, questGoal * 1.0, 0, mobNames, materials, objective.getString(), - customNames, "")); + customNames, "", new ArrayList<>())); } this.mobNames = mobNames; this.rankedRewards = rankedRewards; diff --git a/src/main/java/me/wonka01/ServerQuests/enums/EventType.java b/src/main/java/me/wonka01/ServerQuests/enums/EventType.java index b64e2f1..3ebd578 100644 --- a/src/main/java/me/wonka01/ServerQuests/enums/EventType.java +++ b/src/main/java/me/wonka01/ServerQuests/enums/EventType.java @@ -2,5 +2,6 @@ public enum EventType { COLLAB, - COMPETITIVE + COMPETITIVE, + COLLECTIVE } diff --git a/src/main/java/me/wonka01/ServerQuests/enums/ObjectiveType.java b/src/main/java/me/wonka01/ServerQuests/enums/ObjectiveType.java index 14351ee..9e04db0 100644 --- a/src/main/java/me/wonka01/ServerQuests/enums/ObjectiveType.java +++ b/src/main/java/me/wonka01/ServerQuests/enums/ObjectiveType.java @@ -21,10 +21,14 @@ public enum ObjectiveType { GIVE_MONEY("money", Material.GOLDEN_APPLE), ENCHANT_ITEM("enchantitem", Material.EMERALD), EXPERIENCE("experience", Material.EXPERIENCE_BOTTLE), + LEVELUP("level", Material.EXPERIENCE_BOTTLE), HARVEST("harvest", Material.WHEAT), MYTHIC_MOB("mythicmob", Material.DRAGON_HEAD), CARVE_PUMPKIN("carvepumpkin", Material.CARVED_PUMPKIN), MOVEMENT("movement", Material.DIAMOND_BOOTS), + BREW_POTION("brewpotion", Material.BREWING_STAND), + SMELT_ITEM("furnace", Material.FURNACE), + COMMAND("command", Material.COMMAND_BLOCK), UNKNOWN("unknown", Material.AIR); @Getter diff --git a/src/main/java/me/wonka01/ServerQuests/events/BreakEvent.java b/src/main/java/me/wonka01/ServerQuests/events/BreakEvent.java index a4c9a6c..560d0bf 100644 --- a/src/main/java/me/wonka01/ServerQuests/events/BreakEvent.java +++ b/src/main/java/me/wonka01/ServerQuests/events/BreakEvent.java @@ -16,13 +16,17 @@ import org.bukkit.metadata.MetadataValue; import org.bukkit.plugin.java.JavaPlugin; +import lombok.Setter; + import java.util.List; public class BreakEvent extends QuestListener implements Listener { + @Setter + private static boolean disableDuplicateBreaks = true; + private final String BROKEN = "BROKEN"; private final MetadataValue meta = new FixedMetadataValue(JavaPlugin.getPlugin(ServerQuests.class), true); - private boolean disableDuplicateBreaks = true; public BreakEvent(ActiveQuests activeQuests) { super(activeQuests); @@ -50,9 +54,10 @@ public void onBlockBreakEvent(BlockBreakEvent event) { } List controllers = tryGetControllersOfObjectiveType(ObjectiveType.BLOCK_BREAK); + + block.setMetadata(BROKEN, meta); for (QuestController controller : controllers) { - block.setMetadata(BROKEN, meta); - updateQuest(controller, event.getPlayer(), 1., ObjectiveType.BLOCK_BREAK, block.getType()); + updateQuest(controller, event.getPlayer(), 1, ObjectiveType.BLOCK_BREAK, block.getType()); } } } diff --git a/src/main/java/me/wonka01/ServerQuests/events/CatchFishEvent.java b/src/main/java/me/wonka01/ServerQuests/events/CatchFishEvent.java index 0082fba..a1fb068 100644 --- a/src/main/java/me/wonka01/ServerQuests/events/CatchFishEvent.java +++ b/src/main/java/me/wonka01/ServerQuests/events/CatchFishEvent.java @@ -23,12 +23,16 @@ public CatchFishEvent(ActiveQuests activeQuests) { @EventHandler public void onCatchFish(PlayerFishEvent event) { if (event.getState() == PlayerFishEvent.State.CAUGHT_FISH) { - List controllers = tryGetControllersOfObjectiveType(TYPE); Item item = (Item) event.getCaught(); String fishName = item.getItemStack().getType().toString(); + // log fish name and type + Bukkit.getLogger().info("Caught fish: " + fishName); + Bukkit.getLogger().info("Fish type: " + item.getName()); + + List controllers = tryGetControllersOfObjectiveType(TYPE); for (QuestController controller : controllers) { - updateQuest(controller, event.getPlayer(), 1, ObjectiveType.CATCH_FISH, fishName, item.getName()); + updateQuest(controller, event.getPlayer(), 1, ObjectiveType.CATCH_FISH, fishName); } } } diff --git a/src/main/java/me/wonka01/ServerQuests/events/ConsumeItemQuestEvent.java b/src/main/java/me/wonka01/ServerQuests/events/ConsumeItemQuestEvent.java index da3dabe..5577059 100644 --- a/src/main/java/me/wonka01/ServerQuests/events/ConsumeItemQuestEvent.java +++ b/src/main/java/me/wonka01/ServerQuests/events/ConsumeItemQuestEvent.java @@ -23,7 +23,7 @@ public void onConsumeItem(PlayerItemConsumeEvent event) { List controllers = tryGetControllersOfObjectiveType(ObjectiveType.CONSUME_ITEM); for (QuestController controller : controllers) { - updateQuest(controller, player, 1, ObjectiveType.CONSUME_ITEM, event.getItem().getType()); + updateQuest(controller, player, 1, ObjectiveType.CONSUME_ITEM, event.getItem()); } } } diff --git a/src/main/java/me/wonka01/ServerQuests/events/CraftItemQuestEvent.java b/src/main/java/me/wonka01/ServerQuests/events/CraftItemQuestEvent.java index 8c42d2c..21b060f 100644 --- a/src/main/java/me/wonka01/ServerQuests/events/CraftItemQuestEvent.java +++ b/src/main/java/me/wonka01/ServerQuests/events/CraftItemQuestEvent.java @@ -4,7 +4,6 @@ import me.wonka01.ServerQuests.questcomponents.ActiveQuests; import me.wonka01.ServerQuests.questcomponents.QuestController; -import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -58,7 +57,7 @@ public void onCraftItem(CraftItemEvent event) { List controllers = tryGetControllersOfObjectiveType(ObjectiveType.CRAFT_ITEM); for (QuestController controller : controllers) { - updateQuest(controller, (Player) event.getWhoClicked(), realAmount, ObjectiveType.CRAFT_ITEM, material); + updateQuest(controller, (Player) event.getWhoClicked(), realAmount, ObjectiveType.CRAFT_ITEM, craftedItem); } } } diff --git a/src/main/java/me/wonka01/ServerQuests/events/DistanceTraveled.java b/src/main/java/me/wonka01/ServerQuests/events/DistanceTraveled.java index 8ae673b..530720a 100644 --- a/src/main/java/me/wonka01/ServerQuests/events/DistanceTraveled.java +++ b/src/main/java/me/wonka01/ServerQuests/events/DistanceTraveled.java @@ -3,6 +3,8 @@ import me.wonka01.ServerQuests.enums.ObjectiveType; import me.wonka01.ServerQuests.questcomponents.ActiveQuests; import me.wonka01.ServerQuests.questcomponents.QuestController; + +import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; diff --git a/src/main/java/me/wonka01/ServerQuests/events/EnchantItemQuestEvent.java b/src/main/java/me/wonka01/ServerQuests/events/EnchantItemQuestEvent.java index 70361eb..7db38c5 100644 --- a/src/main/java/me/wonka01/ServerQuests/events/EnchantItemQuestEvent.java +++ b/src/main/java/me/wonka01/ServerQuests/events/EnchantItemQuestEvent.java @@ -23,7 +23,7 @@ public void onEnchantItem(EnchantItemEvent event) { List controllers = tryGetControllersOfObjectiveType(ObjectiveType.ENCHANT_ITEM); for (QuestController controller : controllers) { - updateQuest(controller, player, 1, ObjectiveType.ENCHANT_ITEM, event.getItem().getType()); + updateQuest(controller, player, 1, ObjectiveType.ENCHANT_ITEM, event.getItem(), event.getEnchantsToAdd()); } } } diff --git a/src/main/java/me/wonka01/ServerQuests/events/ExperienceEvent.java b/src/main/java/me/wonka01/ServerQuests/events/ExperienceEvent.java index f720a08..654bb68 100644 --- a/src/main/java/me/wonka01/ServerQuests/events/ExperienceEvent.java +++ b/src/main/java/me/wonka01/ServerQuests/events/ExperienceEvent.java @@ -7,6 +7,7 @@ import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerExpChangeEvent; +import org.bukkit.event.player.PlayerLevelChangeEvent; import java.util.List; @@ -23,4 +24,18 @@ public void onExperienceEvent(PlayerExpChangeEvent event) { updateQuest(controller, event.getPlayer(), event.getAmount(), ObjectiveType.EXPERIENCE); } } + + @EventHandler(priority = EventPriority.MONITOR) + public void onLevelUpEvent(PlayerLevelChangeEvent event) { + int newLevel = event.getNewLevel(); + int oldLevel = event.getOldLevel(); + + if (newLevel <= oldLevel) { + return; + } + List controllers = tryGetControllersOfObjectiveType(ObjectiveType.LEVELUP); + for (QuestController controller : controllers) { + updateQuest(controller, event.getPlayer(), newLevel - oldLevel, ObjectiveType.LEVELUP); + } + } } diff --git a/src/main/java/me/wonka01/ServerQuests/events/HarvestEvent.java b/src/main/java/me/wonka01/ServerQuests/events/HarvestEvent.java index ae1e15a..5c2babe 100644 --- a/src/main/java/me/wonka01/ServerQuests/events/HarvestEvent.java +++ b/src/main/java/me/wonka01/ServerQuests/events/HarvestEvent.java @@ -24,7 +24,7 @@ public void onHarvestEvent(PlayerHarvestBlockEvent event) { for (QuestController controller : controllers) { List harvestedItems = event.getItemsHarvested(); for (ItemStack item : harvestedItems) { - updateQuest(controller, event.getPlayer(), item.getAmount(), ObjectiveType.HARVEST, item.getType()); + updateQuest(controller, event.getPlayer(), item.getAmount(), ObjectiveType.HARVEST, item); } } } diff --git a/src/main/java/me/wonka01/ServerQuests/events/InventoryClickEvents.java b/src/main/java/me/wonka01/ServerQuests/events/InventoryClickEvents.java new file mode 100644 index 0000000..6b16703 --- /dev/null +++ b/src/main/java/me/wonka01/ServerQuests/events/InventoryClickEvents.java @@ -0,0 +1,93 @@ +package me.wonka01.ServerQuests.events; + +import java.util.List; +import java.util.UUID; + +import org.bukkit.NamespacedKey; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryAction; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.event.inventory.InventoryType.SlotType; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; + +import me.wonka01.ServerQuests.ServerQuests; +import me.wonka01.ServerQuests.enums.ObjectiveType; +import me.wonka01.ServerQuests.questcomponents.ActiveQuests; +import me.wonka01.ServerQuests.questcomponents.QuestController; + +public class InventoryClickEvents extends QuestListener implements Listener { + + private final ObjectiveType BREW_TYPE = ObjectiveType.BREW_POTION; + private ServerQuests plugin; + + public InventoryClickEvents(ActiveQuests activeQuests, ServerQuests plugin) { + super(activeQuests); + this.plugin = plugin; + } + + private void markItemAsProcessed(ItemStack item) { + ItemMeta meta = item.getItemMeta(); + PersistentDataContainer container = meta.getPersistentDataContainer(); + NamespacedKey uniqueKey = new NamespacedKey(plugin, "processed"); + container.set(uniqueKey, PersistentDataType.STRING, UUID.randomUUID().toString()); + item.setItemMeta(meta); + } + + private boolean isItemProcessed(ItemStack item) { + if (item == null || !item.hasItemMeta()) + return false; + + ItemMeta meta = item.getItemMeta(); + PersistentDataContainer container = meta.getPersistentDataContainer(); + NamespacedKey uniqueKey = new NamespacedKey(plugin, "processed"); + + return container.has(uniqueKey, PersistentDataType.STRING); + } + + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + if (event.isCancelled() || !(event.getWhoClicked() instanceof Player)) { + return; + } + Player player = (Player) event.getWhoClicked(); + ItemStack item = event.getCurrentItem(); + InventoryType inventoryType = event.getInventory().getType(); + + if (inventoryType == InventoryType.BREWING) { + if (event.getSlotType() == SlotType.CRAFTING + || event.getAction().equals(InventoryAction.MOVE_TO_OTHER_INVENTORY)) { + List controllers = tryGetControllersOfObjectiveType(BREW_TYPE); + + PotionMeta potionMeta = (PotionMeta) item.getItemMeta(); + + // Make sure potion hasn't already been used for a community quest + if (isItemProcessed(item)) { + return; + } + + markItemAsProcessed(item); + for (QuestController controller : controllers) { + updateQuest(controller, player, 1, ObjectiveType.BREW_POTION, + potionMeta.getBasePotionData().getType()); + } + } + } else if (inventoryType == InventoryType.FURNACE + || inventoryType.name().equals("BLAST_FURNACE") + || inventoryType.name().equals("SMOKER")) { + if (event.getSlotType() == SlotType.RESULT) { + List controllers = tryGetControllersOfObjectiveType(ObjectiveType.SMELT_ITEM); + for (QuestController controller : controllers) { + updateQuest(controller, player, item.getAmount(), ObjectiveType.SMELT_ITEM, + item); + } + } + } + } +} diff --git a/src/main/java/me/wonka01/ServerQuests/events/MilkCowEvent.java b/src/main/java/me/wonka01/ServerQuests/events/MilkCowEvent.java index 544d13b..9c5b1f2 100644 --- a/src/main/java/me/wonka01/ServerQuests/events/MilkCowEvent.java +++ b/src/main/java/me/wonka01/ServerQuests/events/MilkCowEvent.java @@ -4,14 +4,11 @@ import me.wonka01.ServerQuests.questcomponents.ActiveQuests; import me.wonka01.ServerQuests.questcomponents.QuestController; import org.bukkit.Material; -import org.bukkit.entity.Cow; -import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerBucketFillEvent; -import org.bukkit.event.player.PlayerInteractEntityEvent; import java.util.List; diff --git a/src/main/java/me/wonka01/ServerQuests/events/MobKillEvent.java b/src/main/java/me/wonka01/ServerQuests/events/MobKillEvent.java index 6615343..0d4a70b 100644 --- a/src/main/java/me/wonka01/ServerQuests/events/MobKillEvent.java +++ b/src/main/java/me/wonka01/ServerQuests/events/MobKillEvent.java @@ -30,7 +30,7 @@ public void OnPlayerKillMob(EntityDeathEvent event) { List controllers = tryGetControllersOfObjectiveType(ObjectiveType.MOB_KILL); for (QuestController controller : controllers) { - updateQuest(controller, killer, 1, ObjectiveType.MOB_KILL, entity.getType().toString(), entity.getName()); + updateQuest(controller, killer, 1, ObjectiveType.MOB_KILL, entity); } } } diff --git a/src/main/java/me/wonka01/ServerQuests/events/MythicMobKillEvent.java b/src/main/java/me/wonka01/ServerQuests/events/MythicMobKillEvent.java index 1297daf..51a347a 100644 --- a/src/main/java/me/wonka01/ServerQuests/events/MythicMobKillEvent.java +++ b/src/main/java/me/wonka01/ServerQuests/events/MythicMobKillEvent.java @@ -28,7 +28,7 @@ public void onKillMythicMobEvent(MythicMobDeathEvent event) { for (QuestController controller : controllers) { updateQuest(controller, (Player) event.getKiller(), 1, ObjectiveType.MYTHIC_MOB, - event.getMobType().getInternalName(), event.getMobType().getInternalName()); + event.getMobType().getInternalName()); } } } diff --git a/src/main/java/me/wonka01/ServerQuests/events/PlaceEvent.java b/src/main/java/me/wonka01/ServerQuests/events/PlaceEvent.java index e25d2cf..815d5d2 100644 --- a/src/main/java/me/wonka01/ServerQuests/events/PlaceEvent.java +++ b/src/main/java/me/wonka01/ServerQuests/events/PlaceEvent.java @@ -14,13 +14,17 @@ import org.bukkit.metadata.MetadataValue; import org.bukkit.plugin.java.JavaPlugin; +import lombok.Setter; + import java.util.List; public class PlaceEvent extends QuestListener implements Listener { + @Setter + private static boolean disableDuplicatePlaces; + private final String PLACED = "PLACED"; private final MetadataValue meta = new FixedMetadataValue(JavaPlugin.getPlugin(ServerQuests.class), true); - private boolean disableDuplicatePlaces; public PlaceEvent(ActiveQuests activeQuests) { super(activeQuests); @@ -40,9 +44,9 @@ public void onBlockPlace(BlockPlaceEvent event) { } List controllers = tryGetControllersOfObjectiveType(ObjectiveType.BLOCK_PLACE); + block.setMetadata(PLACED, meta); for (QuestController controller : controllers) { updateQuest(controller, event.getPlayer(), 1, ObjectiveType.BLOCK_PLACE, block.getType()); - block.setMetadata(PLACED, meta); } } } diff --git a/src/main/java/me/wonka01/ServerQuests/events/ProjectileKillEvent.java b/src/main/java/me/wonka01/ServerQuests/events/ProjectileKillEvent.java index 7d39f51..fac0f3b 100644 --- a/src/main/java/me/wonka01/ServerQuests/events/ProjectileKillEvent.java +++ b/src/main/java/me/wonka01/ServerQuests/events/ProjectileKillEvent.java @@ -1,6 +1,5 @@ package me.wonka01.ServerQuests.events; -import me.knighthat.apis.utils.Utils; import me.wonka01.ServerQuests.enums.ObjectiveType; import me.wonka01.ServerQuests.questcomponents.ActiveQuests; import me.wonka01.ServerQuests.questcomponents.QuestController; @@ -37,8 +36,7 @@ public void onProjectileKill(EntityDeathEvent event) { List controllers = tryGetControllersOfObjectiveType(ObjectiveType.PROJ_KILL); for (QuestController controller : controllers) { - updateQuest(controller, player, 1, ObjectiveType.PROJ_KILL, event.getEntity().getType().toString(), - event.getEntity().getName()); + updateQuest(controller, player, 1, ObjectiveType.PROJ_KILL, event.getEntity()); } } } diff --git a/src/main/java/me/wonka01/ServerQuests/events/QuestListener.java b/src/main/java/me/wonka01/ServerQuests/events/QuestListener.java index 10c3162..89ac5fe 100644 --- a/src/main/java/me/wonka01/ServerQuests/events/QuestListener.java +++ b/src/main/java/me/wonka01/ServerQuests/events/QuestListener.java @@ -1,18 +1,23 @@ package me.wonka01.ServerQuests.events; -import me.knighthat.apis.utils.Utils; import me.wonka01.ServerQuests.enums.ObjectiveType; import me.wonka01.ServerQuests.objectives.Objective; +import me.wonka01.ServerQuests.objectives.ObjectiveFilters; import me.wonka01.ServerQuests.questcomponents.ActiveQuests; import me.wonka01.ServerQuests.questcomponents.QuestController; -import me.wonka01.ServerQuests.questcomponents.QuestData; import org.bukkit.Material; +import org.bukkit.enchantments.Enchantment; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.potion.PotionType; import java.util.ArrayList; import java.util.List; +import java.util.Map; +// TODO - rename updateQuest functions to be specific to their type public abstract class QuestListener { protected ActiveQuests activeQuests; @@ -20,9 +25,17 @@ public QuestListener(ActiveQuests activeQuests) { this.activeQuests = activeQuests; } + protected boolean updateQuest(QuestController controller, Player player, double amount, Objective obj, + int objectiveId) { + if (!isEnabledInWorld(controller.getWorlds(), player.getWorld().getName())) { + return false; + } + return controller.updateQuest(amount, player, obj, objectiveId); + } + + // General quests without filters protected void updateQuest(QuestController controller, Player player, double amount, ObjectiveType type) { - QuestData questData = controller.getQuestData(); - List objectives = questData.getObjectives(); + List objectives = controller.getQuestData().getObjectives(); for (int i = 0; i < objectives.size(); i++) { Objective objective = objectives.get(i); if (objective.getType().equals(type)) { @@ -31,42 +44,117 @@ protected void updateQuest(QuestController controller, Player player, double amo } } - protected boolean updateQuest(QuestController controller, Player player, double amount, Objective obj, - int objectiveId) { - if (!isEnabledInWorld(controller.getEventConstraints().getWorlds(), player.getWorld().getName())) { - return false; + // Mob quests + protected void updateQuest(QuestController controller, Player player, double amount, ObjectiveType type, + Entity entity) { + List objectives = controller.getQuestData().getObjectives(); + for (int i = 0; i < objectives.size(); i++) { + Objective objective = objectives.get(i); + boolean matches = ObjectiveFilters.filter() + .withEntity(entity) + .withType(type) + .matches(objective); + + if (matches) { + updateQuest(controller, player, amount, objective, i); + } + + // if (objective.getType().equals(type)) { + // if (objective.getMobNames().isEmpty()) { + // if (objective.getCustomNames().isEmpty()) { + // updateQuest(controller, player, amount, objective, i); + // } else if (Utils.contains(objective.getCustomNames(), customName)) { + // updateQuest(controller, player, amount, objective, i); + // } + // } else if (Utils.contains(objective.getMobNames(), mobName)) { + // updateQuest(controller, player, amount, objective, i); + // } + // } } - return controller.updateQuest(amount, player, obj, objectiveId); } + // Mob quests with entity as a string protected void updateQuest(QuestController controller, Player player, double amount, ObjectiveType type, - String mobName, String customName) { - QuestData questData = controller.getQuestData(); - List objectives = questData.getObjectives(); + String entity) { + List objectives = controller.getQuestData().getObjectives(); for (int i = 0; i < objectives.size(); i++) { Objective objective = objectives.get(i); - if (objective.getType().equals(type)) { - if (objective.getMobNames().isEmpty()) { - if (objective.getCustomNames().isEmpty()) { - updateQuest(controller, player, amount, objective, i); - } else if (Utils.contains(objective.getCustomNames(), customName)) { - updateQuest(controller, player, amount, objective, i); - } - } else if (Utils.contains(objective.getMobNames(), mobName)) { - updateQuest(controller, player, amount, objective, i); - } + boolean matches = ObjectiveFilters.filter() + .withEntity(entity) + .withType(type) + .matches(objective); + + if (matches) { + updateQuest(controller, player, amount, objective, i); + } + } + } + + // Potion quests + protected void updateQuest(QuestController controller, Player player, double amount, ObjectiveType type, + PotionType potion) { + List objectives = controller.getQuestData().getObjectives(); + for (int i = 0; i < objectives.size(); i++) { + Objective objective = objectives.get(i); + boolean matches = ObjectiveFilters.filter() + .withPotion(potion) + .withType(type) + .matches(objective); + + if (matches) { + updateQuest(controller, player, amount, objective, i); + } + } + } + + // enchantment quests + protected void updateQuest(QuestController controller, Player player, double amount, ObjectiveType type, + ItemStack item, + Map enchantments) { + List objectives = controller.getQuestData().getObjectives(); + for (int i = 0; i < objectives.size(); i++) { + Objective objective = objectives.get(i); + boolean matches = ObjectiveFilters.filter() + .withMaterial(item.getType()) // only check material bc of how we're using customItem names atm + .withEnchantments(enchantments) + .withType(type) + .matches(objective); + + if (matches) { + updateQuest(controller, player, amount, objective, i); } } } + // Block quests protected void updateQuest(QuestController controller, Player player, double amount, ObjectiveType type, Material material) { - QuestData questData = controller.getQuestData(); - List objectives = questData.getObjectives(); + List objectives = controller.getQuestData().getObjectives(); + for (int i = 0; i < objectives.size(); i++) { + Objective objective = objectives.get(i); + boolean matches = ObjectiveFilters.filter() + .withMaterial(material) + .withType(type) + .matches(objective); + + if (matches) { + updateQuest(controller, player, amount, objective, i); + } + } + } + + // Item quests + protected void updateQuest(QuestController controller, Player player, int amount, ObjectiveType type, + ItemStack item) { + List objectives = controller.getQuestData().getObjectives(); for (int i = 0; i < objectives.size(); i++) { Objective objective = objectives.get(i); - if (objective.getType().equals(type) - && (objective.getMaterials().isEmpty() || objective.getMaterials().contains(material))) { + boolean matches = ObjectiveFilters.filter() + .withItem(item) + .withType(type) + .matches(objective); + + if (matches) { updateQuest(controller, player, amount, objective, i); } } diff --git a/src/main/java/me/wonka01/ServerQuests/events/ShearEvent.java b/src/main/java/me/wonka01/ServerQuests/events/ShearEvent.java index d34a462..ce79f90 100644 --- a/src/main/java/me/wonka01/ServerQuests/events/ShearEvent.java +++ b/src/main/java/me/wonka01/ServerQuests/events/ShearEvent.java @@ -27,7 +27,7 @@ public void onSheer(PlayerShearEntityEvent shearEvent) { List controllers = tryGetControllersOfObjectiveType(ObjectiveType.SHEAR); for (QuestController controller : controllers) { updateQuest(controller, shearEvent.getPlayer(), 1, ObjectiveType.SHEAR, - shearEvent.getEntity().getType().toString(), shearEvent.getEntity().getName()); + shearEvent.getEntity()); } } diff --git a/src/main/java/me/wonka01/ServerQuests/events/TameEvent.java b/src/main/java/me/wonka01/ServerQuests/events/TameEvent.java index c3918a6..eaf6110 100644 --- a/src/main/java/me/wonka01/ServerQuests/events/TameEvent.java +++ b/src/main/java/me/wonka01/ServerQuests/events/TameEvent.java @@ -21,8 +21,7 @@ public void onTameEvent(EntityTameEvent tameEvent) { if (tameEvent.getOwner() instanceof Player) { List controllers = tryGetControllersOfObjectiveType(ObjectiveType.TAME); for (QuestController controller : controllers) { - updateQuest(controller, (Player) tameEvent.getOwner(), 1, ObjectiveType.TAME, - tameEvent.getEntity().getType().toString(), tameEvent.getEntity().getName()); + updateQuest(controller, (Player) tameEvent.getOwner(), 1, ObjectiveType.TAME, tameEvent.getEntity()); } } } diff --git a/src/main/java/me/wonka01/ServerQuests/gui/DonateMenu.java b/src/main/java/me/wonka01/ServerQuests/gui/DonateMenu.java index 7682164..972e8c1 100644 --- a/src/main/java/me/wonka01/ServerQuests/gui/DonateMenu.java +++ b/src/main/java/me/wonka01/ServerQuests/gui/DonateMenu.java @@ -4,15 +4,15 @@ import lombok.NonNull; import lombok.Setter; import me.knighthat.apis.menus.Menu; -import me.knighthat.apis.utils.Utils; import me.wonka01.ServerQuests.ServerQuests; import me.wonka01.ServerQuests.enums.ObjectiveType; import me.wonka01.ServerQuests.objectives.Objective; +import me.wonka01.ServerQuests.objectives.ObjectiveFilters; import me.wonka01.ServerQuests.questcomponents.ActiveQuests; import me.wonka01.ServerQuests.questcomponents.CompetitiveQuestData; import me.wonka01.ServerQuests.questcomponents.QuestController; +import me.wonka01.ServerQuests.utils.Utils; -import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.World; import org.bukkit.entity.Player; @@ -97,8 +97,11 @@ protected void onItemClick(@NonNull InventoryClickEvent event) { total = data.getPlayers().getAmountContributedByObjectiveId(getOwner(), i) + inputItem.getAmount(); } - List requirements = objective.getMaterials(); - if (requirements.isEmpty() || requirements.contains(inputItem.getType())) { + boolean matches = ObjectiveFilters.filter() + .withItem(inputItem) + .matches(objective); + + if (matches) { boolean updateResult = updateQuest(ctrl, inputItem, objective, counter); if (updateResult) { if (total > goal && goal > 0) { @@ -165,7 +168,7 @@ private boolean updateQuest(@NonNull QuestController ctrl, @NonNull ItemStack it } private boolean isWorldAllowed(@NonNull QuestController ctrl, @NonNull World world) { - List worlds = ctrl.getEventConstraints().getWorlds(); + List worlds = ctrl.getWorlds(); return worlds.isEmpty() || Utils.contains(worlds, world.getName()); } } diff --git a/src/main/java/me/wonka01/ServerQuests/gui/StopMenu.java b/src/main/java/me/wonka01/ServerQuests/gui/StopMenu.java index 1d041a7..293b906 100644 --- a/src/main/java/me/wonka01/ServerQuests/gui/StopMenu.java +++ b/src/main/java/me/wonka01/ServerQuests/gui/StopMenu.java @@ -2,11 +2,12 @@ import lombok.NonNull; import me.knighthat.apis.menus.Menu; -import me.knighthat.apis.utils.Utils; import me.wonka01.ServerQuests.ServerQuests; import me.wonka01.ServerQuests.questcomponents.ActiveQuests; import me.wonka01.ServerQuests.questcomponents.QuestController; import me.wonka01.ServerQuests.questcomponents.QuestData; +import me.wonka01.ServerQuests.utils.Utils; + import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.inventory.ItemStack; diff --git a/src/main/java/me/wonka01/ServerQuests/gui/TypeMenu.java b/src/main/java/me/wonka01/ServerQuests/gui/TypeMenu.java index b9bcb57..0393b6c 100644 --- a/src/main/java/me/wonka01/ServerQuests/gui/TypeMenu.java +++ b/src/main/java/me/wonka01/ServerQuests/gui/TypeMenu.java @@ -13,11 +13,12 @@ import org.bukkit.inventory.ItemStack; import java.util.Collections; +import java.util.List; import java.util.stream.Collectors; public class TypeMenu extends Menu { - private final int COMP = 12, COOP = 14, BACK = 18; + private final int COMP = 11, COLL = 13, COOP = 15, BACK = 18; private final @NonNull QuestModel model; @@ -38,6 +39,7 @@ protected void setButtons() { @Override protected void setContents() { getInventory().setItem(COMP, getCompItem()); + getInventory().setItem(COLL, getCollItem()); getInventory().setItem(COOP, getCoopItem()); } @@ -47,6 +49,22 @@ protected void setContents() { return createItemStack(material, title); } + private @NonNull ItemStack getCollItem() { + double goal = model.getObjectives().stream().mapToDouble(Objective::getGoal).sum(); + String dynamicGoal = model.getObjectives().stream().map(Objective::getDynamicGoal) + .collect(Collectors.joining("")); + boolean isValid = (goal > 0 || !dynamicGoal.isEmpty()) && model.getCompleteTime() > 0; + List lore = Collections + .singletonList(isValid ? "&fComplete the objective before time runs out to receive a reward!" + : "&cCollective quests need a time limit and a goal."); + + String title = getPlugin().messages().string("collective"); + if (title == null || title.isEmpty()) { + title = "Collective"; + } + return createItemStack(Material.ENCHANTED_BOOK, title, lore); + } + private @NonNull ItemStack getCoopItem() { double goal = model.getObjectives().stream().mapToDouble(Objective::getGoal).sum(); String dynamicGoal = model.getObjectives().stream().map(Objective::getDynamicGoal) @@ -67,6 +85,9 @@ protected void onItemClick(@NonNull InventoryClickEvent event) { case COOP: startQuest(EventType.COLLAB); break; + case COLL: + startQuest(EventType.COLLECTIVE); + break; case BACK: new StartMenu(getPlugin(), getOwner()).open(); default: diff --git a/src/main/java/me/wonka01/ServerQuests/gui/ViewHistoryMenu.java b/src/main/java/me/wonka01/ServerQuests/gui/ViewHistoryMenu.java new file mode 100644 index 0000000..78620b9 --- /dev/null +++ b/src/main/java/me/wonka01/ServerQuests/gui/ViewHistoryMenu.java @@ -0,0 +1,59 @@ +package me.wonka01.ServerQuests.gui; + +import me.knighthat.apis.menus.Menu; +import me.wonka01.ServerQuests.ServerQuests; +import me.wonka01.ServerQuests.configuration.QuestHistoryManager; +import me.wonka01.ServerQuests.utils.Utils; + +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +public class ViewHistoryMenu extends Menu { + private final QuestHistoryManager historyManager; + + public ViewHistoryMenu(ServerQuests plugin, Player owner) { + super(plugin, owner, "historyMenu", 54); // Use full chest inventory + this.historyManager = plugin.getQuestHistoryManager(); + } + + @Override + protected void setContents() { + List> recentQuests = historyManager.getRecentQuests(45); // Get up to 45 recent quests + for (int i = 0; i < recentQuests.size() && i < 45; i++) { + Map questData = recentQuests.get(i); + + // Choose material based on quest type + Material material = Material.valueOf((String) questData.get("displayItem")); + ItemStack questItem = createQuestHistoryItem(material, questData); + getInventory().setItem(i, questItem); + } + } + + private ItemStack createQuestHistoryItem(Material material, Map questData) { + String questName = (String) questData.get("questName"); + + List lore = new ArrayList<>(); + + long completionTime = (Long) questData.get("completionTime"); + // format date in a readable way + SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yy"); + lore.add(color(getPlugin().messages().string("completed") + "&f " + sdf.format(new Date(completionTime)))); + + lore.add(color(getPlugin().messages().string("topContributors"))); + List topContributors = (List) questData.get("topContributors"); + for (int i = 0; i < Math.min(topContributors.size(), 5); i++) { + Map contributor = (Map) topContributors.get(i); + String contributorName = (String) contributor.get("name"); + String contribution = Utils.decimalToString((Double) contributor.get("contribution")); + lore.add(color("&7" + (i + 1) + ") &f" + contributorName + " &7- &6" + contribution)); + } + return super.createItemStack(material, color("&6" + questName), lore); + } +} \ No newline at end of file diff --git a/src/main/java/me/wonka01/ServerQuests/gui/ViewMenu.java b/src/main/java/me/wonka01/ServerQuests/gui/ViewMenu.java index 94b5d15..f616fc1 100644 --- a/src/main/java/me/wonka01/ServerQuests/gui/ViewMenu.java +++ b/src/main/java/me/wonka01/ServerQuests/gui/ViewMenu.java @@ -2,14 +2,15 @@ import lombok.NonNull; import me.knighthat.apis.menus.Menu; -import me.knighthat.apis.utils.Utils; import me.wonka01.ServerQuests.ServerQuests; +import me.wonka01.ServerQuests.configuration.QuestModel; import me.wonka01.ServerQuests.objectives.Objective; import me.wonka01.ServerQuests.questcomponents.CompetitiveQuestData; import me.wonka01.ServerQuests.questcomponents.QuestController; import me.wonka01.ServerQuests.questcomponents.QuestData; -import me.wonka01.ServerQuests.questcomponents.players.BasePlayerComponent; +import me.wonka01.ServerQuests.questcomponents.players.PlayerContributionMap; import me.wonka01.ServerQuests.questcomponents.players.PlayerData; +import me.wonka01.ServerQuests.utils.Utils; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -57,9 +58,21 @@ protected void setContents() { } lore.add(getPlayerProgress(controller)); + addRewardDisplay(lore, controller.getQuestData()); + return super.createItemStack(data.getDisplayItem(), data.getDisplayName(), lore); } + private void addRewardDisplay(List lore, QuestData data) { + if (data.getRewardDisplay() == null || data.getRewardDisplay().isEmpty()) { + return; + } + lore.add(""); + for (String reward : data.getRewardDisplay()) { + lore.add(color(reward)); + } + } + private @NonNull ItemStack compItem(QuestController ctrl) { QuestData data = ctrl.getQuestData(); List lore = super.getLoreFromData(data); @@ -72,6 +85,9 @@ protected void setContents() { lore.add(""); getObjectivePlayerProgress(data.getObjectives(), lore, getOwner(), (CompetitiveQuestData) data); } + + addRewardDisplay(lore, data); + return super.createItemStack(data.getDisplayItem(), data.getDisplayName(), lore); } @@ -92,7 +108,7 @@ protected void onClose(InventoryCloseEvent event) { private void getTopPlayerString(QuestController controller, List lore) { String noData = "&7n/a"; - int size = BasePlayerComponent.getLeaderBoardSize(); + int size = PlayerContributionMap.getLeaderBoardSize(); ArrayList topPlayers = controller.getPlayerComponent().getTopPlayers(size); if (topPlayers.size() < 1) { diff --git a/src/main/java/me/wonka01/ServerQuests/objectives/Objective.java b/src/main/java/me/wonka01/ServerQuests/objectives/Objective.java index 9084abb..21317f6 100644 --- a/src/main/java/me/wonka01/ServerQuests/objectives/Objective.java +++ b/src/main/java/me/wonka01/ServerQuests/objectives/Objective.java @@ -3,7 +3,6 @@ import java.util.List; import org.bukkit.Material; -import org.checkerframework.checker.units.qual.t; import org.json.simple.JSONObject; import com.google.gson.Gson; @@ -22,11 +21,13 @@ public class Objective implements Cloneable { private List mobNames; private List materials; private List customNames; + private List customModelIds; private String description; private String dynamicGoal; public Objective(ObjectiveType type, double goal, double amountComplete, List mobNames, - List materials, String description, List customNames, String dynamicGoal) { + List materials, String description, List customNames, String dynamicGoal, + List customModelIds) { this.type = type; this.goal = goal; this.amountComplete = amountComplete; @@ -35,6 +36,7 @@ public Objective(ObjectiveType type, double goal, double amountComplete, List enchantments; + + public Builder withMaterial(Material material) { + this.material = material; + return this; + } + + public Builder withType(ObjectiveType type) { + this.type = type; + return this; + } + + public Builder withItem(ItemStack item) { + if (item != null) { + this.material = item.getType(); + if (item.hasItemMeta()) { + this.customName = item.getItemMeta().getDisplayName(); + } + this.customModelId = Utils.getCustomModelData(item); + + if (this.customName == null || this.customName.isEmpty()) { + this.customName = item.getType().name().replace("_", " ").toLowerCase(); + } + } + return this; + } + + public Builder withPotion(PotionType potion) { + if (potion != null) { + this.customName = potion.name(); + } + return this; + } + + public Builder withEnchantments(Map enchantments) { + if (enchantments != null) { + this.enchantments = enchantments; + } + return this; + } + + public Builder withEntity(Entity entity) { + if (entity != null) { + this.entity = entity.getType().name(); + if (entity.getCustomName() != null) { + this.customName = entity.getCustomName(); + } else { + this.customName = entity.getName(); + } + } + return this; + } + + // option to define with string for fishing/mythic mobs + public Builder withEntity(String entity) { + this.entity = entity; + return this; + } + + public Builder withCustomName(String customName) { + this.customName = customName; + return this; + } + + public Builder withCustomModelId(Integer customModelId) { + this.customModelId = customModelId; + return this; + } + + /** + * Build a predicate to filter objectives + * + * @param objective The objective to filter + * @return true if the objective matches all specified criteria + */ + public boolean matches(Objective objective) { + // Material filter + if (material != null) { + if (!Utils.contains(objective.getMaterials(), material) && !objective.getMaterials().isEmpty()) { + return false; + } + } + + if (type != null) { + if (!objective.getType().equals(type)) { + return false; + } + } + + // Mob name filter + if (entity != null) { + if (!Utils.contains(objective.getMobNames(), entity) && !objective.getMobNames().isEmpty()) { + return false; + } + } + + // Custom name filter + if (customName != null) { + if (!Utils.contains(objective.getCustomNames(), customName) && !objective.getCustomNames().isEmpty()) { + return false; + } + } + + // Custom model ID filter + if (customModelId != null) { + if (!hasCustomModelId(objective, customModelId) && !objective.getCustomModelIds().isEmpty()) { + return false; + } + } + + // Enchantment filter + if (enchantments != null) { + boolean containsOne = false; + for (Map.Entry entry : enchantments.entrySet()) { + String enchantmentName = entry.getKey().getKey().toString(); + if (Utils.contains(objective.getCustomNames(), enchantmentName.replace("minecraft:", ""))) { + containsOne = true; + break; + } + } + + if (!containsOne && !objective.getCustomNames().isEmpty()) { + return false; + } + } + + return true; + } + + /** + * Check if an objective contains items with a specific custom model ID + * + * @param objective The objective to check + * @param modelId The custom model ID to match + * @return true if the objective contains an item with the specified custom + * model ID + */ + private boolean hasCustomModelId(Objective objective, int modelId) { + return objective.getCustomModelIds().contains(modelId); + } + } + + /** + * Convenience method to create a new filter builder + * + * @return A new ObjectiveFilters.Builder instance + */ + public static Builder filter() { + return new Builder(); + } + + /** + * Additional utility method to check if an item matches a custom model ID + * + * @param item The ItemStack to check + * @param customModelId The custom model ID to match + * @return true if the item's custom model ID matches + */ + public static boolean matchesCustomModelId(ItemStack item, int customModelId) { + if (item == null || !item.hasItemMeta()) { + return false; + } + + return item.getItemMeta().hasCustomModelData() && + item.getItemMeta().getCustomModelData() == customModelId; + } + + /** + * Create a predicate filter for objectives based on multiple criteria + * + * @return A Predicate that can be used to filter objectives + */ + public static Predicate createObjectivePredicate( + Material material, + String entity, + String customName, + Integer customModelId) { + return objective -> { + // Null checks and filtering logic + if (material != null + && (objective.getMaterials().isEmpty() || !objective.getMaterials().contains(material))) { + return false; + } + + if (entity != null + && (objective.getMobNames().isEmpty() || !Utils.contains(objective.getMobNames(), entity))) { + return false; + } + + if (customName != null && (objective.getCustomNames().isEmpty() + || !Utils.contains(objective.getCustomNames(), customName))) { + return false; + } + + // Custom model ID filter (placeholder - implement based on your Objective class + // structure) + if (customModelId != null) { + // Implement custom model ID checking logic + return false; + } + + return true; + }; + } +} \ No newline at end of file diff --git a/src/main/java/me/wonka01/ServerQuests/questcomponents/CollectiveQuestData.java b/src/main/java/me/wonka01/ServerQuests/questcomponents/CollectiveQuestData.java new file mode 100644 index 0000000..4b259c6 --- /dev/null +++ b/src/main/java/me/wonka01/ServerQuests/questcomponents/CollectiveQuestData.java @@ -0,0 +1,71 @@ +package me.wonka01.ServerQuests.questcomponents; + +import me.wonka01.ServerQuests.enums.EventType; +import me.wonka01.ServerQuests.objectives.Objective; +import me.wonka01.ServerQuests.questcomponents.players.PlayerContributionMap; +import me.wonka01.ServerQuests.questcomponents.players.PlayerData; + +import java.util.List; + +import org.bukkit.Material; +import org.bukkit.entity.Player; + +import lombok.Getter; + +public class CollectiveQuestData extends QuestData { + + @Getter + private PlayerContributionMap players; + + public CollectiveQuestData(String displayName, String description, + PlayerContributionMap players, String questType, int durationLeft, Material displayItem, + String questId, String afterQuestCommand, String beforeQuestCommand, List objectives, + String questFailedCommand, List rewardDisplay) { + super(displayName, description, questType, durationLeft, displayItem, questId, + afterQuestCommand, beforeQuestCommand, objectives, questFailedCommand, rewardDisplay); + this.players = players; + } + + @Override + public double getAmountCompleted() { + PlayerData playerData = players.getTopPlayer(); + if (playerData != null) { + return playerData.getAmountContributed(); + } + return 0; + } + + @Override + public double getPercentageComplete() { + PlayerData playerData = players.getTopPlayer(); + if (playerData != null) { + return playerData.getAmountContributed() / this.getQuestGoal(); + } + return 0; + } + + @Override + public boolean isGoalComplete() { + return false; + } + + // This really means is goal complete for the top player + @Override + public boolean isGoalComplete(Objective objective) { + PlayerData playerData = players.getTopPlayer(); + if (playerData == null) { + return false; + } + return (getQuestGoal() > 0 && playerData.getAmountContributed() >= getQuestGoal()); + } + + public boolean isGoalComplete(Objective objective, Player player, Integer objectiveId) { + double amountContributed = players.getAmountContributedByObjectiveId(player, objectiveId); + return (objective.getGoal() > 0 && amountContributed >= objective.getGoal()); + } + + @Override + public EventType getEventType() { + return EventType.COLLECTIVE; + } +} diff --git a/src/main/java/me/wonka01/ServerQuests/questcomponents/CompetitiveQuestData.java b/src/main/java/me/wonka01/ServerQuests/questcomponents/CompetitiveQuestData.java index 26c591d..f2fee68 100644 --- a/src/main/java/me/wonka01/ServerQuests/questcomponents/CompetitiveQuestData.java +++ b/src/main/java/me/wonka01/ServerQuests/questcomponents/CompetitiveQuestData.java @@ -2,7 +2,7 @@ import me.wonka01.ServerQuests.enums.EventType; import me.wonka01.ServerQuests.objectives.Objective; -import me.wonka01.ServerQuests.questcomponents.players.BasePlayerComponent; +import me.wonka01.ServerQuests.questcomponents.players.PlayerContributionMap; import me.wonka01.ServerQuests.questcomponents.players.PlayerData; import java.util.List; @@ -15,14 +15,14 @@ public class CompetitiveQuestData extends QuestData { @Getter - private BasePlayerComponent players; + private PlayerContributionMap players; public CompetitiveQuestData(String displayName, String description, - BasePlayerComponent players, String questType, int durationLeft, Material displayItem, + PlayerContributionMap players, String questType, int durationLeft, Material displayItem, String questId, String afterQuestCommand, String beforeQuestCommand, List objectives, - String questFailedCommand) { + String questFailedCommand, List rewardDisplay) { super(displayName, description, questType, durationLeft, displayItem, questId, - afterQuestCommand, beforeQuestCommand, objectives, questFailedCommand); + afterQuestCommand, beforeQuestCommand, objectives, questFailedCommand, rewardDisplay); this.players = players; } diff --git a/src/main/java/me/wonka01/ServerQuests/questcomponents/EventConstraints.java b/src/main/java/me/wonka01/ServerQuests/questcomponents/EventConstraints.java deleted file mode 100644 index 2dabc98..0000000 --- a/src/main/java/me/wonka01/ServerQuests/questcomponents/EventConstraints.java +++ /dev/null @@ -1,14 +0,0 @@ -package me.wonka01.ServerQuests.questcomponents; - -import lombok.Getter; - -import java.util.List; - -@Getter -public class EventConstraints { - private final List worlds; - - public EventConstraints(List worlds) { - this.worlds = worlds; - } -} diff --git a/src/main/java/me/wonka01/ServerQuests/questcomponents/QuestController.java b/src/main/java/me/wonka01/ServerQuests/questcomponents/QuestController.java index d0a50d2..22393e0 100644 --- a/src/main/java/me/wonka01/ServerQuests/questcomponents/QuestController.java +++ b/src/main/java/me/wonka01/ServerQuests/questcomponents/QuestController.java @@ -2,15 +2,16 @@ import lombok.Getter; import lombok.NonNull; -import me.knighthat.apis.utils.Colorization; import me.wonka01.ServerQuests.ServerQuests; import me.wonka01.ServerQuests.enums.EventType; import me.wonka01.ServerQuests.enums.ObjectiveType; import me.wonka01.ServerQuests.objectives.Objective; import me.wonka01.ServerQuests.questcomponents.bossbar.QuestBar; -import me.wonka01.ServerQuests.questcomponents.players.BasePlayerComponent; +import me.wonka01.ServerQuests.questcomponents.players.PlayerContributionMap; import me.wonka01.ServerQuests.questcomponents.schedulers.QuestTimer; +import me.wonka01.ServerQuests.utils.Colorization; +import org.bukkit.Bukkit; import org.bukkit.entity.Player; import java.text.DecimalFormat; @@ -22,19 +23,19 @@ public class QuestController implements Colorization { private final QuestBar questBar; private final QuestData questData; - private final BasePlayerComponent playerComponent; - private final EventConstraints eventConstraints; + private final PlayerContributionMap playerComponent; + private final List worlds; private final UUID questId; private final ServerQuests plugin; public QuestController(ServerQuests plugin, QuestData questData, QuestBar questBar, - BasePlayerComponent playerComponent, EventConstraints eventConstraints) { + PlayerContributionMap playerComponent, List worlds) { this.plugin = plugin; this.questData = questData; this.questBar = questBar; this.playerComponent = playerComponent; questId = UUID.randomUUID(); - this.eventConstraints = eventConstraints; + this.worlds = worlds; if (questData.getQuestDuration() > 0) { new QuestTimer(this); @@ -44,26 +45,35 @@ public QuestController(ServerQuests plugin, QuestData questData, QuestBar questB public boolean updateQuest(double count, Player player, Objective objective, Integer objectiveId) { double amountToAdd = count; - // Check if quest is complete + // Make sure individuals can't exceed the goal of an objective if (questData.getEventType().equals(EventType.COMPETITIVE)) { CompetitiveQuestData competitiveQuestData = (CompetitiveQuestData) questData; if (competitiveQuestData.isGoalComplete(objective, player, objectiveId)) { return false; } + } else if (questData.getEventType().equals(EventType.COLLECTIVE)) { + CollectiveQuestData goalQuest = (CollectiveQuestData) questData; + if (goalQuest.isGoalComplete(objective, player, objectiveId)) { + return false; + } } else { if (questData.isGoalComplete()) { return false; } } - if (questData.hasGoal() && questData.getEventType().equals(EventType.COLLAB)) { - if (amountToAdd > objective.getGoal() - objective.getAmountComplete()) { - amountToAdd = objective.getGoal() - objective.getAmountComplete(); + if (questData.hasGoal()) { + double amountComplete = objective.getAmountComplete(); + + if (questData.getEventType().equals(EventType.COMPETITIVE)) { + CompetitiveQuestData competitiveQuestData = (CompetitiveQuestData) questData; + amountComplete = competitiveQuestData.getPlayers().getAmountContributedByObjectiveId(player, + objectiveId); + } else if (questData.getEventType().equals(EventType.COLLECTIVE)) { + CollectiveQuestData goalQuest = (CollectiveQuestData) questData; + amountComplete = goalQuest.getPlayers().getAmountContributedByObjectiveId(player, objectiveId); } - } else if (questData.hasGoal() && questData.getEventType().equals(EventType.COMPETITIVE)) { - CompetitiveQuestData competitiveQuestData = (CompetitiveQuestData) questData; - double amountComplete = competitiveQuestData.getPlayers().getAmountContributedByObjectiveId(player, - objectiveId); + if (amountToAdd > objective.getGoal() - amountComplete) { amountToAdd = objective.getGoal() - amountComplete; } @@ -79,11 +89,13 @@ public boolean updateQuest(double count, Player player, Objective objective, Int updateBossBar(); sendPlayerMessage(player, amountToAdd); + // save to file every 100 actions if (questData.getAmountCompleted() % 100 == 0) { plugin.getJsonSave().saveQuestsInProgress(); } if (getQuestData().isGoalComplete()) { + Bukkit.getLogger().info("Quest complete"); endQuest(); } return getQuestData().isGoalComplete(); @@ -100,11 +112,14 @@ public void endQuest() { } else { broadcast("questCompleteMessage"); playerComponent.sendLeaderString(); - playerComponent.giveOutRewards(questData.getQuestGoal()); + String completeMessage = color(plugin.messages().message("questCompleteMessage", questData)); + playerComponent.giveOutRewards(questData.getQuestGoal(), completeMessage, questData.getEventType()); if (questData.getAfterQuestCommand() != null && !questData.getAfterQuestCommand().isEmpty()) { plugin.getServer().dispatchCommand(plugin.getServer().getConsoleSender(), questData.getAfterQuestCommand()); } + getPlugin().getQuestHistoryManager().saveCompletedQuest(questId.toString(), questData.getDisplayName(), + playerComponent.getPlayerMap(), questData.getDisplayItem()); } ActiveQuests.getActiveQuestsInstance().endQuest(questId); @@ -118,6 +133,10 @@ public boolean isCompetitive() { return (questData.getEventType().equals(EventType.COMPETITIVE)); } + public boolean isGoalQuest() { + return (questData.getEventType().equals(EventType.COLLECTIVE)); + } + private void updateBossBar() { double barProgress = questData.getPercentageComplete(); questBar.updateBarProgress(barProgress); diff --git a/src/main/java/me/wonka01/ServerQuests/questcomponents/QuestData.java b/src/main/java/me/wonka01/ServerQuests/questcomponents/QuestData.java index 2993336..337bec4 100644 --- a/src/main/java/me/wonka01/ServerQuests/questcomponents/QuestData.java +++ b/src/main/java/me/wonka01/ServerQuests/questcomponents/QuestData.java @@ -3,11 +3,11 @@ import lombok.Getter; import lombok.NonNull; import me.clip.placeholderapi.PlaceholderAPI; -import me.knighthat.apis.utils.Colorization; import me.wonka01.ServerQuests.ServerQuests; import me.wonka01.ServerQuests.enums.EventType; import me.wonka01.ServerQuests.enums.ObjectiveType; import me.wonka01.ServerQuests.objectives.Objective; +import me.wonka01.ServerQuests.utils.Colorization; import java.util.List; import java.util.stream.Collectors; @@ -15,8 +15,6 @@ import org.bukkit.ChatColor; import org.bukkit.Material; -import eu.decentsoftware.holograms.api.utils.scheduler.S; - @Getter public class QuestData implements Colorization { @@ -31,10 +29,12 @@ public class QuestData implements Colorization { private String beforeQuestCommand; private List objectives; private String questFailedCommand; + private List rewardDisplay; public QuestData(String displayName, String description, String questType, int questDuration, Material displayItem, @NonNull String questId, String afterQuestCommand, - String beforeQuestCommand, List objectives, String questFailedCommand) { + String beforeQuestCommand, List objectives, String questFailedCommand, + List rewardDisplay) { this.questDuration = questDuration; this.displayName = displayName; this.description = description == null ? "" : description; @@ -45,6 +45,7 @@ public QuestData(String displayName, String description, String questType, this.beforeQuestCommand = beforeQuestCommand; this.objectives = objectives; this.questFailedCommand = questFailedCommand; + this.rewardDisplay = rewardDisplay; } public double getAmountCompleted() { diff --git a/src/main/java/me/wonka01/ServerQuests/questcomponents/QuestTypeHandler.java b/src/main/java/me/wonka01/ServerQuests/questcomponents/QuestTypeHandler.java index d8e718d..a7b0208 100644 --- a/src/main/java/me/wonka01/ServerQuests/questcomponents/QuestTypeHandler.java +++ b/src/main/java/me/wonka01/ServerQuests/questcomponents/QuestTypeHandler.java @@ -6,7 +6,7 @@ import me.wonka01.ServerQuests.enums.EventType; import me.wonka01.ServerQuests.objectives.Objective; import me.wonka01.ServerQuests.questcomponents.bossbar.QuestBar; -import me.wonka01.ServerQuests.questcomponents.players.BasePlayerComponent; +import me.wonka01.ServerQuests.questcomponents.players.PlayerContributionMap; import me.wonka01.ServerQuests.questcomponents.players.PlayerData; import org.bukkit.plugin.java.JavaPlugin; @@ -25,7 +25,11 @@ public QuestTypeHandler(EventType eventType) { } public QuestTypeHandler(String type) { - eventType = type.equalsIgnoreCase("comp") ? EventType.COMPETITIVE : EventType.COLLAB; + if (type.equalsIgnoreCase("coll")) { + eventType = EventType.COLLECTIVE; + } else { + eventType = type.equalsIgnoreCase("comp") ? EventType.COMPETITIVE : EventType.COLLAB; + } } public QuestController createQuestController(@NonNull QuestModel model) { @@ -46,13 +50,11 @@ public QuestController createControllerFromSave(@NonNull QuestModel model, @NonN barColor = plugin.getConfig().getString("barColor", "GREEN"); } - QuestBar bar = new QuestBar(model.getDisplayName(), barColor, model.getBarStyle()); - - BasePlayerComponent pComponent = new BasePlayerComponent(model.getRewardLimit(), + PlayerContributionMap pComponent = new PlayerContributionMap(model.getRewardLimit(), model.getRankedRewards()); if (players != null) { - pComponent = new BasePlayerComponent(players, model.getRewardLimit(), + pComponent = new PlayerContributionMap(players, model.getRewardLimit(), model.getRankedRewards()); } @@ -62,27 +64,36 @@ public QuestController createControllerFromSave(@NonNull QuestModel model, @NonN } QuestData data = getQuestData(model, pComponent, timeLeft, objs); - EventConstraints event = new EventConstraints(model.getWorlds()); + + QuestBar bar = new QuestBar(data.getDisplayName(), barColor, model.getBarStyle()); if (data.getAmountCompleted() > 0) { bar.updateBarProgress(data.getAmountCompleted() / data.getQuestGoal()); } - return new QuestController(plugin, data, bar, pComponent, event); + return new QuestController(plugin, data, bar, pComponent, model.getWorlds()); } - private QuestData getQuestData(QuestModel questModel, BasePlayerComponent playerComponent, + private QuestData getQuestData(QuestModel questModel, PlayerContributionMap playerComponent, int timeLeft, List objectives) { if (eventType == EventType.COMPETITIVE) { return new CompetitiveQuestData(questModel.getDisplayName(), questModel.getEventDescription(), playerComponent, questModel.getQuestId(), timeLeft, questModel.getDisplayItem(), questModel.getQuestId(), questModel.getAfterQuestCommand(), - questModel.getBeforeQuestCommand(), objectives, questModel.getQuestFailedCommand()); - } else { + questModel.getBeforeQuestCommand(), objectives, questModel.getQuestFailedCommand(), + questModel.getRewardDisplay()); + } else if (eventType == EventType.COLLAB) { return new QuestData(questModel.getDisplayName(), questModel.getEventDescription(), questModel.getQuestId(), timeLeft, questModel.getDisplayItem(), questModel.getQuestId(), questModel.getAfterQuestCommand(), - questModel.getBeforeQuestCommand(), objectives, questModel.getQuestFailedCommand()); + questModel.getBeforeQuestCommand(), objectives, questModel.getQuestFailedCommand(), + questModel.getRewardDisplay()); + } else { + return new CollectiveQuestData(questModel.getDisplayName(), + questModel.getEventDescription(), playerComponent, questModel.getQuestId(), + timeLeft, questModel.getDisplayItem(), questModel.getQuestId(), questModel.getAfterQuestCommand(), + questModel.getBeforeQuestCommand(), objectives, questModel.getQuestFailedCommand(), + questModel.getRewardDisplay()); } } } diff --git a/src/main/java/me/wonka01/ServerQuests/questcomponents/bossbar/BossbarPlayerInfo.java b/src/main/java/me/wonka01/ServerQuests/questcomponents/bossbar/BossbarPlayerInfo.java index e27d284..4af859b 100644 --- a/src/main/java/me/wonka01/ServerQuests/questcomponents/bossbar/BossbarPlayerInfo.java +++ b/src/main/java/me/wonka01/ServerQuests/questcomponents/bossbar/BossbarPlayerInfo.java @@ -11,8 +11,8 @@ import org.json.simple.JSONArray; import org.json.simple.parser.JSONParser; +// Class that manages the players that have hidden the bossbar public class BossbarPlayerInfo { - // make singleton class with a list of playerId who have the bossbar enabled private static BossbarPlayerInfo instance; private List playersToHideBossbar; @@ -65,9 +65,6 @@ public void saveToJsonFile(File path) { } public void loadFromJsonFile(File path) { - // load the list of players from a json file - // if the file does not exist, do nothing - // if the file exists, load the list of players JSONParser parser = new JSONParser(); File fullPath = new File(path + "/togglebar.json"); try { diff --git a/src/main/java/me/wonka01/ServerQuests/questcomponents/bossbar/QuestBar.java b/src/main/java/me/wonka01/ServerQuests/questcomponents/bossbar/QuestBar.java index 4d7b5f2..a6ed39d 100644 --- a/src/main/java/me/wonka01/ServerQuests/questcomponents/bossbar/QuestBar.java +++ b/src/main/java/me/wonka01/ServerQuests/questcomponents/bossbar/QuestBar.java @@ -2,7 +2,8 @@ import lombok.NonNull; import lombok.Setter; -import me.knighthat.apis.utils.Colorization; +import me.wonka01.ServerQuests.utils.Colorization; + import org.bukkit.Bukkit; import org.bukkit.boss.BarColor; import org.bukkit.boss.BarStyle; diff --git a/src/main/java/me/wonka01/ServerQuests/questcomponents/players/BasePlayerComponent.java b/src/main/java/me/wonka01/ServerQuests/questcomponents/players/PlayerContributionMap.java similarity index 79% rename from src/main/java/me/wonka01/ServerQuests/questcomponents/players/BasePlayerComponent.java rename to src/main/java/me/wonka01/ServerQuests/questcomponents/players/PlayerContributionMap.java index b3033db..b251bab 100644 --- a/src/main/java/me/wonka01/ServerQuests/questcomponents/players/BasePlayerComponent.java +++ b/src/main/java/me/wonka01/ServerQuests/questcomponents/players/PlayerContributionMap.java @@ -1,10 +1,12 @@ package me.wonka01.ServerQuests.questcomponents.players; -import me.knighthat.apis.utils.Colorization; -import me.knighthat.apis.utils.Utils; import me.wonka01.ServerQuests.ServerQuests; -import me.wonka01.ServerQuests.questcomponents.rewards.Reward; import me.wonka01.ServerQuests.questcomponents.rewards.RewardManager; +import me.wonka01.ServerQuests.questcomponents.rewards.RewardMessage; +import me.wonka01.ServerQuests.questcomponents.rewards.types.Reward; +import me.wonka01.ServerQuests.utils.Colorization; +import me.wonka01.ServerQuests.utils.Utils; +import me.wonka01.ServerQuests.enums.EventType; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; @@ -24,23 +26,24 @@ import java.util.TreeMap; import java.util.UUID; -public class BasePlayerComponent implements Colorization { +public class PlayerContributionMap implements Colorization { @Setter @Getter private static int leaderBoardSize = 5; + @Getter private final Map playerMap; private final Map> rankedRewards; private final int rewardsLimit; - public BasePlayerComponent(int rewardLimit, + public PlayerContributionMap(int rewardLimit, Map> rankedRewards) { this.playerMap = new TreeMap<>(); this.rewardsLimit = rewardLimit; this.rankedRewards = rankedRewards; } - public BasePlayerComponent(Map map, int rewardLimit, + public PlayerContributionMap(Map map, int rewardLimit, Map> rankedRewards) { this.playerMap = map; this.rewardsLimit = rewardLimit; @@ -51,6 +54,9 @@ public void savePlayerAction(Player player, double count, Integer objectiveId) { if (playerMap.containsKey(player.getUniqueId())) { PlayerData playerData = playerMap.get(player.getUniqueId()); playerData.increaseContribution(count, objectiveId); + + // update display name so ranks and such are handled better + playerData.setName(player.getDisplayName()); } else { PlayerData playerData = new PlayerData(player.getDisplayName(), player.getUniqueId()); playerData.increaseContribution(count, objectiveId); @@ -151,18 +157,20 @@ public JSONArray toJSONArray() { String jsonString = gson.toJson(playerMap.get(key).getObjectiveContributions()); jsonObject.put(key.toString(), jsonString); jsonObject.put("name", playerMap.get(key).getName()); + jsonObject.put("lastUpdated", playerMap.get(key).getLastUpdated()); jArray.add(jsonObject); } return jArray; } - public void giveOutRewards(double questGoal) { + public void giveOutRewards(double questGoal, String completeMessage, EventType eventType) { List players; if (rewardsLimit > 0) { players = getTopPlayers(rewardsLimit); } else { players = getTopPlayers(playerMap.size()); } + for (PlayerData playerData : players) { double playerContributionRatio; double playerContribution = playerData.getAmountContributed(); @@ -187,19 +195,31 @@ public void giveOutRewards(double questGoal) { } OfflinePlayer player = Bukkit.getServer().getOfflinePlayer(playerData.getUuid()); + RewardManager rewardManager = RewardManager.getInstance(); + + if (eventType == EventType.COLLECTIVE) { + if (playerContribution >= questGoal) { + playerContributionRatio = 1.0; + } else { + continue; + } + } if (player.isOnline()) { Player onlinePlayer = (Player) player; if (rankedReward.size() > 0) { - ServerQuests plugin = JavaPlugin.getPlugin(ServerQuests.class); - String rewardsMessage = plugin.messages().message("rewardsMessage"); - onlinePlayer.sendMessage(rewardsMessage); + // ServerQuests plugin = JavaPlugin.getPlugin(ServerQuests.class); + // String rewardsMessage = plugin.messages().message("rewardsMessage"); + for (Reward reward : rankedReward) { + reward.giveRewardToPlayer(onlinePlayer, playerContributionRatio); + } + } + } else { + RewardMessage rewardMessage = new RewardMessage(completeMessage); + rewardManager.addReward(player.getUniqueId(), rewardMessage, playerContributionRatio); + for (Reward reward : rankedReward) { + rewardManager.addReward(player.getUniqueId(), reward, playerContributionRatio); } - } - - RewardManager rewardManager = RewardManager.getInstance(); - for (Reward reward : rankedReward) { - rewardManager.addReward(player.getUniqueId(), reward, playerContributionRatio); } } } diff --git a/src/main/java/me/wonka01/ServerQuests/questcomponents/players/PlayerData.java b/src/main/java/me/wonka01/ServerQuests/questcomponents/players/PlayerData.java index f51e5cf..767769b 100644 --- a/src/main/java/me/wonka01/ServerQuests/questcomponents/players/PlayerData.java +++ b/src/main/java/me/wonka01/ServerQuests/questcomponents/players/PlayerData.java @@ -4,27 +4,30 @@ import java.util.Map; import java.util.UUID; -import org.bukkit.Bukkit; - import lombok.Getter; +import lombok.Setter; @Getter public class PlayerData { + @Setter private String name; private UUID uuid; Map objectiveContributions; + long lastUpdated; public PlayerData(String name, UUID uuid) { objectiveContributions = new HashMap<>(); this.name = name; this.uuid = uuid; + this.lastUpdated = System.currentTimeMillis(); } - public PlayerData(String name, UUID uuid, Map objectives) { + public PlayerData(String name, UUID uuid, Map objectives, long lastUpdated) { this.objectiveContributions = objectives; this.name = name; this.uuid = uuid; + this.lastUpdated = lastUpdated; } public double getAmountContributed() { @@ -47,5 +50,6 @@ public void increaseContribution(double count, int objectiveId) { objectiveContributions.put(objectiveId, 0.0); } objectiveContributions.put(objectiveId, objectiveContributions.get(objectiveId) + count); + lastUpdated = System.currentTimeMillis(); } } diff --git a/src/main/java/me/wonka01/ServerQuests/questcomponents/players/SortByContributions.java b/src/main/java/me/wonka01/ServerQuests/questcomponents/players/SortByContributions.java index d262e5f..2ce4854 100644 --- a/src/main/java/me/wonka01/ServerQuests/questcomponents/players/SortByContributions.java +++ b/src/main/java/me/wonka01/ServerQuests/questcomponents/players/SortByContributions.java @@ -16,7 +16,8 @@ public int compare(UUID p1Id, UUID p2Id) { map.get(p1Id).getAmountContributed()); if (contributionComparison == 0) { - return p1Id.compareTo(p2Id); + // if same amount whoever updated first would be higher + return Long.compare(map.get(p1Id).getLastUpdated(), map.get(p2Id).getLastUpdated()); } return contributionComparison; } diff --git a/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/RankedRewards.java b/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/RankedRewards.java index d7a91b6..4e427b3 100644 --- a/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/RankedRewards.java +++ b/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/RankedRewards.java @@ -5,6 +5,7 @@ import java.util.Map; import lombok.Getter; +import me.wonka01.ServerQuests.questcomponents.rewards.types.Reward; public class RankedRewards { // make a map of rewards where the key is a string and the value is a reward diff --git a/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/RewardEntry.java b/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/RewardEntry.java index 5162c59..eb15aa7 100644 --- a/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/RewardEntry.java +++ b/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/RewardEntry.java @@ -1,6 +1,7 @@ package me.wonka01.ServerQuests.questcomponents.rewards; import lombok.Getter; +import me.wonka01.ServerQuests.questcomponents.rewards.types.Reward; @Getter public class RewardEntry { diff --git a/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/RewardJoinListener.java b/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/RewardJoinListener.java new file mode 100644 index 0000000..970c5e8 --- /dev/null +++ b/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/RewardJoinListener.java @@ -0,0 +1,34 @@ +package me.wonka01.ServerQuests.questcomponents.rewards; + +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.ChatColor; + +public class RewardJoinListener implements Listener { + private final RewardManager rewardManager; + private final Boolean enabled; + + public RewardJoinListener(Boolean enabled) { + this.rewardManager = RewardManager.getInstance(); + this.enabled = enabled; + } + + @EventHandler + public void onPlayerJoin(PlayerJoinEvent event) { + if (!enabled) { + return; + } + + Player player = event.getPlayer(); + + if (rewardManager.hasRewards(player.getUniqueId())) { + // Give rewards to the player + rewardManager.giveRewardToPlayer(player.getUniqueId()); + + // Send completion message + player.sendMessage(ChatColor.GREEN + "You have received your quest rewards!"); + } + } +} \ No newline at end of file diff --git a/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/RewardManager.java b/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/RewardManager.java index dca7e43..8d862ef 100644 --- a/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/RewardManager.java +++ b/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/RewardManager.java @@ -17,6 +17,12 @@ import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; +import me.wonka01.ServerQuests.questcomponents.rewards.types.CommandReward; +import me.wonka01.ServerQuests.questcomponents.rewards.types.ExperienceReward; +import me.wonka01.ServerQuests.questcomponents.rewards.types.ItemReward; +import me.wonka01.ServerQuests.questcomponents.rewards.types.MoneyReward; +import me.wonka01.ServerQuests.questcomponents.rewards.types.Reward; + public class RewardManager { private static RewardManager instance; private HashMap> playerRewards = new HashMap<>(); diff --git a/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/RewardMessage.java b/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/RewardMessage.java index 44dd14f..e4e2d3c 100644 --- a/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/RewardMessage.java +++ b/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/RewardMessage.java @@ -1,7 +1,9 @@ package me.wonka01.ServerQuests.questcomponents.rewards; import lombok.Getter; -import me.knighthat.apis.utils.Colorization; +import me.wonka01.ServerQuests.questcomponents.rewards.types.Reward; +import me.wonka01.ServerQuests.utils.Colorization; + import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import org.json.simple.JSONObject; diff --git a/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/CommandReward.java b/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/types/CommandReward.java similarity index 93% rename from src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/CommandReward.java rename to src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/types/CommandReward.java index 6b550b3..61d6995 100644 --- a/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/CommandReward.java +++ b/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/types/CommandReward.java @@ -1,4 +1,4 @@ -package me.wonka01.ServerQuests.questcomponents.rewards; +package me.wonka01.ServerQuests.questcomponents.rewards.types; import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; diff --git a/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/ExperienceReward.java b/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/types/ExperienceReward.java similarity index 91% rename from src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/ExperienceReward.java rename to src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/types/ExperienceReward.java index 7fba2d9..ba65695 100644 --- a/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/ExperienceReward.java +++ b/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/types/ExperienceReward.java @@ -1,15 +1,14 @@ -package me.wonka01.ServerQuests.questcomponents.rewards; +package me.wonka01.ServerQuests.questcomponents.rewards.types; import lombok.Getter; -import me.knighthat.apis.utils.Colorization; import me.wonka01.ServerQuests.ServerQuests; +import me.wonka01.ServerQuests.utils.Colorization; + import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; import org.json.simple.JSONObject; -import java.text.MessageFormat; - public class ExperienceReward implements Reward, Colorization { @Getter diff --git a/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/ItemReward.java b/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/types/ItemReward.java similarity index 96% rename from src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/ItemReward.java rename to src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/types/ItemReward.java index 985a7dc..3b1217c 100644 --- a/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/ItemReward.java +++ b/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/types/ItemReward.java @@ -1,9 +1,9 @@ -package me.wonka01.ServerQuests.questcomponents.rewards; +package me.wonka01.ServerQuests.questcomponents.rewards.types; import lombok.Getter; import lombok.NonNull; -import me.knighthat.apis.utils.Colorization; import me.wonka01.ServerQuests.ServerQuests; +import me.wonka01.ServerQuests.utils.Colorization; import java.util.ArrayList; import java.util.HashMap; diff --git a/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/MoneyReward.java b/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/types/MoneyReward.java similarity index 92% rename from src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/MoneyReward.java rename to src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/types/MoneyReward.java index dbb2565..aa9d1f1 100644 --- a/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/MoneyReward.java +++ b/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/types/MoneyReward.java @@ -1,8 +1,8 @@ -package me.wonka01.ServerQuests.questcomponents.rewards; +package me.wonka01.ServerQuests.questcomponents.rewards.types; import lombok.Getter; -import me.knighthat.apis.utils.Colorization; import me.wonka01.ServerQuests.ServerQuests; +import me.wonka01.ServerQuests.utils.Colorization; import net.milkbowl.vault.economy.Economy; import org.bukkit.OfflinePlayer; import org.bukkit.plugin.java.JavaPlugin; diff --git a/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/Reward.java b/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/types/Reward.java similarity index 77% rename from src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/Reward.java rename to src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/types/Reward.java index b5d8fd7..a76f02c 100644 --- a/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/Reward.java +++ b/src/main/java/me/wonka01/ServerQuests/questcomponents/rewards/types/Reward.java @@ -1,4 +1,4 @@ -package me.wonka01.ServerQuests.questcomponents.rewards; +package me.wonka01.ServerQuests.questcomponents.rewards.types; import org.bukkit.OfflinePlayer; import org.json.simple.JSONObject; diff --git a/src/main/java/me/knighthat/apis/utils/Colorization.java b/src/main/java/me/wonka01/ServerQuests/utils/Colorization.java similarity index 89% rename from src/main/java/me/knighthat/apis/utils/Colorization.java rename to src/main/java/me/wonka01/ServerQuests/utils/Colorization.java index 54ceb8b..2d28c92 100644 --- a/src/main/java/me/knighthat/apis/utils/Colorization.java +++ b/src/main/java/me/wonka01/ServerQuests/utils/Colorization.java @@ -1,4 +1,4 @@ -package me.knighthat.apis.utils; +package me.wonka01.ServerQuests.utils; import lombok.NonNull; import org.bukkit.ChatColor; diff --git a/src/main/java/me/knighthat/apis/utils/Utils.java b/src/main/java/me/wonka01/ServerQuests/utils/Utils.java similarity index 64% rename from src/main/java/me/knighthat/apis/utils/Utils.java rename to src/main/java/me/wonka01/ServerQuests/utils/Utils.java index f2a3d91..1f11364 100644 --- a/src/main/java/me/knighthat/apis/utils/Utils.java +++ b/src/main/java/me/wonka01/ServerQuests/utils/Utils.java @@ -1,6 +1,9 @@ -package me.knighthat.apis.utils; +package me.wonka01.ServerQuests.utils; import lombok.NonNull; + +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; import org.jetbrains.annotations.Nullable; import java.text.DecimalFormat; @@ -29,7 +32,19 @@ static boolean contains(@Nullable F[] from, @Nullable V value) { } static @NonNull String decimalToString(double a) { - DecimalFormat format = new DecimalFormat("0.#"); + DecimalFormat format = new DecimalFormat("#,###.#"); return format.format(a); } + + static int getCustomModelData(ItemStack item) { + if (item == null || !item.hasItemMeta()) { + return -1; + } + + ItemMeta meta = item.getItemMeta(); + if (meta != null && meta.hasCustomModelData()) { + return meta.getCustomModelData(); + } + return -1; + } } diff --git a/src/main/java/me/wonka01/bStats/Metrics.java b/src/main/java/me/wonka01/bStats/Metrics.java new file mode 100644 index 0000000..5c8dba0 --- /dev/null +++ b/src/main/java/me/wonka01/bStats/Metrics.java @@ -0,0 +1,915 @@ + +/* + * This Metrics class was auto-generated and can be copied into your project if you are + * not using a build tool like Gradle or Maven for dependency management. + * + * IMPORTANT: You are not allowed to modify this class, except changing the package. + * + * Disallowed modifications include but are not limited to: + * - Remove the option for users to opt-out + * - Change the frequency for data submission + * - Obfuscate the code (every obfuscator should allow you to make an exception for specific files) + * - Reformat the code (if you use a linter, add an exception) + * + * Violations will result in a ban of your plugin and account from bStats. + */ +package me.wonka01.bStats; + +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.lang.reflect.Method; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.Callable; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.logging.Level; +import java.util.stream.Collectors; +import java.util.zip.GZIPOutputStream; +import javax.net.ssl.HttpsURLConnection; +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; + +public class Metrics { + + private final Plugin plugin; + + private final MetricsBase metricsBase; + + /** + * Creates a new Metrics instance. + * + * @param plugin Your plugin instance. + * @param serviceId The id of the service. It can be found at What is my + * plugin id? + */ + public Metrics(Plugin plugin, int serviceId) { + this.plugin = plugin; + // Get the config file + File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); + File configFile = new File(bStatsFolder, "config.yml"); + YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); + if (!config.isSet("serverUuid")) { + config.addDefault("enabled", true); + config.addDefault("serverUuid", UUID.randomUUID().toString()); + config.addDefault("logFailedRequests", false); + config.addDefault("logSentData", false); + config.addDefault("logResponseStatusText", false); + // Inform the server owners about bStats + config + .options() + .header( + "bStats (https://bStats.org) collects some basic information for plugin authors, like how\n" + + "many people use their plugin and their total player count. It's recommended to keep bStats\n" + + "enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n" + + "performance penalty associated with having metrics enabled, and data sent to bStats is fully\n" + + "anonymous.") + .copyDefaults(true); + try { + config.save(configFile); + } catch (IOException ignored) { + } + } + // Load the data + boolean enabled = config.getBoolean("enabled", true); + String serverUUID = config.getString("serverUuid"); + boolean logErrors = config.getBoolean("logFailedRequests", false); + boolean logSentData = config.getBoolean("logSentData", false); + boolean logResponseStatusText = config.getBoolean("logResponseStatusText", false); + boolean isFolia = false; + try { + isFolia = Class.forName("io.papermc.paper.threadedregions.RegionizedServer") != null; + } catch (Exception e) { + } + metricsBase = new // See https://github.com/Bastian/bstats-metrics/pull/126 + // See https://github.com/Bastian/bstats-metrics/pull/126 + // See https://github.com/Bastian/bstats-metrics/pull/126 + // See https://github.com/Bastian/bstats-metrics/pull/126 + // See https://github.com/Bastian/bstats-metrics/pull/126 + // See https://github.com/Bastian/bstats-metrics/pull/126 + // See https://github.com/Bastian/bstats-metrics/pull/126 + MetricsBase( + "bukkit", + serverUUID, + serviceId, + enabled, + this::appendPlatformData, + this::appendServiceData, + isFolia + ? null + : submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask), + plugin::isEnabled, + (message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error), + (message) -> this.plugin.getLogger().log(Level.INFO, message), + logErrors, + logSentData, + logResponseStatusText, + false); + } + + /** Shuts down the underlying scheduler service. */ + public void shutdown() { + metricsBase.shutdown(); + } + + /** + * Adds a custom chart. + * + * @param chart The chart to add. + */ + public void addCustomChart(CustomChart chart) { + metricsBase.addCustomChart(chart); + } + + private void appendPlatformData(JsonObjectBuilder builder) { + builder.appendField("playerAmount", getPlayerAmount()); + builder.appendField("onlineMode", Bukkit.getOnlineMode() ? 1 : 0); + builder.appendField("bukkitVersion", Bukkit.getVersion()); + builder.appendField("bukkitName", Bukkit.getName()); + builder.appendField("javaVersion", System.getProperty("java.version")); + builder.appendField("osName", System.getProperty("os.name")); + builder.appendField("osArch", System.getProperty("os.arch")); + builder.appendField("osVersion", System.getProperty("os.version")); + builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); + } + + private void appendServiceData(JsonObjectBuilder builder) { + builder.appendField("pluginVersion", plugin.getDescription().getVersion()); + } + + private int getPlayerAmount() { + try { + // Around MC 1.8 the return type was changed from an array to a collection, + // This fixes java.lang.NoSuchMethodError: + // org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; + Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); + return onlinePlayersMethod.getReturnType().equals(Collection.class) + ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() + : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; + } catch (Exception e) { + // Just use the new method if the reflection failed + return Bukkit.getOnlinePlayers().size(); + } + } + + public static class MetricsBase { + + /** The version of the Metrics class. */ + public static final String METRICS_VERSION = "3.1.0"; + + private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; + + private final ScheduledExecutorService scheduler; + + private final String platform; + + private final String serverUuid; + + private final int serviceId; + + private final Consumer appendPlatformDataConsumer; + + private final Consumer appendServiceDataConsumer; + + private final Consumer submitTaskConsumer; + + private final Supplier checkServiceEnabledSupplier; + + private final BiConsumer errorLogger; + + private final Consumer infoLogger; + + private final boolean logErrors; + + private final boolean logSentData; + + private final boolean logResponseStatusText; + + private final Set customCharts = new HashSet<>(); + + private final boolean enabled; + + /** + * Creates a new MetricsBase class instance. + * + * @param platform The platform of the service. + * @param serviceId The id of the service. + * @param serverUuid The server uuid. + * @param enabled Whether or not data sending is enabled. + * @param appendPlatformDataConsumer A consumer that receives a + * {@code JsonObjectBuilder} and + * appends all platform-specific data. + * @param appendServiceDataConsumer A consumer that receives a + * {@code JsonObjectBuilder} and + * appends all service-specific data. + * @param submitTaskConsumer A consumer that takes a runnable with the + * submit task. This can be + * used to delegate the data collection to a + * another thread to prevent errors caused by + * concurrency. Can be {@code null}. + * @param checkServiceEnabledSupplier A supplier to check if the service is + * still enabled. + * @param errorLogger A consumer that accepts log message and an + * error. + * @param infoLogger A consumer that accepts info log messages. + * @param logErrors Whether or not errors should be logged. + * @param logSentData Whether or not the sent data should be + * logged. + * @param logResponseStatusText Whether or not the response status text + * should be logged. + * @param skipRelocateCheck Whether or not the relocate check should + * be skipped. + */ + public MetricsBase( + String platform, + String serverUuid, + int serviceId, + boolean enabled, + Consumer appendPlatformDataConsumer, + Consumer appendServiceDataConsumer, + Consumer submitTaskConsumer, + Supplier checkServiceEnabledSupplier, + BiConsumer errorLogger, + Consumer infoLogger, + boolean logErrors, + boolean logSentData, + boolean logResponseStatusText, + boolean skipRelocateCheck) { + ScheduledThreadPoolExecutor scheduler = new ScheduledThreadPoolExecutor( + 1, + task -> { + Thread thread = new Thread(task, "bStats-Metrics"); + thread.setDaemon(true); + return thread; + }); + // We want delayed tasks (non-periodic) that will execute in the future to be + // cancelled when the scheduler is shutdown. + // Otherwise, we risk preventing the server from shutting down even when + // MetricsBase#shutdown() is called + scheduler.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); + this.scheduler = scheduler; + this.platform = platform; + this.serverUuid = serverUuid; + this.serviceId = serviceId; + this.enabled = enabled; + this.appendPlatformDataConsumer = appendPlatformDataConsumer; + this.appendServiceDataConsumer = appendServiceDataConsumer; + this.submitTaskConsumer = submitTaskConsumer; + this.checkServiceEnabledSupplier = checkServiceEnabledSupplier; + this.errorLogger = errorLogger; + this.infoLogger = infoLogger; + this.logErrors = logErrors; + this.logSentData = logSentData; + this.logResponseStatusText = logResponseStatusText; + if (!skipRelocateCheck) { + checkRelocation(); + } + if (enabled) { + // WARNING: Removing the option to opt-out will get your plugin banned from + // bStats + startSubmitting(); + } + } + + public void addCustomChart(CustomChart chart) { + this.customCharts.add(chart); + } + + public void shutdown() { + scheduler.shutdown(); + } + + private void startSubmitting() { + final Runnable submitTask = () -> { + if (!enabled || !checkServiceEnabledSupplier.get()) { + // Submitting data or service is disabled + scheduler.shutdown(); + return; + } + if (submitTaskConsumer != null) { + submitTaskConsumer.accept(this::submitData); + } else { + this.submitData(); + } + }; + // Many servers tend to restart at a fixed time at xx:00 which causes an uneven + // distribution of requests on the + // bStats backend. To circumvent this problem, we introduce some randomness into + // the initial and second delay. + // WARNING: You must not modify and part of this Metrics class, including the + // submit delay or frequency! + // WARNING: Modifying this code will get your plugin banned on bStats. Just + // don't do it! + long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); + long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); + scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); + scheduler.scheduleAtFixedRate( + submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); + } + + private void submitData() { + final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder(); + appendPlatformDataConsumer.accept(baseJsonBuilder); + final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder(); + appendServiceDataConsumer.accept(serviceJsonBuilder); + JsonObjectBuilder.JsonObject[] chartData = customCharts.stream() + .map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors)) + .filter(Objects::nonNull) + .toArray(JsonObjectBuilder.JsonObject[]::new); + serviceJsonBuilder.appendField("id", serviceId); + serviceJsonBuilder.appendField("customCharts", chartData); + baseJsonBuilder.appendField("service", serviceJsonBuilder.build()); + baseJsonBuilder.appendField("serverUUID", serverUuid); + baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION); + JsonObjectBuilder.JsonObject data = baseJsonBuilder.build(); + scheduler.execute( + () -> { + try { + // Send the data + sendData(data); + } catch (Exception e) { + // Something went wrong! :( + if (logErrors) { + errorLogger.accept("Could not submit bStats metrics data", e); + } + } + }); + } + + private void sendData(JsonObjectBuilder.JsonObject data) throws Exception { + if (logSentData) { + infoLogger.accept("Sent bStats metrics data: " + data.toString()); + } + String url = String.format(REPORT_URL, platform); + HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); + connection.setRequestProperty("User-Agent", "Metrics-Service/1"); + connection.setDoOutput(true); + try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { + outputStream.write(compressedData); + } + StringBuilder builder = new StringBuilder(); + try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { + String line; + while ((line = bufferedReader.readLine()) != null) { + builder.append(line); + } + } + if (logResponseStatusText) { + infoLogger.accept("Sent data to bStats and received response: " + builder); + } + } + + /** Checks that the class was properly relocated. */ + private void checkRelocation() { + // You can use the property to disable the check in your test environment + if (System.getProperty("bstats.relocatecheck") == null + || !System.getProperty("bstats.relocatecheck").equals("false")) { + // Maven's Relocate is clever and changes strings, too. So we have to use this + // little "trick" ... :D + final String defaultPackage = new String(new byte[] { 'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's' }); + final String examplePackage = new String( + new byte[] { 'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e' }); + // We want to make sure no one just copy & pastes the example and uses the wrong + // package names + if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage) + || MetricsBase.class.getPackage().getName().startsWith(examplePackage)) { + throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); + } + } + } + + /** + * Gzips the given string. + * + * @param str The string to gzip. + * @return The gzipped string. + */ + private static byte[] compress(final String str) throws IOException { + if (str == null) { + return null; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { + gzip.write(str.getBytes(StandardCharsets.UTF_8)); + } + return outputStream.toByteArray(); + } + } + + public static class AdvancedBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue().length == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class SimplePie extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimplePie(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + String value = callable.call(); + if (value == null || value.isEmpty()) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("value", value).build(); + } + } + + public static class DrilldownPie extends CustomChart { + + private final Callable>> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public DrilldownPie(String chartId, Callable>> callable) { + super(chartId); + this.callable = callable; + } + + @Override + public JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map> map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean reallyAllSkipped = true; + for (Map.Entry> entryValues : map.entrySet()) { + JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); + boolean allSkipped = true; + for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { + valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue()); + allSkipped = false; + } + if (!allSkipped) { + reallyAllSkipped = false; + valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build()); + } + } + if (reallyAllSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class SingleLineChart extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SingleLineChart(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + int value = callable.call(); + if (value == 0) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("value", value).build(); + } + } + + public static class MultiLineChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public MultiLineChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public static class AdvancedPie extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedPie(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + // Skip this invalid + continue; + } + allSkipped = false; + valuesBuilder.appendField(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + public abstract static class CustomChart { + + private final String chartId; + + protected CustomChart(String chartId) { + if (chartId == null) { + throw new IllegalArgumentException("chartId must not be null"); + } + this.chartId = chartId; + } + + public JsonObjectBuilder.JsonObject getRequestJsonObject( + BiConsumer errorLogger, boolean logErrors) { + JsonObjectBuilder builder = new JsonObjectBuilder(); + builder.appendField("chartId", chartId); + try { + JsonObjectBuilder.JsonObject data = getChartData(); + if (data == null) { + // If the data is null we don't send the chart. + return null; + } + builder.appendField("data", data); + } catch (Throwable t) { + if (logErrors) { + errorLogger.accept("Failed to get data for custom chart with id " + chartId, t); + } + return null; + } + return builder.build(); + } + + protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception; + } + + public static class SimpleBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimpleBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JsonObjectBuilder.JsonObject getChartData() throws Exception { + JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + for (Map.Entry entry : map.entrySet()) { + valuesBuilder.appendField(entry.getKey(), new int[] { entry.getValue() }); + } + return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); + } + } + + /** + * An extremely simple JSON builder. + * + *

+ * While this class is neither feature-rich nor the most performant one, it's + * sufficient enough + * for its use-case. + */ + public static class JsonObjectBuilder { + + private StringBuilder builder = new StringBuilder(); + + private boolean hasAtLeastOneField = false; + + public JsonObjectBuilder() { + builder.append("{"); + } + + /** + * Appends a null field to the JSON. + * + * @param key The key of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendNull(String key) { + appendFieldUnescaped(key, "null"); + return this; + } + + /** + * Appends a string field to the JSON. + * + * @param key The key of the field. + * @param value The value of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, String value) { + if (value == null) { + throw new IllegalArgumentException("JSON value must not be null"); + } + appendFieldUnescaped(key, "\"" + escape(value) + "\""); + return this; + } + + /** + * Appends an integer field to the JSON. + * + * @param key The key of the field. + * @param value The value of the field. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, int value) { + appendFieldUnescaped(key, String.valueOf(value)); + return this; + } + + /** + * Appends an object to the JSON. + * + * @param key The key of the field. + * @param object The object. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, JsonObject object) { + if (object == null) { + throw new IllegalArgumentException("JSON object must not be null"); + } + appendFieldUnescaped(key, object.toString()); + return this; + } + + /** + * Appends a string array to the JSON. + * + * @param key The key of the field. + * @param values The string array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, String[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = Arrays.stream(values) + .map(value -> "\"" + escape(value) + "\"") + .collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends an integer array to the JSON. + * + * @param key The key of the field. + * @param values The integer array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, int[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends an object array to the JSON. + * + * @param key The key of the field. + * @param values The integer array. + * @return A reference to this object. + */ + public JsonObjectBuilder appendField(String key, JsonObject[] values) { + if (values == null) { + throw new IllegalArgumentException("JSON values must not be null"); + } + String escapedValues = Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(",")); + appendFieldUnescaped(key, "[" + escapedValues + "]"); + return this; + } + + /** + * Appends a field to the object. + * + * @param key The key of the field. + * @param escapedValue The escaped value of the field. + */ + private void appendFieldUnescaped(String key, String escapedValue) { + if (builder == null) { + throw new IllegalStateException("JSON has already been built"); + } + if (key == null) { + throw new IllegalArgumentException("JSON key must not be null"); + } + if (hasAtLeastOneField) { + builder.append(","); + } + builder.append("\"").append(escape(key)).append("\":").append(escapedValue); + hasAtLeastOneField = true; + } + + /** + * Builds the JSON string and invalidates this builder. + * + * @return The built JSON string. + */ + public JsonObject build() { + if (builder == null) { + throw new IllegalStateException("JSON has already been built"); + } + JsonObject object = new JsonObject(builder.append("}").toString()); + builder = null; + return object; + } + + /** + * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. + * + *

+ * This method escapes only the necessary characters '"', '\'. and '\u0000' - + * '\u001F'. + * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as + * "\n"). + * + * @param value The value to escape. + * @return The escaped value. + */ + private static String escape(String value) { + final StringBuilder builder = new StringBuilder(); + for (int i = 0; i < value.length(); i++) { + char c = value.charAt(i); + if (c == '"') { + builder.append("\\\""); + } else if (c == '\\') { + builder.append("\\\\"); + } else if (c <= '\u000F') { + builder.append("\\u000").append(Integer.toHexString(c)); + } else if (c <= '\u001F') { + builder.append("\\u00").append(Integer.toHexString(c)); + } else { + builder.append(c); + } + } + return builder.toString(); + } + + /** + * A super simple representation of a JSON object. + * + *

+ * This class only exists to make methods of the {@link JsonObjectBuilder} + * type-safe and not + * allow a raw string inputs for methods like + * {@link JsonObjectBuilder#appendField(String, + * JsonObject)}. + */ + public static class JsonObject { + + private final String value; + + private JsonObject(String value) { + this.value = value; + } + + @Override + public String toString() { + return value; + } + } + } +} diff --git a/src/main/java/me/wonka01/placeholders/CommunityQuestsPlaceholders.java b/src/main/java/me/wonka01/placeholders/CommunityQuestsPlaceholders.java index be551a9..a723f4c 100644 --- a/src/main/java/me/wonka01/placeholders/CommunityQuestsPlaceholders.java +++ b/src/main/java/me/wonka01/placeholders/CommunityQuestsPlaceholders.java @@ -1,13 +1,13 @@ package me.wonka01.placeholders; import me.clip.placeholderapi.expansion.PlaceholderExpansion; -import me.knighthat.apis.utils.Colorization; -import me.knighthat.apis.utils.Utils; import me.wonka01.ServerQuests.ServerQuests; import me.wonka01.ServerQuests.questcomponents.ActiveQuests; import me.wonka01.ServerQuests.questcomponents.QuestController; import me.wonka01.ServerQuests.questcomponents.QuestData; import me.wonka01.ServerQuests.questcomponents.schedulers.ParseDurationString; +import me.wonka01.ServerQuests.utils.Colorization; +import me.wonka01.ServerQuests.utils.Utils; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; @@ -77,7 +77,7 @@ public String onRequest(OfflinePlayer player, String identifier) { // %communityquests_goal_% if (identifier.startsWith("goal")) { - return String.valueOf(questData.getQuestGoal()); + return formatNumber(questData.getQuestGoal()); } // %communityquests_complete_questId% @@ -120,8 +120,8 @@ public String onRequest(OfflinePlayer player, String identifier) { // %communityquests_you_questId% if (identifier.startsWith("you")) { if (player.isOnline()) { - int playerContribution = (int) controller.getPlayerComponent().getAmountContributed((Player) player); - return "" + playerContribution; + double playerContribution = controller.getPlayerComponent().getAmountContributed((Player) player); + return formatNumber(playerContribution); } return "0"; } @@ -129,14 +129,15 @@ public String onRequest(OfflinePlayer player, String identifier) { // %communityquests_objective_goal_objId_questId% if (identifier.startsWith("objective_goal")) { int index = extractIndex(identifier.replace(questId, "")); - return "" + questData.getObjectives().get(index).getGoal(); + return formatNumber(questData.getObjectives().get(index).getGoal()); } // %communityquests_objective_completed_objId_questId% if (identifier.startsWith("objective_completed")) { // get value of index from identifier and covert it to an integer int index = extractIndex(identifier.replace(questId, "")); - return "" + questData.getObjectives().get(index).getAmountComplete(); + Double amountComplete = questData.getObjectives().get(index).getAmountComplete(); + return formatNumber(amountComplete); } // %communityquests_objective_objId_questId% @@ -175,6 +176,10 @@ public String onRequest(OfflinePlayer player, String identifier) { return null; } + private String formatNumber(Double number) { + return String.format("%,.0f", number); + } + public static int extractIndex(String input) { Pattern pattern = Pattern.compile("\\d+"); // Match one or more digits Matcher matcher = pattern.matcher(input); diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index db61800..1d36e07 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -8,7 +8,7 @@ leaderBoardSize: 5 disableBossBar: false # Material used in the donate quest menu. -donateMenuItem: EMERALD +donateMenuItem: BLACK_STAINED_GLASS_PANE # If true, blockbreak quests will not be able to break the same item multiple # times in a row. @@ -18,13 +18,13 @@ barColor: "GREEN" # If true, blockplace quests will not be able to place the same item multiple # times in a row. -disableDuplicatePlaces: false +disableDuplicatePlaces: true ## configure the location and text of a hologram that displays quest information hologram: # set to 0 or omit refresh-interval if you don't want it to refresh. Refresh is needed for dynamic placeholders refresh-interval: 60 - enabled: true + enabled: false location: world: world x: -78 @@ -39,6 +39,37 @@ hologram: Quests: TestQuest: # Quest identifier can be whatever you'd like as long as it's unique + displayName: "&cBasalt Zerstörer" + displayItem: BASALT # optional parameter to set the item used in GUIs for a given quest + # See color options here: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/boss/BarColor.html + barColor: "GREEN" + barStyle: "SEGMENTED_20" + worlds: # this parameter is optional, if included the quest will only occur in the specified worlds + - WL + - world + - WL_nether + objectives: # at least one objective is required, each quest can have infinite objectives + - type: donate + goal: 10000000 + materials: + - BASALT + description: "Liefert insgesamt 10.000.000 Basalt" + questDuration: 21d # The quest will last for 1 day or until the goal is reached, whichever comes first + rewards: + rankedRewards: + "1": # The key is the rank of the player + experience: 10000 + commands: + - "gems add %player% 10000" + "2": + experience: 5000 + commands: + - "gems add %player% 5000" + "*": # This is a wildcard, this reward will apply to all other players who participated in the quest and didn't get a ranked reward + experience: 2500 + commands: + - "eco add %player% 10000" + ZombieQuest: # Quest identifier can be whatever you'd like as long as it's unique displayName: "&cZombie and Pig Slayer" displayItem: ZOMBIE_HEAD # optional parameter to set the item used in GUIs for a given quest # See color options here: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/boss/BarColor.html @@ -48,17 +79,12 @@ Quests: - world objectives: # at least one objective is required, each quest can have infinite objectives - type: mobkill ## required, see type list for available types - dynamicGoal: "%server_online%" + dynamicGoal: "%math_{server_online}*1000%" entities: # This is an optional parameter, if it doesn't exist the quest will count ALL mob kills. entity reference: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/entity/EntityType.html - Zombie - Pig - ZOMBIFIED_PIGLIN description: "Zombies & Pigs" # A short description of the objective used in the GUI - - type: mobkill - goal: 5 - customMobNames: - - Skeleton - description: Skeletons description: "Kill 100 zombies and pigs! \nKill 50 skeletons!" questDuration: 1d # The quest will last for 1 day or until the goal is reached, whichever comes first rewards: @@ -81,21 +107,120 @@ Quests: - material: stone_sword amount: 1 displayName: "&8Stone Sword" + brewPotion: + displayName: "&6&lPotion Master" + displayItem: BREWING_STAND + objectives: + - type: brewpotion + goal: 5 + description: "&cHealth Potions" + # https://hub.spigotmc.org/javadocs/spigot/org/bukkit/potion/PotionType.html + potions: + - INSTANT_HEAL + - STRONG_HEALING + - type: brewpotion + goal: 5 + description: "&6Regen Potions" + potions: + - LONG_REGENERATION + - REGENERATION + - STRONG_REGENERATION + description: "&fBrew 5 healing and 5 regen potions!" + rewards: + rewardDisplay: + - "&a&lRewards" + - "&f- &b15 Diamonds" + - "&f- &b100 Experience" + rankedRewards: + "1": + experience: 100 + items: + - material: DIAMOND + amount: 15 + displayName: "&bDiamond!" + rewardMessage: "&6You have been rewarded with a diamond and 100 experience for completing the quest!" + enchantItem: + displayName: "&6Enchant Item" + displayItem: ENCHANTING_TABLE + objectives: + - type: enchantitem + goal: 5 + description: "&6Enchantments" + materials: + - DIAMOND_SWORD + enchantments: + - SHARPNESS + - KNOCKBACK + description: "&fEnchant 5 diamond swords with sharpness or knockback" + rewards: + rankedRewards: + "1": + experience: 100 + items: + - material: DIAMOND + amount: 1 + displayName: "&bDiamond!" + rewardMessage: "&6You have been rewarded with a diamond!" + smeltIron: + displayName: "&6Smelt Iron" + displayItem: IRON_INGOT + description: "&6Smelt 10 iron ore into iron ingots" + questDuration: 1h + objectives: + - type: furnace + goal: 10 + materials: + - IRON_INGOT + description: "&6Iron Ingots" + - type: smeltitem + goal: 10 + customNames: + - "Cooked Cod" + description: "&6Cod" + rewards: + experience: 100 + items: + - material: DIAMOND + amount: 1 + displayName: "&bDiamond" + logs: + displayName: "&2Im a lumberjack" + description: "&2chop 500 logs of each type of wood" + displayItem: OAK_WOOD + objectives: + - type: blockbreak + goal: 5 + materials: + - OAK_LOG + description: "&2oak wood" Egg: displayName: "&6Eggcellent" displayItem: EGG questDuration: 7d - barColor: "WHITE" + barColor: "RED" barStyle: "SEGMENTED_20" objectives: - - type: donate - materials: - - EGG + - type: milkcow + goal: 20 description: H1 needs some cake, you can get some eggs! rewards: items: - material: CHICKEN_SPAWN_EGG amount: 1 + ShearSheep: + displayName: "&6Shear Sheep" + displayItem: SHEARS + questDuration: 7d + barColor: "YELLOW" + barStyle: "SEGMENTED_20" + objectives: + - type: shear + goal: 20 + description: "Shear 20 sheep!" + rewards: + items: + - material: CHICKEN_SPAWN_EGG + amount: 1 MoveIt: displayName: "&6Movement Quest" displayItem: DIAMOND_BOOTS @@ -183,19 +308,19 @@ Quests: amount: 1 displayName: "&8Stone Sword" GuiQuest: - displayName: "&a&lPotato Diamond Quest" + displayName: "&l&bDiamond Miner" objectives: - type: donate goal: 60 materials: - - POTATO + - DIAMOND description: "&l&aPotatoes" - type: donate goal: 20 materials: - - DIAMOND + - POTATO description: "&l&aDiamonds" - description: "&fDonate 60 potatoes and 20 diamonds with /cq donate!" + description: '&fThe royal family is in dire need of diamonds. Bring us 60 diamonds and we will reward you handsomely! \n&fAlso throw in 20 potatoes for the next royal feast!' rewards: rankedRewards: "1": # The key is the rank of the player @@ -290,7 +415,8 @@ Quests: displayName: "&bPowerful Diamond Sword" MythicQuest: displayName: "&c&lKill mythic mobs" - objective: + # displayItem: DRAGON_HEAD + objectives: - type: mythicmob goal: 4 description: "Spiderz" diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml index e6342a4..4b96baf 100644 --- a/src/main/resources/messages.yml +++ b/src/main/resources/messages.yml @@ -4,7 +4,7 @@ reloadCommand: "&AThe Community Quests configuration has reloaded successfully." helpMessage: "&eThe Community Quests plugin allows servers to run quests that the entire server either works together or competes against one another to complete an objective. To view active quests use /cq view, to begin a new quests use /cq start. For donation quests, use the /cq donate command to open up the donation GUI." noActiveDonateQuests: "&cThere are no active quests that require you to donate items." invalidQuestName: "&cThe quest name you entered does not exist" -invalidQuestType: "&cYou must enter a quest type; coop or comp" +invalidQuestType: "&cYou must enter a quest type; coop, comp or coll" noActiveQuests: "&cThere are currently no active quests running on the server." questLimitReached: "&cThe quest could not be created, the number of active quests has reached its limit" endAllQuestsMessage: "&cAll active quests have been ended" @@ -43,6 +43,7 @@ you: "&fYou" leader: "&n&7Leader" progress: "&bProgress" topContributors: "&6Top Contributors" +completed: "&7Completed:" # GUI titles viewQuests: "Active Quests" @@ -50,6 +51,7 @@ startQuest: "Begin Quest Menu" stopQuest: "End Quest Menu" typeMenu: "Select an Event Type" donateMenu: "Place item in the center slot!" +historyMenu: "&7Quest History" endQuestText: "&eClick to end the quest" goBack: "&cGo Back" diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 3b2310b..d4ce559 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -8,6 +8,7 @@ commands: communityquests: aliases: [cq, quests] description: Commands for Community Quests Plugin + usage: / [start|stop|reload|view|rewards|donate|money|showmessages|togglebar|claim|clearrewards|schedule] softdepend: - Vault