aboutsummaryrefslogtreecommitdiffstats
path: root/bukkit/src/main
diff options
context:
space:
mode:
authorLMBishop <13875753+LMBishop@users.noreply.github.com>2021-06-17 13:32:02 +0100
committerLMBishop <13875753+LMBishop@users.noreply.github.com>2021-06-17 13:32:02 +0100
commitaf7e1e435f577bbf9742bb526ac00a71a21c219c (patch)
tree0d0b4cff2dd42721e7673a518394084ea67d90ee /bukkit/src/main
parent5c3d30840bb62c047f077d9ec1cec6b8572cc17b (diff)
Convert to multi module project
- Common module to provide an abstract Quests plugin - Api is still todo
Diffstat (limited to 'bukkit/src/main')
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/BukkitQuestsLogger.java66
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/BukkitQuestsPlugin.java515
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/BukkitServerSchedulerAdapter.java22
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerCancelQuestEvent.java51
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerFinishQuestEvent.java51
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerQuestEvent.java19
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerStartQuestEvent.java52
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerStartTrackQuestEvent.java32
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerStopTrackQuestEvent.java27
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PreStartQuestEvent.java57
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/QuestsCommand.java644
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/config/BukkitQuestsConfig.java91
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/config/BukkitQuestsLoader.java331
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/coreprotect/AbstractCoreProtectHook.java16
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/coreprotect/CoreProtectHook.java29
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/coreprotect/CoreProtectNoHook.java10
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/itemgetter/ItemGetter.java46
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/itemgetter/ItemGetterLatest.java240
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/itemgetter/ItemGetter_1_13.java229
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/itemgetter/ItemGetter_Late_1_8.java158
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/papi/AbstractPlaceholderAPIHook.java14
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/papi/PlaceholderAPIHook.java27
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/papi/QuestsPlaceholders.java346
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/title/Title.java8
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/title/Title_Bukkit.java12
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/title/Title_BukkitNoTimings.java13
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/title/Title_Other.java11
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/listener/PlayerJoinListener.java51
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/listener/PlayerLeaveListener.java24
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/CancelQMenu.java85
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/CategoryQMenu.java171
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/DailyQMenu.java97
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/MenuController.java176
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/QMenu.java13
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/QuestQMenu.java288
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/QuestSortWrapper.java44
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/StartedQMenu.java175
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/element/CategoryMenuElement.java64
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/element/CustomMenuElement.java17
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/element/MenuElement.java9
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/element/QuestMenuElement.java120
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/element/SpacerMenuElement.java15
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/itemstack/QItemStack.java149
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/itemstack/QItemStackRegistry.java36
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/questcompleter/BukkitQuestCompleter.java106
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/questcontroller/DailyQuestController.java184
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/questcontroller/NormalQuestController.java285
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/runnable/QuestsAutoSaveRunnable.java42
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/storage/MySqlStorageProvider.java276
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/storage/YamlStorageProvider.java138
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/BukkitTaskType.java16
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/BukkitTaskTypeManager.java25
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/BreedingTaskType.java94
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/BrewingCertainTaskType.java114
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/BrewingTaskType.java107
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/BuildingCertainTaskType.java187
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/BuildingTaskType.java78
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/CommandTaskType.java91
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/DealDamageTaskType.java86
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/DistancefromTaskType.java108
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/EnchantingTaskType.java80
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/ExpEarnTaskType.java78
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/FarmingTaskType.java85
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/FishingTaskType.java90
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/InventoryTaskType.java168
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MilkingTaskType.java91
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MiningCertainTaskType.java195
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MiningTaskType.java79
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MobkillingCertainTaskType.java137
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MobkillingTaskType.java109
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/PermissionTaskType.java65
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/PlayerkillingTaskType.java91
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/PlaytimeTaskType.java87
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/PositionTaskType.java98
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/ShearingTaskType.java86
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/TamingTaskType.java85
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/WalkingTaskType.java85
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/ASkyBlockLevelTaskType.java66
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/BentoBoxLevelTaskType.java100
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/CitizensDeliverTaskType.java138
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/CitizensInteractTaskType.java68
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/EssentialsBalanceTaskType.java95
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/EssentialsMoneyEarnTaskType.java73
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/IridiumSkyblockValueType.java87
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/MythicMobsKillingType.java107
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/PlaceholderAPIEvaluateTaskType.java151
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/ShopGUIPlusBuyCertainTaskType.java85
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/ShopGUIPlusSellCertainTaskType.java85
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/uSkyBlockLevelTaskType.java67
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/Chat.java40
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/MenuUtils.java74
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/Messages.java83
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/TaskUtils.java90
-rw-r--r--bukkit/src/main/resources/plugin.yml30
-rw-r--r--bukkit/src/main/resources/resources/bukkit/config.yml355
-rw-r--r--bukkit/src/main/resources/resources/bukkit/quests/README.txt40
-rw-r--r--bukkit/src/main/resources/resources/bukkit/quests/example1.yml61
-rw-r--r--bukkit/src/main/resources/resources/bukkit/quests/example2.yml47
-rw-r--r--bukkit/src/main/resources/resources/bukkit/quests/example3.yml49
-rw-r--r--bukkit/src/main/resources/resources/bukkit/quests/example4.yml50
-rw-r--r--bukkit/src/main/resources/resources/bukkit/quests/example5.yml38
-rw-r--r--bukkit/src/main/resources/resources/bukkit/quests/example6.yml35
-rw-r--r--bukkit/src/main/resources/resources/bukkit/quests/example7.yml37
103 files changed, 10448 insertions, 0 deletions
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/BukkitQuestsLogger.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/BukkitQuestsLogger.java
new file mode 100644
index 00000000..d50752fd
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/BukkitQuestsLogger.java
@@ -0,0 +1,66 @@
+package com.leonardobishop.quests.bukkit;
+
+import com.leonardobishop.quests.common.logger.QuestsLogger;
+
+public class BukkitQuestsLogger implements QuestsLogger {
+
+ private final BukkitQuestsPlugin plugin;
+ private LoggingLevel serverLoggingLevel;
+
+ public BukkitQuestsLogger(BukkitQuestsPlugin plugin) {
+ this.plugin = plugin;
+ serverLoggingLevel = LoggingLevel.INFO;
+ }
+
+ @Override
+ public LoggingLevel getServerLoggingLevel() {
+ return serverLoggingLevel;
+ }
+
+ @Override
+ public void setServerLoggingLevel(LoggingLevel serverLoggingLevel) {
+ this.serverLoggingLevel = serverLoggingLevel;
+ }
+
+ @Override
+ public void log(String str, LoggingLevel level) {
+ if (serverLoggingLevel.getNumericVerbosity() < level.getNumericVerbosity()) {
+ return;
+ }
+ switch (level) {
+ case DEBUG:
+ plugin.getLogger().info("Debug: " + str);
+ break;
+ case INFO:
+ plugin.getLogger().info(str);
+ break;
+ case ERROR:
+ plugin.getLogger().severe(str);
+ break;
+ case WARNING:
+ plugin.getLogger().warning(str);
+ break;
+ }
+ }
+
+ @Override
+ public void debug(String str) {
+ log(str, LoggingLevel.DEBUG);
+ }
+
+ @Override
+ public void info(String str) {
+ log(str, LoggingLevel.INFO);
+ }
+
+ @Override
+ public void warning(String str) {
+ log(str, LoggingLevel.WARNING);
+ }
+
+ @Override
+ public void severe(String str) {
+ log(str, LoggingLevel.ERROR);
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/BukkitQuestsPlugin.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/BukkitQuestsPlugin.java
new file mode 100644
index 00000000..eeb359bc
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/BukkitQuestsPlugin.java
@@ -0,0 +1,515 @@
+package com.leonardobishop.quests.bukkit;
+
+import com.leonardobishop.quests.bukkit.command.QuestsCommand;
+import com.leonardobishop.quests.bukkit.config.BukkitQuestsConfig;
+import com.leonardobishop.quests.bukkit.config.BukkitQuestsLoader;
+import com.leonardobishop.quests.bukkit.hook.coreprotect.AbstractCoreProtectHook;
+import com.leonardobishop.quests.bukkit.hook.coreprotect.CoreProtectHook;
+import com.leonardobishop.quests.bukkit.hook.coreprotect.CoreProtectNoHook;
+import com.leonardobishop.quests.bukkit.hook.itemgetter.ItemGetter;
+import com.leonardobishop.quests.bukkit.hook.itemgetter.ItemGetterLatest;
+import com.leonardobishop.quests.bukkit.hook.itemgetter.ItemGetter_1_13;
+import com.leonardobishop.quests.bukkit.hook.itemgetter.ItemGetter_Late_1_8;
+import com.leonardobishop.quests.bukkit.hook.papi.AbstractPlaceholderAPIHook;
+import com.leonardobishop.quests.bukkit.hook.papi.PlaceholderAPIHook;
+import com.leonardobishop.quests.bukkit.hook.title.Title;
+import com.leonardobishop.quests.bukkit.hook.title.Title_Bukkit;
+import com.leonardobishop.quests.bukkit.hook.title.Title_BukkitNoTimings;
+import com.leonardobishop.quests.bukkit.hook.title.Title_Other;
+import com.leonardobishop.quests.bukkit.listener.PlayerJoinListener;
+import com.leonardobishop.quests.bukkit.listener.PlayerLeaveListener;
+import com.leonardobishop.quests.bukkit.questcompleter.BukkitQuestCompleter;
+import com.leonardobishop.quests.bukkit.menu.MenuController;
+import com.leonardobishop.quests.bukkit.menu.itemstack.QItemStackRegistry;
+import com.leonardobishop.quests.bukkit.questcontroller.NormalQuestController;
+import com.leonardobishop.quests.bukkit.runnable.QuestsAutoSaveRunnable;
+import com.leonardobishop.quests.bukkit.storage.MySqlStorageProvider;
+import com.leonardobishop.quests.bukkit.storage.YamlStorageProvider;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskTypeManager;
+import com.leonardobishop.quests.bukkit.tasktype.type.BreedingTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.BrewingTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.BuildingCertainTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.BuildingTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.CommandTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.DealDamageTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.DistancefromTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.EnchantingTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.ExpEarnTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.FishingTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.InventoryTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.MilkingTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.MiningCertainTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.MiningTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.MobkillingCertainTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.MobkillingTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.PermissionTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.PlayerkillingTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.PlaytimeTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.PositionTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.ShearingTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.TamingTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.WalkingTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.dependent.ASkyBlockLevelTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.dependent.BentoBoxLevelTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.dependent.CitizensDeliverTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.dependent.CitizensInteractTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.dependent.EssentialsBalanceTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.dependent.EssentialsMoneyEarnTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.dependent.MythicMobsKillingType;
+import com.leonardobishop.quests.bukkit.tasktype.type.dependent.PlaceholderAPIEvaluateTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.dependent.ShopGUIPlusBuyCertainTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.dependent.ShopGUIPlusSellCertainTaskType;
+import com.leonardobishop.quests.bukkit.tasktype.type.dependent.uSkyBlockLevelTaskType;
+import com.leonardobishop.quests.bukkit.util.Messages;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.config.ConfigProblemDescriptions;
+import com.leonardobishop.quests.common.config.QuestsConfig;
+import com.leonardobishop.quests.common.logger.QuestsLogger;
+import com.leonardobishop.quests.common.player.QPlayerManager;
+import com.leonardobishop.quests.common.plugin.Quests;
+import com.leonardobishop.quests.common.quest.QuestCompleter;
+import com.leonardobishop.quests.common.quest.QuestManager;
+import com.leonardobishop.quests.common.questcontroller.QuestController;
+import com.leonardobishop.quests.common.scheduler.ServerScheduler;
+import com.leonardobishop.quests.common.storage.StorageProvider;
+import com.leonardobishop.quests.common.tasktype.TaskType;
+import com.leonardobishop.quests.common.tasktype.TaskTypeManager;
+import com.leonardobishop.quests.common.updater.Updater;
+import org.bstats.bukkit.MetricsLite;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.plugin.java.JavaPlugin;
+import org.bukkit.scheduler.BukkitTask;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+public class BukkitQuestsPlugin extends JavaPlugin implements Quests {
+
+ private QuestsLogger questsLogger;
+ private QuestManager questManager;
+ private TaskTypeManager taskTypeManager;
+ private QPlayerManager qPlayerManager;
+ private QuestController questController;
+ private QuestCompleter questCompleter;
+ private BukkitQuestsConfig questsConfig;
+ private Updater updater;
+ private ServerScheduler serverScheduler;
+ private StorageProvider storageProvider;
+
+ private boolean validConfiguration;
+ private Map<String, List<ConfigProblem>> configProblems;
+
+ private QItemStackRegistry qItemStackRegistry;
+ private MenuController menuController;
+ private AbstractPlaceholderAPIHook placeholderAPIHook;
+ private AbstractCoreProtectHook coreProtectHook;
+ private ItemGetter itemGetter;
+ private Title titleHandle;
+
+ private BukkitTask questAutoSaveTask;
+ private BukkitTask questQueuePollTask;
+
+ @Override
+ public QuestsLogger getQuestsLogger() {
+ return questsLogger;
+ }
+
+ @Override
+ public QuestManager getQuestManager() {
+ return questManager;
+ }
+
+ @Override
+ public TaskTypeManager getTaskTypeManager() {
+ return taskTypeManager;
+ }
+
+ @Override
+ public QPlayerManager getPlayerManager() {
+ return qPlayerManager;
+ }
+
+ @Override
+ public QuestController getQuestController() {
+ return questController;
+ }
+
+ @Override
+ public QuestCompleter getQuestCompleter() {
+ return questCompleter;
+ }
+
+ @Override
+ public QuestsConfig getQuestsConfig() {
+ return questsConfig;
+ }
+
+ @Override
+ public Updater getUpdater() {
+ return updater;
+ }
+
+ @Override
+ public StorageProvider getStorageProvider() {
+ return storageProvider;
+ }
+
+ @Override
+ public ServerScheduler getScheduler() {
+ return serverScheduler;
+ }
+
+ @Override
+ public void onEnable() {
+ this.questsLogger = new BukkitQuestsLogger(this);
+ this.generateConfigurations();
+ this.questsConfig = new BukkitQuestsConfig(new File(super.getDataFolder() + File.separator + "config.yml"));
+ this.questManager = new QuestManager(this);
+ this.taskTypeManager = new BukkitTaskTypeManager(this);
+ this.serverScheduler = new BukkitServerSchedulerAdapter(this);
+
+ if (!this.reloadBaseConfiguration()) {
+ questsLogger.severe("Plugin cannot start into a stable state as the configuration is broken!");
+ super.getServer().getPluginManager().disablePlugin(this);
+ return;
+ }
+
+ String configuredProvider = questsConfig.getString("options.storage.provider", "yaml");
+ switch (configuredProvider.toLowerCase()) {
+ default:
+ questsLogger.warning("No valid storage provider is configured - Quests will use YAML storage as a default");
+ case "yaml":
+ this.storageProvider = new YamlStorageProvider(this);
+ break;
+ case "mysql":
+ this.storageProvider = new MySqlStorageProvider(this, this.getConfig().getConfigurationSection("options.storage.database-settings"));
+ }
+
+ try {
+ storageProvider.init();
+ } catch (Exception e) {
+ questsLogger.severe("An error occurred initialising the storage provider.");
+ e.printStackTrace();
+ }
+
+ this.setupVersionSpecific();
+
+ Messages.setPlugin(this);
+ this.qPlayerManager = new QPlayerManager(this, storageProvider, questController);
+ this.menuController = new MenuController(this);
+ this.qItemStackRegistry = new QItemStackRegistry();
+ this.questCompleter = new BukkitQuestCompleter(this);
+
+ MetricsLite metrics = new MetricsLite(this, 3443);
+ if (metrics.isEnabled()) {
+ this.getQuestsLogger().info("Metrics started. This can be disabled at /plugins/bStats/config.yml.");
+ }
+
+ if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
+ this.placeholderAPIHook = new PlaceholderAPIHook();
+ this.placeholderAPIHook.registerExpansion(this);
+ }
+
+ if (Bukkit.getPluginManager().isPluginEnabled("CoreProtect")) {
+ this.coreProtectHook = new CoreProtectHook();
+ } else {
+ this.coreProtectHook = new CoreProtectNoHook();
+ }
+
+ boolean ignoreUpdates = false;
+ try {
+ ignoreUpdates = new File(this.getDataFolder() + File.separator + "stfuQuestsUpdate").exists();
+ } catch (Throwable ignored) { }
+
+
+ this.updater = new Updater(this, super.getDescription().getVersion(), !ignoreUpdates);
+ if (!ignoreUpdates) {
+ serverScheduler.doAsync(() -> {
+ updater.check();
+ });
+ }
+
+ super.getCommand("quests").setExecutor(new QuestsCommand(this));
+
+ super.getServer().getPluginManager().registerEvents(new PlayerJoinListener(this), this);
+ super.getServer().getPluginManager().registerEvents(menuController, this);
+ super.getServer().getPluginManager().registerEvents(new PlayerLeaveListener(this), this);
+
+ // register task types after the server has fully started
+ Bukkit.getScheduler().runTask(this, () -> {
+ taskTypeManager.registerTaskType(new MiningTaskType(this));
+ taskTypeManager.registerTaskType(new MiningCertainTaskType(this));
+ taskTypeManager.registerTaskType(new BuildingTaskType(this));
+ taskTypeManager.registerTaskType(new BuildingCertainTaskType(this));
+ taskTypeManager.registerTaskType(new MobkillingTaskType(this));
+ taskTypeManager.registerTaskType(new MobkillingCertainTaskType(this));
+ taskTypeManager.registerTaskType(new PlayerkillingTaskType(this));
+ taskTypeManager.registerTaskType(new FishingTaskType(this));
+ taskTypeManager.registerTaskType(new InventoryTaskType(this));
+ taskTypeManager.registerTaskType(new WalkingTaskType(this));
+ taskTypeManager.registerTaskType(new TamingTaskType(this));
+ taskTypeManager.registerTaskType(new MilkingTaskType(this));
+ taskTypeManager.registerTaskType(new ShearingTaskType(this));
+ taskTypeManager.registerTaskType(new PositionTaskType(this));
+ taskTypeManager.registerTaskType(new PlaytimeTaskType(this));
+ taskTypeManager.registerTaskType(new BrewingTaskType(this));
+ taskTypeManager.registerTaskType(new ExpEarnTaskType(this));
+ taskTypeManager.registerTaskType(new BreedingTaskType(this));
+ taskTypeManager.registerTaskType(new EnchantingTaskType(this));
+ taskTypeManager.registerTaskType(new DealDamageTaskType(this));
+ taskTypeManager.registerTaskType(new PermissionTaskType(this));
+ taskTypeManager.registerTaskType(new DistancefromTaskType(this));
+ taskTypeManager.registerTaskType(new CommandTaskType(this));
+ // TODO: FIX
+ // taskTypeManager.registerTaskType(new BrewingCertainTaskType());
+ if (Bukkit.getPluginManager().isPluginEnabled("ASkyBlock")) {
+ taskTypeManager.registerTaskType(new ASkyBlockLevelTaskType(this));
+ }
+ if (Bukkit.getPluginManager().isPluginEnabled("BentoBox")) {
+ BentoBoxLevelTaskType.register(this, taskTypeManager);
+ }
+ //TODO FIX
+// if (Bukkit.getPluginManager().isPluginEnabled("IridiumSkyblock")) {
+// taskTypeManager.registerTaskType(new IridiumSkyblockValueType());
+// }
+ if (Bukkit.getPluginManager().isPluginEnabled("uSkyBlock")) {
+ taskTypeManager.registerTaskType(new uSkyBlockLevelTaskType(this));
+ }
+ if (Bukkit.getPluginManager().isPluginEnabled("Citizens")) {
+ taskTypeManager.registerTaskType(new CitizensDeliverTaskType(this));
+ taskTypeManager.registerTaskType(new CitizensInteractTaskType(this));
+ }
+ if (Bukkit.getPluginManager().isPluginEnabled("MythicMobs")) {
+ taskTypeManager.registerTaskType(new MythicMobsKillingType(this));
+ }
+ if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) {
+ taskTypeManager.registerTaskType(new PlaceholderAPIEvaluateTaskType(this));
+ }
+ if (Bukkit.getPluginManager().isPluginEnabled("Essentials")) {
+ taskTypeManager.registerTaskType(new EssentialsMoneyEarnTaskType(this));
+ taskTypeManager.registerTaskType(new EssentialsBalanceTaskType(this));
+ }
+ if (Bukkit.getPluginManager().isPluginEnabled("ShopGUIPlus")) {
+ // not tested
+ taskTypeManager.registerTaskType(new ShopGUIPlusBuyCertainTaskType(this));
+ taskTypeManager.registerTaskType(new ShopGUIPlusSellCertainTaskType(this));
+ }
+
+ taskTypeManager.closeRegistrations();
+ reloadQuests();
+
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ qPlayerManager.loadPlayer(player.getUniqueId());
+ }
+ });
+ }
+
+ @Override
+ public void reloadQuests() {
+ if (this.reloadBaseConfiguration()) {
+ BukkitQuestsLoader questsLoader = new BukkitQuestsLoader(this);
+ configProblems = questsLoader.loadQuests(new File(super.getDataFolder() + File.separator + "quests"));
+
+ for (TaskType taskType : taskTypeManager.getTaskTypes()) {
+ try {
+ taskType.onReady();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ } else {
+ configProblems = Collections.singletonMap("<MAIN CONFIG> config.yml",
+ Collections.singletonList(new ConfigProblem(ConfigProblem.ConfigProblemType.ERROR, ConfigProblemDescriptions.MALFORMED_YAML.getDescription())));
+ }
+ }
+
+ public ItemStack getItemStack(String path, ConfigurationSection config, ItemGetter.Filter... excludes) {
+ return itemGetter.getItem(path, config, excludes);
+ }
+
+ private boolean reloadBaseConfiguration() {
+ this.validConfiguration = questsConfig.loadConfig();
+
+ if (validConfiguration) {
+ int loggingLevel = questsConfig.getInt("options.verbose-logging-level", 2);
+ questsLogger.setServerLoggingLevel(QuestsLogger.LoggingLevel.fromNumber(loggingLevel));
+
+ switch (questsConfig.getString("quest-mode.mode", "normal").toLowerCase()) {
+ default:
+ case "normal":
+ questController = new NormalQuestController(this);
+ //TODO the other one
+ }
+
+ long autoSaveInterval = this.getConfig().getLong("options.performance-tweaking.quest-autosave-interval", 12000);
+ try {
+ if (questAutoSaveTask != null) questAutoSaveTask.cancel();
+ questAutoSaveTask = Bukkit.getScheduler().runTaskTimer(this, () -> new QuestsAutoSaveRunnable(this), autoSaveInterval, autoSaveInterval);
+ } catch (Exception ex) {
+ questsLogger.debug("Cannot cancel and restart quest autosave task");
+ }
+
+ long queueExecuteInterval = this.getConfig().getLong("options.performance-tweaking.quest-queue-executor-interval", 1);
+ try {
+ if (questQueuePollTask != null) questQueuePollTask.cancel();
+ questQueuePollTask = Bukkit.getScheduler().runTaskTimer(this, (BukkitQuestCompleter) questCompleter, queueExecuteInterval, queueExecuteInterval);
+ } catch (Exception ex) {
+ questsLogger.debug("Cannot cancel and restart queue executor task");
+ }
+ }
+ return validConfiguration;
+ }
+
+ private void setupVersionSpecific() {
+ String version;
+ try {
+ version = Bukkit.getServer().getClass().getPackage().getName().replace(".", ",").split(",")[3];
+ } catch (ArrayIndexOutOfBoundsException e) {
+ getQuestsLogger().warning("Failed to resolve server version - some features will not work!");
+ titleHandle = new Title_Other();
+ itemGetter = new ItemGetter_Late_1_8();
+ return;
+ }
+
+ getQuestsLogger().info("Your server is running version " + version + ".");
+
+ if (version.startsWith("v1_7")) {
+ titleHandle = new Title_Other();
+ } else if (version.startsWith("v1_8") || version.startsWith("v1_9") || version.startsWith("v1_10")) {
+ titleHandle = new Title_BukkitNoTimings();
+ } else {
+ titleHandle = new Title_Bukkit();
+ }
+
+ if (version.startsWith("v1_7") || version.startsWith("v1_8") || version.startsWith("v1_9")
+ || version.startsWith("v1_10") || version.startsWith("v1_11") || version.startsWith("v1_12")) {
+ itemGetter = new ItemGetter_Late_1_8();
+ } else if (version.startsWith("v1_13")) {
+ itemGetter = new ItemGetter_1_13();
+ } else {
+ itemGetter = new ItemGetterLatest();
+ }
+
+ questsConfig.setItemGetter(itemGetter);
+
+ if (titleHandle instanceof Title_Bukkit) {
+ getQuestsLogger().info("Titles have been enabled.");
+ } else if (titleHandle instanceof Title_BukkitNoTimings) {
+ getQuestsLogger().info("Titles have been enabled, although they have limited timings.");
+ } else {
+ getQuestsLogger().info("Titles are not supported for this version.");
+ }
+ }
+
+ private void generateConfigurations() {
+ File directory = new File(String.valueOf(this.getDataFolder()));
+ if (!directory.exists() && !directory.isDirectory()) {
+ directory.mkdir();
+ }
+
+ File config = new File(this.getDataFolder() + File.separator + "config.yml");
+ if (!config.exists()) {
+ try {
+ config.createNewFile();
+ try (InputStream in = BukkitQuestsPlugin.class.getClassLoader().getResourceAsStream("resources/bukkit/config.yml");
+ OutputStream out = new FileOutputStream(config)) {
+ byte[] buffer = new byte[1024];
+ int lenght = in.read(buffer);
+ while (lenght != -1) {
+ out.write(buffer, 0, lenght);
+ lenght = in.read(buffer);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ File questsDirectory = new File(this.getDataFolder() + File.separator + "quests");
+ if (!questsDirectory.exists() && !questsDirectory.isDirectory()) {
+ questsDirectory.mkdir();
+
+ ArrayList<String> examples = new ArrayList<>();
+ examples.add("example1.yml");
+ examples.add("example2.yml");
+ examples.add("example3.yml");
+ examples.add("example4.yml");
+ examples.add("example5.yml");
+ examples.add("example6.yml");
+ examples.add("example7.yml");
+ examples.add("README.txt");
+
+ for (String name : examples) {
+ File file = new File(this.getDataFolder() + File.separator + "quests" + File.separator + name);
+ try {
+ file.createNewFile();
+ try (InputStream in = BukkitQuestsPlugin.class.getClassLoader().getResourceAsStream("resources/bukkit/quests/" + name);
+ OutputStream out = new FileOutputStream(file)) {
+ byte[] buffer = new byte[1024];
+ int lenght = in.read(buffer);
+ while (lenght != -1) {
+ out.write(buffer, 0, lenght);
+ lenght = in.read(buffer);
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ }
+
+ public boolean isValidConfiguration() {
+ return validConfiguration;
+ }
+
+ public Map<String, List<ConfigProblem>> getConfigProblems() {
+ return configProblems;
+ }
+
+ public AbstractPlaceholderAPIHook getPlaceholderAPIHook() {
+ return placeholderAPIHook;
+ }
+
+ public AbstractCoreProtectHook getCoreProtectHook() {
+ return coreProtectHook;
+ }
+
+ public ItemGetter getItemGetter() {
+ return itemGetter;
+ }
+
+ public Title getTitleHandle() {
+ return titleHandle;
+ }
+
+ public QItemStackRegistry getQItemStackRegistry() {
+ return qItemStackRegistry;
+ }
+
+ public MenuController getMenuController() {
+ return menuController;
+ }
+
+ @NotNull
+ @Override
+ public FileConfiguration getConfig() {
+ return questsConfig.getConfig();
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/BukkitServerSchedulerAdapter.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/BukkitServerSchedulerAdapter.java
new file mode 100644
index 00000000..8619642b
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/BukkitServerSchedulerAdapter.java
@@ -0,0 +1,22 @@
+package com.leonardobishop.quests.bukkit;
+
+import com.leonardobishop.quests.common.scheduler.ServerScheduler;
+
+public class BukkitServerSchedulerAdapter implements ServerScheduler {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public BukkitServerSchedulerAdapter(BukkitQuestsPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void doSync(Runnable runnable) {
+ plugin.getServer().getScheduler().runTask(plugin, runnable);
+ }
+
+ @Override
+ public void doAsync(Runnable runnable) {
+ plugin.getServer().getScheduler().runTaskAsynchronously(plugin, runnable);
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerCancelQuestEvent.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerCancelQuestEvent.java
new file mode 100644
index 00000000..af431626
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerCancelQuestEvent.java
@@ -0,0 +1,51 @@
+package com.leonardobishop.quests.bukkit.api.event;
+
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import org.bukkit.entity.Player;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+public class PlayerCancelQuestEvent extends PlayerQuestEvent {
+ private final static HandlerList handlers = new HandlerList();
+ private final QuestProgress questProgress;
+ private String questCancelMessage;
+
+ public PlayerCancelQuestEvent(@NotNull Player who, @NotNull QPlayer questPlayer, @NotNull QuestProgress questProgress, String questCancelMessage) {
+ super(who, questPlayer);
+ this.questProgress = questProgress;
+ this.questCancelMessage = questCancelMessage;
+ }
+
+ /**
+ * @return The quest progress
+ */
+ public QuestProgress getQuestProgress() {
+ return this.questProgress;
+ }
+
+ /**
+ * @return The message sent to the player that cancel the quest
+ */
+ public String getQuestCancelMessage() {
+ return this.questCancelMessage;
+ }
+
+ /**
+ * @param questCancelMessage The quest cancel message
+ * @return The quest cancel message set
+ */
+ public String setQuestCancelMessage(String questCancelMessage) {
+ return (this.questCancelMessage = questCancelMessage);
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerFinishQuestEvent.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerFinishQuestEvent.java
new file mode 100644
index 00000000..d38d4f78
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerFinishQuestEvent.java
@@ -0,0 +1,51 @@
+package com.leonardobishop.quests.bukkit.api.event;
+
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import org.bukkit.entity.Player;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+public class PlayerFinishQuestEvent extends PlayerQuestEvent {
+ private final static HandlerList handlers = new HandlerList();
+ private final QuestProgress questProgress;
+ private String questFinishMessage;
+
+ public PlayerFinishQuestEvent(@NotNull Player who, @NotNull QPlayer questPlayer, @NotNull QuestProgress questProgress, String questFinishMessage) {
+ super(who, questPlayer);
+ this.questProgress = questProgress;
+ this.questFinishMessage = questFinishMessage;
+ }
+
+ /**
+ * @return The quest progress
+ */
+ public QuestProgress getQuestProgress() {
+ return this.questProgress;
+ }
+
+ /**
+ * @return The message sent to the player that finish the quest
+ */
+ public String getQuestFinishMessage() {
+ return this.questFinishMessage;
+ }
+
+ /**
+ * @param questFinishMessage The quest finish message
+ * @return The quest finish message set
+ */
+ public String setQuestFinishMessage(String questFinishMessage) {
+ return (this.questFinishMessage = questFinishMessage);
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerQuestEvent.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerQuestEvent.java
new file mode 100644
index 00000000..a6a0a26a
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerQuestEvent.java
@@ -0,0 +1,19 @@
+package com.leonardobishop.quests.bukkit.api.event;
+
+import com.leonardobishop.quests.common.player.QPlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.event.player.PlayerEvent;
+import org.jetbrains.annotations.NotNull;
+
+public abstract class PlayerQuestEvent extends PlayerEvent {
+ private final QPlayer questPlayer;
+
+ public PlayerQuestEvent(@NotNull Player who, @NotNull QPlayer questPlayer) {
+ super(who);
+ this.questPlayer = questPlayer;
+ }
+
+ public QPlayer getQuestPlayer() {
+ return this.questPlayer;
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerStartQuestEvent.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerStartQuestEvent.java
new file mode 100644
index 00000000..87f01ce1
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerStartQuestEvent.java
@@ -0,0 +1,52 @@
+package com.leonardobishop.quests.bukkit.api.event;
+
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import org.bukkit.entity.Player;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+public class PlayerStartQuestEvent extends PlayerQuestEvent {
+
+ private final static HandlerList handlers = new HandlerList();
+ private final QuestProgress questProgress;
+ private String questStartMessage;
+
+ public PlayerStartQuestEvent(@NotNull Player who, @NotNull QPlayer questPlayer, @NotNull QuestProgress questProgress, String questStartMessage) {
+ super(who, questPlayer);
+ this.questProgress = questProgress;
+ this.questStartMessage = questStartMessage;
+ }
+
+ /**
+ * @return The quest progress
+ */
+ public QuestProgress getQuestProgress() {
+ return this.questProgress;
+ }
+
+ /**
+ * @return The message sent to the player that start the quest
+ */
+ public String getQuestStartMessage() {
+ return this.questStartMessage;
+ }
+
+ /**
+ * @param questStartMessage The quest start message
+ * @return The quest start message set
+ */
+ public String setQuestStartMessage(String questStartMessage) {
+ return (this.questStartMessage = questStartMessage);
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerStartTrackQuestEvent.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerStartTrackQuestEvent.java
new file mode 100644
index 00000000..107924a6
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerStartTrackQuestEvent.java
@@ -0,0 +1,32 @@
+package com.leonardobishop.quests.bukkit.api.event;
+
+import com.leonardobishop.quests.common.player.QPlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+public class PlayerStartTrackQuestEvent extends PlayerQuestEvent {
+ private final static HandlerList handlers = new HandlerList();
+ private final QPlayer qPlayer;
+
+ public PlayerStartTrackQuestEvent(@NotNull Player who, QPlayer qPlayer) {
+ super(who, qPlayer);
+ this.qPlayer = qPlayer;
+ }
+
+ public QPlayer getQPlayer() {
+ return qPlayer;
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerStopTrackQuestEvent.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerStopTrackQuestEvent.java
new file mode 100644
index 00000000..288013b7
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerStopTrackQuestEvent.java
@@ -0,0 +1,27 @@
+package com.leonardobishop.quests.bukkit.api.event;
+
+import com.leonardobishop.quests.common.player.QPlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+public class PlayerStopTrackQuestEvent extends PlayerQuestEvent {
+
+ private final static HandlerList handlers = new HandlerList();
+ private final QPlayer qPlayer;
+
+ public PlayerStopTrackQuestEvent(@NotNull Player who, QPlayer qPlayer) {
+ super(who, qPlayer);
+ this.qPlayer = qPlayer;
+ }
+
+ public QPlayer getQPlayer() {
+ return qPlayer;
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PreStartQuestEvent.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PreStartQuestEvent.java
new file mode 100644
index 00000000..b2513415
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PreStartQuestEvent.java
@@ -0,0 +1,57 @@
+package com.leonardobishop.quests.bukkit.api.event;
+
+import com.leonardobishop.quests.common.enums.QuestStartResult;
+import com.leonardobishop.quests.common.player.QPlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+
+public class PreStartQuestEvent extends PlayerQuestEvent {
+
+ private final static HandlerList handlers = new HandlerList();
+ private QuestStartResult questStartResult;
+ private String questResultMessage;
+
+ public PreStartQuestEvent(@NotNull Player who, @NotNull QPlayer questPlayer, String questResultMessage, @NotNull QuestStartResult questStartResult) {
+ super(who, questPlayer);
+ this.questStartResult = questStartResult;
+ this.questResultMessage = questResultMessage;
+ }
+
+ public QuestStartResult getQuestStartResult() {
+ return this.questStartResult;
+ }
+
+ public QuestStartResult setQuestStartResult(QuestStartResult questStartResult) {
+ return (this.questStartResult = questStartResult);
+ }
+
+ /**
+ * @return The message sent to the player of the result of the quest
+ * <p>
+ * For {@link QuestStartResult#QUEST_SUCCESS} please use {@link PlayerStartQuestEvent}
+ */
+ public String getQuestResultMessage() {
+ return this.questResultMessage;
+ }
+
+ /**
+ * @param questResultMessage The quest result message
+ * @return The quest result message set
+ * <p>
+ * For {@link QuestStartResult#QUEST_SUCCESS} please use {@link PlayerStartQuestEvent}
+ */
+ public String setQuestResultMessage(String questResultMessage) {
+ return (this.questResultMessage = questResultMessage);
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/QuestsCommand.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/QuestsCommand.java
new file mode 100644
index 00000000..37a388fb
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/command/QuestsCommand.java
@@ -0,0 +1,644 @@
+package com.leonardobishop.quests.bukkit.command;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.util.Chat;
+import com.leonardobishop.quests.bukkit.util.Messages;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.enums.QuestStartResult;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgressFile;
+import com.leonardobishop.quests.common.quest.Category;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import com.leonardobishop.quests.common.tasktype.TaskType;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.command.Command;
+import org.bukkit.command.CommandSender;
+import org.bukkit.command.TabExecutor;
+import org.bukkit.entity.Player;
+import org.bukkit.util.StringUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ThreadLocalRandom;
+
+public class QuestsCommand implements TabExecutor {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public QuestsCommand(BukkitQuestsPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public boolean onCommand(CommandSender sender, Command cmd, String label, String[] args) {
+ if (plugin.getTaskTypeManager().areRegistrationsAccepted()) {
+ sender.sendMessage(ChatColor.RED + "Quests is not ready yet.");
+ return true;
+ }
+ if (!plugin.isValidConfiguration()
+ && !(args.length >= 2
+ && (args[0].equalsIgnoreCase("a") || args[0].equalsIgnoreCase("admin"))
+ && args[1].equalsIgnoreCase("reload"))) {
+ sender.sendMessage(ChatColor.RED + "Quests cannot be used right now. Please speak to an administrator.");
+ if (sender.hasPermission("quests.admin")) {
+ showProblems(sender);
+ sender.sendMessage(ChatColor.RED + "The main config (config.yml) must be in tact before quests can be used. " +
+ "Please use the above information to help rectify the problem.");
+ }
+ return true;
+ }
+
+ if (args.length >= 1 && args[0].equalsIgnoreCase("help")) {
+ showHelp(sender);
+ return true;
+ }
+
+ if (args.length == 0 && sender instanceof Player) {
+ Player player = (Player) sender;
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) {
+ player.sendMessage(Messages.COMMAND_DATA_NOT_LOADED.getMessage());
+ return true;
+ }
+ plugin.getMenuController().openMainMenu(qPlayer);
+ return true;
+ } else if (args.length >= 1) {
+ if ((args[0].equalsIgnoreCase("a") || args[0].equalsIgnoreCase("admin")) && sender.hasPermission("quests.admin")) {
+ if (args.length == 2) {
+ if (args[1].equalsIgnoreCase("opengui")) {
+ showAdminHelp(sender, "opengui");
+ return true;
+ } else if (args[1].equalsIgnoreCase("moddata")) {
+ showAdminHelp(sender, "moddata");
+ return true;
+ } else if (args[1].equalsIgnoreCase("reload")) {
+ sender.sendMessage(ChatColor.GRAY + "Please note that some options, such as storage, require a full restart for chances to take effect.");
+ plugin.reloadConfig();
+ plugin.reloadQuests();
+ if (!plugin.getConfigProblems().isEmpty()) showProblems(sender);
+ sender.sendMessage(ChatColor.GREEN + "Quests successfully reloaded.");
+ return true;
+ } else if (args[1].equalsIgnoreCase("config")) {
+ showProblems(sender);
+ return true;
+ //TODO
+// } else if (args[1].equalsIgnoreCase("itemstack")) {
+// if (!(sender instanceof Player)) {
+// sender.sendMessage("You must be a player to use this command.");
+// return true;
+// }
+// Player player = (Player) sender;
+// ItemStack is = player.getItemInHand();
+// if (is == null || is.getType() == Material.AIR) {
+// sender.sendMessage(ChatColor.GRAY + "There is no information about this ItemStack.");
+// return true;
+// }
+// sender.sendMessage(ToStringBuilder.reflectionToString(is));
+// sender.sendMessage(ToStringBuilder.reflectionToString(is.getItemMeta()));
+// return true;
+ } else if (args[1].equalsIgnoreCase("types")) {
+ sender.sendMessage(ChatColor.GRAY + "Registered task types:");
+ for (TaskType taskType : plugin.getTaskTypeManager().getTaskTypes()) {
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + taskType.getType());
+ }
+ sender.sendMessage(ChatColor.DARK_GRAY + "View info using /q a types [type].");
+ return true;
+ } else if (args[1].equalsIgnoreCase("info")) {
+ sender.sendMessage(ChatColor.RED + "Quest controller: " + plugin.getQuestController().getName());
+ sender.sendMessage(ChatColor.GRAY + "Loaded quests:");
+ for (Quest quest : plugin.getQuestManager().getQuests().values()) {
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + quest.getId() + ChatColor.GRAY + " [" + quest.getTasks().size() + " tasks]");
+ }
+ sender.sendMessage(ChatColor.DARK_GRAY + "View info using /q a info [quest].");
+ return true;
+ } else if (args[1].equalsIgnoreCase("update")) {
+ sender.sendMessage(ChatColor.GRAY + "Checking for updates...");
+ Bukkit.getScheduler().runTaskAsynchronously(this.plugin, () -> {
+ plugin.getUpdater().check();
+ if (plugin.getUpdater().isUpdateReady()) {
+ String updateMessage = Messages.QUEST_UPDATER.getMessage()
+ .replace("{newver}", plugin.getUpdater().getReturnedVersion())
+ .replace("{oldver}", plugin.getUpdater().getInstalledVersion())
+ .replace("{link}", plugin.getUpdater().getUpdateLink());
+ sender.sendMessage(updateMessage);
+ } else {
+ sender.sendMessage(ChatColor.GRAY + "No updates were found.");
+ }
+ });
+ return true;
+ } else if (args[1].equalsIgnoreCase("wiki")) {
+ sender.sendMessage(ChatColor.RED + "Link to Quests wiki: " + ChatColor.GRAY + "https://github.com/LMBishop/Quests/wiki");
+ return true;
+ } else if (args[1].equalsIgnoreCase("about")) {
+ sender.sendMessage(ChatColor.RED + "Quests " + ChatColor.BOLD + "v" + plugin.getDescription().getVersion());
+ sender.sendMessage(ChatColor.DARK_GRAY + " - " + ChatColor.RED + "Source code: " + ChatColor.GRAY + "https://github.com/LMBishop/Quests/");
+ sender.sendMessage(ChatColor.DARK_GRAY + " - " + ChatColor.RED + "Report an issue: " + ChatColor.GRAY + "https://github.com/LMBishop/Quests/issues");
+ sender.sendMessage(ChatColor.DARK_GRAY + " - " + ChatColor.RED + "Wiki: " + ChatColor.GRAY + "https://github.com/LMBishop/Quests/wiki");
+ sender.sendMessage(ChatColor.DARK_GRAY + " - " + ChatColor.RED + "Licensed under the GPLv3");
+ sender.sendMessage(ChatColor.GRAY + "Many contributors have written source code and task types for Quests," +
+ " please see the GitHub link for an up-to-date list of contributors.");
+ return true;
+ }
+ } else if (args.length == 3) {
+ if (args[1].equalsIgnoreCase("opengui")) {
+ showAdminHelp(sender, "opengui");
+ return true;
+ } else if (args[1].equalsIgnoreCase("moddata")) {
+ showAdminHelp(sender, "moddata");
+ return true;
+ } else if (args[1].equalsIgnoreCase("types")) {
+ TaskType taskType = null;
+ for (TaskType task : plugin.getTaskTypeManager().getTaskTypes()) {
+ if (task.getType().equals(args[2])) {
+ taskType = task;
+ }
+ }
+ if (taskType == null) {
+ sender.sendMessage(Messages.COMMAND_TASKVIEW_ADMIN_FAIL.getMessage().replace("{task}", args[2]));
+ } else {
+ sender.sendMessage(ChatColor.RED + "Task type: " + ChatColor.GRAY + taskType.getType());
+ sender.sendMessage(ChatColor.RED + "Author: " + ChatColor.GRAY + taskType.getAuthor());
+ sender.sendMessage(ChatColor.RED + "Description: " + ChatColor.GRAY + taskType.getDescription());
+ }
+ return true;
+ } else if (args[1].equalsIgnoreCase("info")) {
+ Quest quest = plugin.getQuestManager().getQuestById(args[2]);
+ if (quest == null) {
+ sender.sendMessage(Messages.COMMAND_QUEST_GENERAL_DOESNTEXIST.getMessage().replace("{quest}", args[2]));
+ } else {
+ sender.sendMessage(ChatColor.RED.toString() + ChatColor.BOLD + "Information for quest '" + quest.getId() + "'");
+ sender.sendMessage(ChatColor.RED.toString() + ChatColor.UNDERLINE + "Task configurations (" + quest.getTasks().size() + ")");
+ for (Task task : quest.getTasks()) {
+ sender.sendMessage(ChatColor.RED + "Task '" + task.getId() + "':");
+ for (Map.Entry<String, Object> config : task.getConfigValues().entrySet()) {
+ sender.sendMessage(ChatColor.DARK_GRAY + " | " + ChatColor.GRAY + config.getKey() + ": " + ChatColor.GRAY + ChatColor.ITALIC + config.getValue());
+ }
+ }
+ sender.sendMessage(ChatColor.RED.toString() + ChatColor.UNDERLINE + "Start string");
+ for (String s : quest.getStartString()) {
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.GRAY + s);
+ }
+ sender.sendMessage(ChatColor.RED.toString() + ChatColor.UNDERLINE + "Reward string");
+ for (String s : quest.getRewardString()) {
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.GRAY + s);
+ }
+ sender.sendMessage(ChatColor.RED.toString() + ChatColor.UNDERLINE + "Rewards");
+ for (String s : quest.getRewards()) {
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.GRAY + s);
+ }
+ sender.sendMessage(ChatColor.RED.toString() + ChatColor.UNDERLINE + "Quest options");
+ sender.sendMessage(ChatColor.RED + "Category: " + ChatColor.GRAY + quest.getCategoryId());
+ sender.sendMessage(ChatColor.RED + "Repeatable: " + ChatColor.GRAY + quest.isRepeatable());
+ sender.sendMessage(ChatColor.RED + "Requirements: " + ChatColor.GRAY + String.join(", ", quest.getRequirements()));
+ sender.sendMessage(ChatColor.RED + "Cooldown enabled: " + ChatColor.GRAY + quest.isCooldownEnabled());
+ sender.sendMessage(ChatColor.RED + "Cooldown time: " + ChatColor.GRAY + quest.getCooldown());
+ }
+ return true;
+ }
+ } else if (args.length == 4) {
+ if (args[1].equalsIgnoreCase("opengui")) {
+ if (args[2].equalsIgnoreCase("q") || args[2].equalsIgnoreCase("resources/bukkit/quests")) {
+ Player player = Bukkit.getPlayer(args[3]);
+ if (player != null) {
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer != null) {
+ plugin.getMenuController().openMainMenu(qPlayer);
+ sender.sendMessage(Messages.COMMAND_QUEST_OPENQUESTS_ADMIN_SUCCESS.getMessage().replace("{player}", player.getName()));
+ return true;
+ }
+ }
+ sender.sendMessage(Messages.COMMAND_QUEST_ADMIN_PLAYERNOTFOUND.getMessage().replace("{player}", args[3]));
+ return true;
+ }
+ showAdminHelp(sender, "opengui");
+ return true;
+ } else if (args[1].equalsIgnoreCase("moddata")) {
+ QPlayer qPlayer = getOtherPlayer(sender, args[3]);
+ if (qPlayer == null) return true;
+ if (args[2].equalsIgnoreCase("fullreset")) {
+ QuestProgressFile questProgressFile = qPlayer.getQuestProgressFile();
+ questProgressFile.clear();
+ plugin.getPlayerManager().savePlayerSync(qPlayer.getPlayerUUID(), questProgressFile);
+ if (Bukkit.getPlayer(qPlayer.getPlayerUUID()) == null) {
+ plugin.getPlayerManager().dropPlayer(qPlayer.getPlayerUUID());
+ }
+ sender.sendMessage(Messages.COMMAND_QUEST_ADMIN_FULLRESET.getMessage().replace("{player}", args[3]));
+ return true;
+ }
+ showAdminHelp(sender, "moddata");
+ return true;
+ }
+ } else if (args.length == 5) {
+ if (args[1].equalsIgnoreCase("opengui")) {
+ if (args[2].equalsIgnoreCase("c") || args[2].equalsIgnoreCase("category")) {
+ if (!plugin.getQuestsConfig().getBoolean("options.categories-enabled")) {
+ sender.sendMessage(Messages.COMMAND_CATEGORY_OPEN_DISABLED.getMessage());
+ return true;
+ }
+ Category category = plugin.getQuestManager().getCategoryById(args[4]);
+ if (category == null) {
+ sender.sendMessage(Messages.COMMAND_CATEGORY_OPEN_DOESNTEXIST.getMessage().replace("{category}", args[4]));
+ return true;
+ }
+ Player player = Bukkit.getPlayer(args[3]);
+ if (player != null) {
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer != null) {
+ if (plugin.getMenuController().openQuestCategory(qPlayer, category, null, false) == 0) {
+ sender.sendMessage(Messages.COMMAND_QUEST_OPENCATEGORY_ADMIN_SUCCESS.getMessage().replace("{player}", player.getName())
+ .replace("{category}", category.getId()));
+ } else {
+ sender.sendMessage(Messages.COMMAND_QUEST_ADMIN_CATEGORY_PERMISSION.getMessage().replace("{player}", player.getName())
+ .replace("{category}", category.getId()));
+ }
+ return true;
+ }
+ }
+ sender.sendMessage(Messages.COMMAND_QUEST_ADMIN_PLAYERNOTFOUND.getMessage().replace("{player}", args[3]));
+ return true;
+ }
+ } else if (args[1].equalsIgnoreCase("moddata")) {
+ boolean success = false;
+ QPlayer qPlayer = getOtherPlayer(sender, args[3]);
+ if (qPlayer == null) return true;
+ QuestProgressFile questProgressFile = qPlayer.getQuestProgressFile();
+ Quest quest = plugin.getQuestManager().getQuestById(args[4]);
+ if (quest == null) {
+ sender.sendMessage(Messages.COMMAND_QUEST_START_DOESNTEXIST.getMessage().replace("{quest}", args[4]));
+ //success = true;
+ return true;
+ }
+ if (args[2].equalsIgnoreCase("reset")) {
+ questProgressFile.generateBlankQuestProgress(quest);
+ plugin.getPlayerManager().savePlayerSync(qPlayer.getPlayerUUID(), questProgressFile);
+ sender.sendMessage(Messages.COMMAND_QUEST_ADMIN_RESET_SUCCESS.getMessage().replace("{player}", args[3]).replace("{quest}", quest.getId()));
+ success = true;
+ } else if (args[2].equalsIgnoreCase("start")) {
+ QuestStartResult response = qPlayer.startQuest(quest);
+ if (response == QuestStartResult.QUEST_LIMIT_REACHED) {
+ sender.sendMessage(Messages.COMMAND_QUEST_ADMIN_START_FAILLIMIT.getMessage().replace("{player}", args[3]).replace("{quest}", quest.getId()));
+ return true;
+ } else if (response == QuestStartResult.QUEST_ALREADY_COMPLETED) {
+ sender.sendMessage(Messages.COMMAND_QUEST_ADMIN_START_FAILCOMPLETE.getMessage().replace("{player}", args[3]).replace("{quest}", quest.getId()));
+ return true;
+ } else if (response == QuestStartResult.QUEST_COOLDOWN) {
+ sender.sendMessage(Messages.COMMAND_QUEST_ADMIN_START_FAILCOOLDOWN.getMessage().replace("{player}", args[3]).replace("{quest}", quest.getId()));
+ return true;
+ } else if (response == QuestStartResult.QUEST_LOCKED) {
+ sender.sendMessage(Messages.COMMAND_QUEST_ADMIN_START_FAILLOCKED.getMessage().replace("{player}", args[3]).replace("{quest}", quest.getId()));
+ return true;
+ } else if (response == QuestStartResult.QUEST_ALREADY_STARTED) {
+ sender.sendMessage(Messages.COMMAND_QUEST_ADMIN_START_FAILSTARTED.getMessage().replace("{player}", args[3]).replace("{quest}", quest.getId()));
+ return true;
+ } else if (response == QuestStartResult.QUEST_NO_PERMISSION) {
+ sender.sendMessage(Messages.COMMAND_QUEST_ADMIN_START_FAILPERMISSION.getMessage().replace("{player}", args[3]).replace("{quest}", quest.getId()));
+ return true;
+ } else if (response == QuestStartResult.NO_PERMISSION_FOR_CATEGORY) {
+ sender.sendMessage(Messages.COMMAND_QUEST_ADMIN_START_FAILCATEGORYPERMISSION.getMessage().replace("{player}", args[3]).replace("{quest}", quest.getId()));
+ return true;
+ }
+ plugin.getPlayerManager().savePlayerSync(qPlayer.getPlayerUUID(), questProgressFile);
+ sender.sendMessage(Messages.COMMAND_QUEST_ADMIN_START_SUCCESS.getMessage().replace("{player}", args[3]).replace("{quest}", quest.getId()));
+ success = true;
+ } else if (args[2].equalsIgnoreCase("complete")) {
+ qPlayer.completeQuest(quest);
+ plugin.getPlayerManager().savePlayerSync(qPlayer.getPlayerUUID(), questProgressFile);
+ sender.sendMessage(Messages.COMMAND_QUEST_ADMIN_COMPLETE_SUCCESS.getMessage().replace("{player}", args[3]).replace("{quest}", quest.getId()));
+ success = true;
+ }
+ if (!success) {
+ showAdminHelp(sender, "moddata");
+ }
+ if (Bukkit.getPlayer(qPlayer.getPlayerUUID()) == null) {
+ plugin.getPlayerManager().dropPlayer(qPlayer.getPlayerUUID());
+ }
+ return true;
+ }
+ }
+ showAdminHelp(sender, null);
+ return true;
+ }
+ if (sender instanceof Player && (args[0].equalsIgnoreCase("q") || args[0].equalsIgnoreCase("resources/bukkit/quests") || args[0].equalsIgnoreCase("quest"))) {
+ Player player = (Player) sender;
+ if (args.length >= 3) {
+ Quest quest = plugin.getQuestManager().getQuestById(args[1]);
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) {
+ player.sendMessage(Messages.COMMAND_DATA_NOT_LOADED.getMessage());
+ return true;
+ }
+ if (quest == null) {
+ sender.sendMessage(Messages.COMMAND_QUEST_GENERAL_DOESNTEXIST.getMessage().replace("{quest}", args[1]));
+ }
+ if (args[2].equalsIgnoreCase("s") || args[2].equalsIgnoreCase("start")) {
+ qPlayer.startQuest(quest);
+ } else if (args[2].equalsIgnoreCase("c") || args[2].equalsIgnoreCase("cancel")) {
+ qPlayer.cancelQuest(quest);
+ } else if (args[2].equalsIgnoreCase("t") || args[2].equalsIgnoreCase("track")) {
+ qPlayer.trackQuest(quest);
+ } else {
+ sender.sendMessage(Messages.COMMAND_SUB_DOESNTEXIST.getMessage().replace("{sub}", args[2]));
+ }
+ return true;
+ }
+ } else if (sender instanceof Player && (args[0].equalsIgnoreCase("c") || args[0].equalsIgnoreCase("category"))) {
+ if (!plugin.getQuestsConfig().getBoolean("options.categories-enabled")) {
+ sender.sendMessage(Messages.COMMAND_CATEGORY_OPEN_DISABLED.getMessage());
+ return true;
+ }
+ Player player = (Player) sender;
+ if (args.length >= 2) {
+ Category category = plugin.getQuestManager().getCategoryById(args[1]);
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) {
+ player.sendMessage(Messages.COMMAND_DATA_NOT_LOADED.getMessage());
+ return true;
+ }
+ if (category == null) {
+ sender.sendMessage(Messages.COMMAND_CATEGORY_OPEN_DOESNTEXIST.getMessage().replace("{category}", args[1]));
+ } else {
+ plugin.getMenuController().openQuestCategory(qPlayer, category, null, false);
+ return true;
+ }
+ return true;
+ }
+ } else if (sender instanceof Player && (args[0].equalsIgnoreCase("random")) && sender.hasPermission("quests.command.random")) {
+ Player player = (Player) sender;
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) {
+ player.sendMessage(Messages.COMMAND_DATA_NOT_LOADED.getMessage());
+ return true;
+ }
+ List<Quest> validQuests = new ArrayList<>();
+ for (Quest quest : plugin.getQuestManager().getQuests().values()) {
+ if (qPlayer.canStartQuest(quest) == QuestStartResult.QUEST_SUCCESS) {
+ validQuests.add(quest);
+ }
+ }
+
+ if (validQuests.isEmpty()) {
+ player.sendMessage(Messages.QUEST_RANDOM_NONE.getMessage());
+ return true;
+ }
+ int random = ThreadLocalRandom.current().nextInt(0, validQuests.size());
+ qPlayer.startQuest(validQuests.get(random));
+ return true;
+ } else if (sender instanceof Player && (args[0].equalsIgnoreCase("started"))) {
+ Player player = (Player) sender;
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) {
+ player.sendMessage(Messages.COMMAND_DATA_NOT_LOADED.getMessage());
+ return true;
+ }
+ plugin.getMenuController().openStartedQuests(qPlayer);
+ return true;
+ }
+ showHelp(sender);
+ } else {
+ sender.sendMessage(ChatColor.RED + "Only admin commands are available to non-player senders.");
+ }
+ return true;
+ }
+
+ private QPlayer getOtherPlayer(CommandSender sender, String name) {
+ OfflinePlayer ofp = Bukkit.getOfflinePlayer(name);
+ UUID uuid;
+ String username;
+ if (ofp.hasPlayedBefore()) {
+ uuid = ofp.getUniqueId();
+ username = ofp.getName();
+ } else {
+ sender.sendMessage(Messages.COMMAND_QUEST_ADMIN_PLAYERNOTFOUND.getMessage().replace("{player}", name));
+ return null;
+ }
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(uuid);
+ if (qPlayer == null) {
+ sender.sendMessage(Messages.COMMAND_QUEST_ADMIN_LOADDATA.getMessage().replace("{player}", username));
+ plugin.getPlayerManager().loadPlayer(uuid);
+ qPlayer = plugin.getPlayerManager().getPlayer(uuid);
+ }
+ if (qPlayer == null) {
+ sender.sendMessage(Messages.COMMAND_QUEST_ADMIN_NODATA.getMessage().replace("{player}", username));
+ return null;
+ }
+ return qPlayer;
+ }
+
+ private void showProblems(CommandSender sender) {
+ if (!plugin.getConfigProblems().isEmpty()) {
+// sender.sendMessage(ChatColor.DARK_GRAY.toString() + "----");
+ sender.sendMessage(ChatColor.GRAY + "Detected problems and potential issues:");
+ Set<ConfigProblem.ConfigProblemType> problemTypes = new HashSet<>();
+ int count = 0;
+ for (Map.Entry<String, List<ConfigProblem>> entry : plugin.getConfigProblems().entrySet()) {
+ HashMap<ConfigProblem.ConfigProblemType, List<ConfigProblem>> sortedProblems = new HashMap<>();
+ for (ConfigProblem problem : entry.getValue()) {
+ if (sortedProblems.containsKey(problem.getType())) {
+ sortedProblems.get(problem.getType()).add(problem);
+ } else {
+ List<ConfigProblem> specificProblems = new ArrayList<>();
+ specificProblems.add(problem);
+ sortedProblems.put(problem.getType(), specificProblems);
+ }
+ problemTypes.add(problem.getType());
+ }
+ ConfigProblem.ConfigProblemType highest = null;
+ for (ConfigProblem.ConfigProblemType type : ConfigProblem.ConfigProblemType.values()) {
+ if (sortedProblems.containsKey(type)) {
+ highest = type;
+ break;
+ }
+ }
+ ChatColor highestColor = ChatColor.WHITE;
+ if (highest != null) {
+ highestColor = Chat.matchConfigProblemToColor(highest);
+ }
+ sender.sendMessage(highestColor + entry.getKey() + ChatColor.DARK_GRAY + " ----");
+ for (ConfigProblem.ConfigProblemType type : ConfigProblem.ConfigProblemType.values()) {
+ if (sortedProblems.containsKey(type)) {
+ for (ConfigProblem problem : sortedProblems.get(type)) {
+ sender.sendMessage(ChatColor.DARK_GRAY + " | - " + Chat.matchConfigProblemToColor(problem.getType())
+ + problem.getType().getShortened() + ChatColor.DARK_GRAY + ": "
+ + ChatColor.GRAY + problem.getDescription() + ChatColor.DARK_GRAY + " :" + problem.getLocation());
+ count++;
+ }
+ }
+ }
+ }
+// sender.sendMessage(ChatColor.DARK_GRAY.toString() + "----");
+ List<String> legend = new ArrayList<>();
+ for (ConfigProblem.ConfigProblemType type : ConfigProblem.ConfigProblemType.values()) {
+ if (problemTypes.contains(type)) {
+ legend.add(Chat.matchConfigProblemToColor(type) + type.getShortened() + ChatColor.DARK_GRAY + " = " + Chat.matchConfigProblemToColor(type) + type.getTitle());
+ }
+ }
+ sender.sendMessage(ChatColor.DARK_GRAY.toString() + "----");
+
+ sender.sendMessage(ChatColor.GRAY.toString() + count + " problem(s) | " + String.join(ChatColor.DARK_GRAY + ", ", legend));
+ } else {
+ sender.sendMessage(ChatColor.GRAY + "Quests did not detect any problems with your configuration.");
+ }
+ }
+
+ private void showHelp(CommandSender sender) {
+ sender.sendMessage(ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH + "------------=[" + ChatColor.RED + " Quests v" + plugin
+ .getDescription().getVersion() + " " + ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH + "]=------------");
+ sender.sendMessage(ChatColor.GRAY + "The following commands are available: ");
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/resources/bukkit/quests " + ChatColor.DARK_GRAY + ": show quests");
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests c/category <categoryid> " + ChatColor.DARK_GRAY + ": open category by ID");
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests q/quest <questid> <start|cancel|track>" + ChatColor.DARK_GRAY + ": start, cancel or track quest by ID");
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a/admin " + ChatColor.DARK_GRAY + ": view help for admins");
+ sender.sendMessage(ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH + "--------=[" + ChatColor.RED + " made with <3 by LMBishop " + ChatColor
+ .GRAY.toString() + ChatColor.STRIKETHROUGH + "]=--------");
+ }
+
+ private void showAdminHelp(CommandSender sender, String command) {
+ if (command != null && command.equalsIgnoreCase("opengui")) {
+ sender.sendMessage(ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH + "------------=[" + ChatColor.RED + " Quests Admin: opengui " + ChatColor
+ .GRAY.toString() + ChatColor.STRIKETHROUGH + "]=------------");
+ sender.sendMessage(ChatColor.GRAY + "The following commands are available: ");
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a opengui q/quests <player> " + ChatColor.DARK_GRAY + ": forcefully show" +
+ " quests for player");
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a opengui c/category <player> <category> " + ChatColor.DARK_GRAY + ": " +
+ "forcefully " +
+ "open category by ID for player");
+ sender.sendMessage(ChatColor.GRAY + "These commands are useful for command NPCs. These will bypass the usual quests.command permission.");
+ } else if (command != null && command.equalsIgnoreCase("moddata")) {
+ sender.sendMessage(ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH + "------------=[" + ChatColor.RED + " Quests Admin: moddata " + ChatColor
+ .GRAY.toString() + ChatColor.STRIKETHROUGH + "]=------------");
+ sender.sendMessage(ChatColor.GRAY + "The following commands are available: ");
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a moddata fullreset <player> " + ChatColor.DARK_GRAY + ": clear a " +
+ "players quest data file");
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a moddata reset <player> <questid> " + ChatColor.DARK_GRAY + ": clear a " +
+ "players data for specifc quest");
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a moddata start <player> <questid> " + ChatColor.DARK_GRAY + ": start a " +
+ "quest for a player");
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a moddata complete <player> <questid> " + ChatColor.DARK_GRAY + ": " +
+ "complete a quest for a player");
+ sender.sendMessage(ChatColor.GRAY + "These commands modify quest progress for players. Use them cautiously. Changes are irreversible.");
+ } else {
+ sender.sendMessage(ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH + "------------=[" + ChatColor.RED + " Quests Admin " + ChatColor.GRAY
+ .toString() + ChatColor.STRIKETHROUGH + "]=------------");
+ sender.sendMessage(ChatColor.GRAY + "The following commands are available: ");
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a opengui " + ChatColor.DARK_GRAY + ": view help for opengui");
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a moddata " + ChatColor.DARK_GRAY + ": view help for quest progression");
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a types [type]" + ChatColor.DARK_GRAY + ": view registered task types");
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a info [quest]" + ChatColor.DARK_GRAY + ": see information about loaded quests");
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a reload " + ChatColor.DARK_GRAY + ": reload Quests configuration");
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a config " + ChatColor.DARK_GRAY + ": see detected problems in config");
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a itemstack " + ChatColor.DARK_GRAY + ": print information about the current held ItemStack");
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a update " + ChatColor.DARK_GRAY + ": check for updates");
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a wiki " + ChatColor.DARK_GRAY + ": get a link to the Quests wiki");
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a about " + ChatColor.DARK_GRAY + ": get information about Quests");
+ }
+ sender.sendMessage(ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH + "-----=[" + ChatColor.RED + " requires permission: quests.admin " +
+ ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH + "]=-----");
+ }
+
+ private List<String> matchTabComplete(String arg, List<String> options) {
+ List<String> completions = new ArrayList<>();
+ StringUtil.copyPartialMatches(arg, options, completions);
+ Collections.sort(completions);
+ return completions;
+ }
+
+ private List<String> tabCompleteCategory(String arg) {
+ List<String> options = new ArrayList<>();
+ for (Category c : plugin.getQuestManager().getCategories()) {
+ options.add(c.getId());
+ }
+ return matchTabComplete(arg, options);
+ }
+
+ private List<String> tabCompleteQuests(String arg) {
+ List<String> options = new ArrayList<>(plugin.getQuestManager().getQuests().keySet());
+ return matchTabComplete(arg, options);
+ }
+
+ @Nullable
+ @Override
+ public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String alias, @NotNull String[] args) {
+ if (!plugin.getQuestsConfig().getBoolean("options.tab-completion.enabled")) {
+ return null;
+ }
+ if (sender instanceof Player) {
+ if (args.length == 1) {
+ List<String> options = new ArrayList<>(Arrays.asList("quest", "category", "started"));
+ if (sender.hasPermission("quests.admin")) {
+ options.add("admin");
+ }
+ if (sender.hasPermission("quests.command.random")) {
+ options.add("random");
+ }
+ return matchTabComplete(args[0], options);
+ } else if (args.length == 2) {
+ if (args[0].equalsIgnoreCase("c") || args[0].equalsIgnoreCase("category")) {
+ return tabCompleteCategory(args[1]);
+ } else if (args[0].equalsIgnoreCase("q") || args[0].equalsIgnoreCase("quest")) {
+ return tabCompleteQuests(args[1]);
+ } else if (args[0].equalsIgnoreCase("a") || args[0].equalsIgnoreCase("admin")
+ && sender.hasPermission("quests.admin")) {
+ List<String> options = Arrays.asList("opengui", "moddata", "types", "reload", "update", "config", "info", "wiki", "about");
+ return matchTabComplete(args[1], options);
+ }
+ } else if (args.length == 3) {
+ if (args[0].equalsIgnoreCase("q") || args[0].equalsIgnoreCase("quest")
+ && sender.hasPermission("quests.admin")) {
+ Quest q = plugin.getQuestManager().getQuestById(args[1]);
+ if (q != null) {
+ List<String> options = Arrays.asList("start", "cancel", "track");
+ return matchTabComplete(args[2], options);
+ }
+ } else if (args[0].equalsIgnoreCase("a") || args[0].equalsIgnoreCase("admin")
+ && sender.hasPermission("quests.admin")) {
+ if (args[1].equalsIgnoreCase("types")) {
+ List<String> options = new ArrayList<>();
+ for (TaskType taskType : plugin.getTaskTypeManager().getTaskTypes()) {
+ options.add(taskType.getType());
+ }
+ return matchTabComplete(args[2], options);
+ } else if (args[1].equalsIgnoreCase("opengui")) {
+ List<String> options = Arrays.asList("resources/bukkit/quests", "category");
+ return matchTabComplete(args[2], options);
+ } else if (args[1].equalsIgnoreCase("moddata")) {
+ List<String> options = Arrays.asList("fullreset", "reset", "start", "complete");
+ return matchTabComplete(args[2], options);
+ } else if (args[1].equalsIgnoreCase("info")) {
+ return tabCompleteQuests(args[2]);
+ }
+ }
+ } else if (args.length == 4) {
+ if (sender.hasPermission("quests.admin")) return null;
+ } else if (args.length == 5) {
+ if (args[0].equalsIgnoreCase("a") || args[0].equalsIgnoreCase("admin")
+ && sender.hasPermission("quests.admin")) {
+ if (args[1].equalsIgnoreCase("opengui")) {
+ if (args[2].equalsIgnoreCase("c") || args[2].equalsIgnoreCase("category")) {
+ return tabCompleteCategory(args[4]);
+ }
+ } else if (args[1].equalsIgnoreCase("moddata")) {
+ if (args[2].equalsIgnoreCase("start")
+ || args[2].equalsIgnoreCase("complete")
+ || args[2].equalsIgnoreCase("reset")) {
+ return tabCompleteQuests(args[4]);
+ }
+ }
+ }
+ }
+ }
+ return Collections.emptyList();
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/config/BukkitQuestsConfig.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/config/BukkitQuestsConfig.java
new file mode 100644
index 00000000..add7c33e
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/config/BukkitQuestsConfig.java
@@ -0,0 +1,91 @@
+package com.leonardobishop.quests.bukkit.config;
+
+import com.leonardobishop.quests.bukkit.hook.itemgetter.ItemGetter;
+import com.leonardobishop.quests.common.config.QuestsConfig;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.inventory.ItemStack;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class BukkitQuestsConfig implements QuestsConfig {
+
+ private final Map<String, ItemStack> cachedItemStacks = new HashMap<>();
+ // this is faster than just relying on the YamlConfiguration to cache it for some reason
+ private final Map<String, Boolean> cachedBooleans = new HashMap<>();
+ private YamlConfiguration config;
+ private File file;
+ private ItemGetter itemGetter;
+
+ public BukkitQuestsConfig(File file) {
+ this.file = file;
+ }
+
+ public void setItemGetter(ItemGetter itemGetter) {
+ this.itemGetter = itemGetter;
+ }
+
+ @Override
+ public boolean loadConfig() {
+ this.cachedBooleans.clear();
+ this.cachedItemStacks.clear();
+ try {
+ config = YamlConfiguration.loadConfiguration(file);
+ return true;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ public YamlConfiguration getConfig() {
+ return config;
+ }
+
+ @Override
+ public String getString(String path) {
+ return config.getString(path);
+ }
+
+ @Override
+ public String getString(String path, String def) {
+ return config.getString(path, def);
+ }
+
+ @Override
+ public boolean getBoolean(String path) {
+ return config.getBoolean(path);
+ }
+
+ @Override
+ public boolean getBoolean(String path, boolean def) {
+ return config.getBoolean(path, def);
+ }
+
+ @Override
+ public int getInt(String path) {
+ return config.getInt(path);
+ }
+
+ @Override
+ public int getInt(String path, int def) {
+ return config.getInt(path, def);
+ }
+
+ @Override
+ public List<String> getStringList(String path) {
+ return config.getStringList(path);
+ }
+
+ @Override
+ public List<String> getStringList(String path, List<String> def) {
+ List<String> list = config.getStringList(path);
+ return list.isEmpty() ? def : list;
+ }
+
+ public ItemStack getItem(String path) {
+ return new ItemStack(cachedItemStacks.computeIfAbsent(path, s -> itemGetter.getItem(path, config)));
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/config/BukkitQuestsLoader.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/config/BukkitQuestsLoader.java
new file mode 100644
index 00000000..f80ec3f0
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/config/BukkitQuestsLoader.java
@@ -0,0 +1,331 @@
+package com.leonardobishop.quests.bukkit.config;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.hook.itemgetter.ItemGetter;
+import com.leonardobishop.quests.bukkit.menu.itemstack.QItemStack;
+import com.leonardobishop.quests.bukkit.menu.itemstack.QItemStackRegistry;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.config.ConfigProblemDescriptions;
+import com.leonardobishop.quests.common.config.QuestsLoader;
+import com.leonardobishop.quests.common.logger.QuestsLogger;
+import com.leonardobishop.quests.common.quest.Category;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.QuestManager;
+import com.leonardobishop.quests.common.quest.Task;
+import com.leonardobishop.quests.common.questcontroller.QuestController;
+import com.leonardobishop.quests.common.tasktype.TaskType;
+import com.leonardobishop.quests.common.tasktype.TaskTypeManager;
+import org.apache.commons.lang.StringUtils;
+import org.bukkit.ChatColor;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.inventory.ItemStack;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.nio.file.FileVisitResult;
+import java.nio.file.FileVisitor;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class BukkitQuestsLoader implements QuestsLoader {
+
+ private final BukkitQuestsPlugin plugin;
+ private final BukkitQuestsConfig questsConfig;
+ private final QuestManager questManager;
+ private final TaskTypeManager taskTypeManager;
+ private final QuestController questController;
+ private final QuestsLogger questsLogger;
+ private final QItemStackRegistry qItemStackRegistry;
+
+ public BukkitQuestsLoader(BukkitQuestsPlugin plugin) {
+ this.plugin = plugin;
+ this.questsConfig = (BukkitQuestsConfig) plugin.getQuestsConfig();
+ this.questManager = plugin.getQuestManager();
+ this.taskTypeManager = plugin.getTaskTypeManager();
+ this.questController = plugin.getQuestController();
+ this.questsLogger = plugin.getQuestsLogger();
+ this.qItemStackRegistry = plugin.getQItemStackRegistry();
+ }
+
+ @Override
+ public Map<String, List<ConfigProblem>> loadQuests(File root) {
+ qItemStackRegistry.clearRegistry();
+ questManager.getQuests().clear();
+ questManager.getCategories().clear();
+ taskTypeManager.resetTaskTypes();
+
+ Map<String, List<ConfigProblem>> configProblems = new HashMap<>();
+ HashMap<String, Quest> pathToQuest = new HashMap<>();
+ HashMap<String, Map<String, Object>> globalTaskConfig = new HashMap<>();
+
+ if (questsConfig.getConfig().isConfigurationSection("global-task-configuration.types")) {
+ for (String type : questsConfig.getConfig().getConfigurationSection("global-task-configuration.types").getKeys(false)) {
+ HashMap<String, Object> configValues = new HashMap<>();
+ for (String key : questsConfig.getConfig().getConfigurationSection("global-task-configuration.types." + type).getKeys(false)) {
+ configValues.put(key, questsConfig.getConfig().get("global-task-configuration.types." + type + "." + key));
+ }
+ globalTaskConfig.putIfAbsent(type, configValues);
+ }
+ }
+
+ for (String id : plugin.getConfig().getConfigurationSection("categories").getKeys(false)) {
+ ItemStack displayItem = plugin.getItemStack("categories." + id + ".display", plugin.getConfig());
+ boolean permissionRequired = plugin.getConfig().getBoolean("categories." + id + ".permission-required", false);
+
+ Category category = new Category(id, permissionRequired);
+ questManager.registerCategory(category);
+ qItemStackRegistry.register(category, displayItem);
+ }
+
+ FileVisitor<Path> fileVisitor = new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(Path path, BasicFileAttributes attributes) {
+ try {
+ File questFile = new File(path.toUri());
+ URI relativeLocation = root.toURI().relativize(path.toUri());
+
+ if (!questFile.getName().toLowerCase().endsWith(".yml")) return FileVisitResult.CONTINUE;
+
+ YamlConfiguration config = new YamlConfiguration();
+ // test QUEST file integrity
+ try {
+ config.load(questFile);
+ } catch (Exception ex) {
+ configProblems.put(relativeLocation.getPath(), Collections.singletonList(new ConfigProblem(ConfigProblem.ConfigProblemType.ERROR, ConfigProblemDescriptions.MALFORMED_YAML.getDescription())));
+ return FileVisitResult.CONTINUE;
+ }
+
+ String id = questFile.getName().replace(".yml", "");
+
+ List<ConfigProblem> problems = new ArrayList<>();
+
+ if (!StringUtils.isAlphanumeric(id)) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.ERROR, ConfigProblemDescriptions.INVALID_QUEST_ID.getDescription(id)));
+ }
+
+ // CHECK EVERYTHING WRONG WITH THE QUEST FILE BEFORE ACTUALLY LOADING THE QUEST
+
+ if (!config.isConfigurationSection("tasks")) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.ERROR, ConfigProblemDescriptions.NO_TASKS.getDescription(), "tasks"));
+ } else { //continue
+ int validTasks = 0;
+ for (String taskId : config.getConfigurationSection("tasks").getKeys(false)) {
+ boolean isValid = true;
+ String taskRoot = "tasks." + taskId;
+ String taskType = config.getString(taskRoot + ".type");
+
+ if (!config.isConfigurationSection(taskRoot)) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.WARNING, ConfigProblemDescriptions.TASK_MALFORMED_NOT_SECTION.getDescription(taskId), taskRoot));
+ continue;
+ }
+
+ if (taskType == null) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.WARNING, ConfigProblemDescriptions.NO_TASK_TYPE.getDescription(), taskRoot));
+ continue;
+ }
+
+ // check the tasks
+ TaskType t = taskTypeManager.getTaskType(taskType);
+ if (t != null) {
+ HashMap<String, Object> configValues = new HashMap<>();
+ for (String key : config.getConfigurationSection(taskRoot).getKeys(false)) {
+ configValues.put(key, config.get(taskRoot + "." + key));
+ }
+
+ problems.addAll(t.validateConfig(taskRoot, configValues));
+ } else {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.WARNING, ConfigProblemDescriptions.UNKNOWN_TASK_TYPE.getDescription(taskType), taskRoot));
+ isValid = false;
+ }
+
+ if (isValid) {
+ validTasks++;
+ }
+ }
+ if (validTasks == 0) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.ERROR, ConfigProblemDescriptions.NO_TASKS.getDescription(), "tasks"));
+ }
+ }
+
+ boolean error = false;
+ for (ConfigProblem problem : problems) {
+ if (problem.getType() == ConfigProblem.ConfigProblemType.ERROR) {
+ error = true;
+ break;
+ }
+ }
+
+ // END OF THE CHECKING
+ if (!error && !questsConfig.getBoolean("options.error-checking.override-errors", false)) {
+ QItemStack displayItem = getQItemStack("display", config);
+ List<String> rewards = config.getStringList("rewards");
+ List<String> requirements = config.getStringList("options.requires");
+ List<String> rewardString = config.getStringList("rewardstring");
+ List<String> startString = config.getStringList("startstring");
+ boolean repeatable = config.getBoolean("options.repeatable", false);
+ boolean cooldown = config.getBoolean("options.cooldown.enabled", false);
+ boolean permissionRequired = config.getBoolean("options.permission-required", false);
+ int cooldownTime = config.getInt("options.cooldown.time", 10);
+ int sortOrder = config.getInt("options.sort-order", 1);
+ String category = config.getString("options.category");
+ Map<String, String> placeholders = new HashMap<>();
+
+ if (category == null) category = "";
+
+ if (questController.getName().equals("daily")) {
+ repeatable = true;
+ cooldown = true;
+ cooldownTime = 0;
+ requirements = Collections.emptyList();
+ permissionRequired = false;
+ }
+
+ Quest quest;
+ if (category.equals("")) {
+ quest = new Quest(id, rewards, requirements, repeatable, cooldown, cooldownTime, permissionRequired, rewardString, startString, placeholders, sortOrder);
+ } else {
+ quest = new Quest(id, rewards, requirements, repeatable, cooldown, cooldownTime, permissionRequired, rewardString, startString, placeholders, category, sortOrder);
+ Category c = questManager.getCategoryById(category);
+ if (c != null) {
+ c.registerQuestId(id);
+ } else {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.WARNING, ConfigProblemDescriptions.UNKNOWN_CATEGORY.getDescription(category), "options.category"));
+ }
+ }
+
+ for (String taskId : config.getConfigurationSection("tasks").getKeys(false)) {
+ String taskRoot = "tasks." + taskId;
+ String taskType = config.getString(taskRoot + ".type");
+
+ Task task = new Task(taskId, taskType);
+
+ for (String key : config.getConfigurationSection(taskRoot).getKeys(false)) {
+ task.addConfigValue(key, config.get(taskRoot + "." + key));
+ }
+
+ if (globalTaskConfig.containsKey(taskType)) {
+ for (Map.Entry<String, Object> entry : globalTaskConfig.get(taskType).entrySet()) {
+ if (questsConfig.getBoolean("options.global-task-configuration-override") && task.getConfigValue(entry.getKey()) != null)
+ continue;
+ task.addConfigValue(entry.getKey(), entry.getValue());
+ }
+ }
+
+ quest.registerTask(task);
+ }
+
+
+ for (String line : displayItem.getLoreNormal()) {
+ findInvalidTaskReferences(quest, line, problems, "display.lore-normal");
+ }
+ for (String line : displayItem.getLoreStarted()) {
+ findInvalidTaskReferences(quest, line, problems, "display.lore-started");
+ }
+
+ if (config.isConfigurationSection("placeholders")) {
+ for (String p : config.getConfigurationSection("placeholders").getKeys(false)) {
+ placeholders.put(p, config.getString("placeholders." + p));
+ findInvalidTaskReferences(quest, config.getString("placeholders." + p), problems, "placeholders." + p);
+ }
+ }
+ if (questsConfig.getBoolean("options.show-quest-registrations")) {
+ questsLogger.info("Registering quest " + quest.getId() + " with " + quest.getTasks().size() + " tasks.");
+ }
+ questManager.registerQuest(quest);
+ taskTypeManager.registerQuestTasksWithTaskTypes(quest);
+ qItemStackRegistry.register(quest, displayItem);
+ pathToQuest.put(relativeLocation.getPath(), quest);
+ }
+ if (!problems.isEmpty()) {
+ configProblems.put(relativeLocation.getPath(), problems);
+ }
+ } catch (Exception e) {
+ questsLogger.severe("An exception occurred when attempting to load quest '" + path + "' (will be ignored)");
+ e.printStackTrace();
+ }
+ return FileVisitResult.CONTINUE;
+ }
+ };
+
+ try {
+ Files.walkFileTree(root.toPath(), fileVisitor);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ // post-load checks
+ for (Map.Entry<String, Quest> loadedQuest : pathToQuest.entrySet()) {
+ List<ConfigProblem> problems = new ArrayList<>();
+ for (String req : loadedQuest.getValue().getRequirements()) {
+ if (questManager.getQuestById(req) == null) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.WARNING, ConfigProblemDescriptions.UNKNOWN_REQUIREMENT.getDescription(req), "options.requires"));
+ }
+ }
+
+ if (!problems.isEmpty()) {
+ if (configProblems.containsKey(loadedQuest.getKey())) {
+ configProblems.get(loadedQuest.getKey()).addAll(problems);
+ } else {
+ configProblems.put(loadedQuest.getKey(), problems);
+ }
+ }
+ }
+
+ return configProblems;
+ }
+
+ private void findInvalidTaskReferences(Quest quest, String s, List<ConfigProblem> configProblems, String location) {
+ Pattern pattern = Pattern.compile("\\{([^}]+)}");
+
+ Matcher matcher = pattern.matcher(s);
+ while (matcher.find()) {
+ String[] parts = matcher.group(1).split(":");
+ boolean match = false;
+ for (Task t : quest.getTasks()) {
+ if (t.getId().equals(parts[0])) {
+ match = true;
+ break;
+ }
+ }
+ if (!match)
+ configProblems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.WARNING,
+ ConfigProblemDescriptions.UNKNOWN_TASK_REFERENCE.getDescription(parts[0]), location));
+ }
+ }
+
+ private QItemStack getQItemStack(String path, FileConfiguration config) {
+ String cName = config.getString(path + ".name", path + ".name");
+ List<String> cLoreNormal = config.getStringList(path + ".lore-normal");
+ List<String> cLoreStarted = config.getStringList(path + ".lore-started");
+
+ List<String> loreNormal = translateColoursInList(cLoreNormal);
+ List<String> loreStarted = translateColoursInList(cLoreStarted);
+
+ String name;
+ name = ChatColor.translateAlternateColorCodes('&', cName);
+
+ ItemStack is = plugin.getItemStack(path, config,
+ ItemGetter.Filter.DISPLAY_NAME, ItemGetter.Filter.LORE, ItemGetter.Filter.ENCHANTMENTS, ItemGetter.Filter.ITEM_FLAGS);
+
+ return new QItemStack(plugin, name, loreNormal, loreStarted, is);
+ }
+
+ private List<String> translateColoursInList(List<String> list) {
+ List<String> coloured = new ArrayList<>();
+ for (String s : list) {
+ coloured.add(ChatColor.translateAlternateColorCodes('&', s));
+ }
+ return coloured;
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/coreprotect/AbstractCoreProtectHook.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/coreprotect/AbstractCoreProtectHook.java
new file mode 100644
index 00000000..75f96ecd
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/coreprotect/AbstractCoreProtectHook.java
@@ -0,0 +1,16 @@
+package com.leonardobishop.quests.bukkit.hook.coreprotect;
+
+import org.bukkit.block.Block;
+
+public interface AbstractCoreProtectHook {
+
+ /**
+ * Check whether or not the most recent edit to a block was the result of a player.
+ *
+ * @param block the block
+ * @param time the time to look back in seconds
+ * @return true if from a player
+ */
+ boolean checkBlock(Block block, int time);
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/coreprotect/CoreProtectHook.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/coreprotect/CoreProtectHook.java
new file mode 100644
index 00000000..1dbfe3bd
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/coreprotect/CoreProtectHook.java
@@ -0,0 +1,29 @@
+package com.leonardobishop.quests.bukkit.hook.coreprotect;
+
+import net.coreprotect.CoreProtect;
+import net.coreprotect.CoreProtectAPI;
+import org.bukkit.Bukkit;
+import org.bukkit.block.Block;
+
+import java.util.List;
+
+public class CoreProtectHook implements AbstractCoreProtectHook {
+
+ private final CoreProtectAPI api;
+
+ public CoreProtectHook() {
+ api = ((CoreProtect) Bukkit.getPluginManager().getPlugin("CoreProtect")).getAPI();
+ }
+
+ @Override
+ public boolean checkBlock(Block block, int time) {
+ List<String[]> lookup = api.blockLookup(block, time);
+ if (lookup.isEmpty()) return false;
+
+ String[] result = lookup.get(0);
+ CoreProtectAPI.ParseResult parseResult = api.parseResult(result);
+
+ return !parseResult.getPlayer().isEmpty() && parseResult.getActionId() == 1;
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/coreprotect/CoreProtectNoHook.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/coreprotect/CoreProtectNoHook.java
new file mode 100644
index 00000000..4fc0eae9
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/coreprotect/CoreProtectNoHook.java
@@ -0,0 +1,10 @@
+package com.leonardobishop.quests.bukkit.hook.coreprotect;
+
+import org.bukkit.block.Block;
+
+public class CoreProtectNoHook implements AbstractCoreProtectHook {
+ @Override
+ public boolean checkBlock(Block block, int time) {
+ return false;
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/itemgetter/ItemGetter.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/itemgetter/ItemGetter.java
new file mode 100644
index 00000000..efbd1a36
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/itemgetter/ItemGetter.java
@@ -0,0 +1,46 @@
+package com.leonardobishop.quests.bukkit.hook.itemgetter;
+
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.inventory.ItemStack;
+
+public interface ItemGetter {
+
+ /**
+ * Gets an ItemStack from a configuration.
+ * Implementations should specific to the server version.
+ *
+ * @param path the path to where the item is defined in the config (null if item is defined in second param)
+ * @param config the configuration file
+ * @param excludes exclude certain fields in the configuration
+ * @return {@link ItemStack}
+ */
+ ItemStack getItem(String path, ConfigurationSection config, Filter... excludes);
+
+ /**
+ * Gets an ItemStack from a given string (which represents a material).
+ * For pre-1.13 server implementations, the string may use a data code.
+ *
+ * @param material the string
+ * @return {@link ItemStack}
+ */
+ ItemStack getItemStack(String material);
+
+ /**
+ * Validates a material from a string.
+ * For pre-1.13 server implementations, the string may use a data code.
+ *
+ * @param material the string
+ * @return true if it a material
+ */
+ boolean isValidMaterial(String material);
+
+ enum Filter {
+ DISPLAY_NAME,
+ LORE,
+ ENCHANTMENTS,
+ ITEM_FLAGS,
+ UNBREAKABLE,
+ ATTRIBUTE_MODIFIER,
+ CUSTOM_MODEL_DATA;
+ }
+} \ No newline at end of file
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/itemgetter/ItemGetterLatest.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/itemgetter/ItemGetterLatest.java
new file mode 100644
index 00000000..4f87c2ac
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/itemgetter/ItemGetterLatest.java
@@ -0,0 +1,240 @@
+package com.leonardobishop.quests.bukkit.hook.itemgetter;
+
+import org.bukkit.ChatColor;
+import org.bukkit.Material;
+import org.bukkit.attribute.Attribute;
+import org.bukkit.attribute.AttributeModifier;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.enchantments.Enchantment;
+import org.bukkit.inventory.EquipmentSlot;
+import org.bukkit.inventory.ItemFlag;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+public class ItemGetterLatest implements ItemGetter {
+
+ /*
+ supporting:
+ - name
+ - material
+ - lore
+ - enchantments (NamespacedKey)
+ - itemflags
+ - unbreakable
+ - attribute modifier
+ - custom model data
+
+ requires at least API version 1.14
+ */
+ @Override
+ public ItemStack getItem(String path, ConfigurationSection config, ItemGetter.Filter... excludes) {
+ if (path != null && !path.equals("")) {
+ path = path + ".";
+ }
+ List<Filter> filters = Arrays.asList(excludes);
+
+ String cName = config.getString(path + "name", path + "name");
+ String cType = config.getString(path + "item", config.getString(path + "type", path + "item"));
+ boolean hasCustomModelData = config.contains(path + "custommodeldata");
+ int customModelData = config.getInt(path + "custommodeldata", 0);
+ boolean unbreakable = config.getBoolean(path + "unbreakable", false);
+ List<String> cLore = config.getStringList(path + "lore");
+ List<String> cItemFlags = config.getStringList(path + "itemflags");
+ boolean hasAttributeModifiers = config.contains(path + "attributemodifiers");
+ List<Map<?, ?>> cAttributeModifiers = config.getMapList(path + "attributemodifiers");
+
+ String name;
+ Material type = null;
+ int data = 0;
+
+ // material
+ ItemStack is = getItemStack(cType);
+ ItemMeta ism = is.getItemMeta();
+
+ // name
+ if (!filters.contains(Filter.DISPLAY_NAME)) {
+ name = ChatColor.translateAlternateColorCodes('&', cName);
+ ism.setDisplayName(name);
+ }
+
+ // lore
+ if (!filters.contains(Filter.LORE)) {
+ List<String> lore = new ArrayList<>();
+ if (cLore != null) {
+ for (String s : cLore) {
+ lore.add(ChatColor.translateAlternateColorCodes('&', s));
+ }
+ }
+ ism.setLore(lore);
+ }
+
+ // attribute modifiers
+ if (!filters.contains(Filter.ATTRIBUTE_MODIFIER)) {
+ if (hasAttributeModifiers) {
+ for (Map<?, ?> attr : cAttributeModifiers) {
+ String cAttribute = (String) attr.get("attribute");
+ Attribute attribute = null;
+ for (Attribute enumattr : Attribute.values()) {
+ if (enumattr.toString().equals(cAttribute)) {
+ attribute = enumattr;
+ break;
+ }
+ }
+
+ if (attribute == null) continue;
+
+ Map<?, ?> configurationSection = (Map<?, ?>) attr.get("modifier");
+
+ String cUUID = (String) configurationSection.get("uuid");
+ String cModifierName = (String) configurationSection.get("name");
+ String cModifierOperation = (String) configurationSection.get("operation");
+ double cAmount;
+ try {
+ Object cAmountObj = configurationSection.get("amount");
+ if (cAmountObj instanceof Integer) {
+ cAmount = ((Integer) cAmountObj).doubleValue();
+ } else {
+ cAmount = (Double) cAmountObj;
+ }
+ } catch (Exception e) {
+ cAmount = 1;
+ }
+ String cEquipmentSlot = (String) configurationSection.get("equipmentslot");
+
+ UUID uuid = null;
+ if (cUUID != null) {
+ try {
+ uuid = UUID.fromString(cUUID);
+ } catch (Exception ignored) {
+ // ignored
+ }
+ }
+ EquipmentSlot equipmentSlot = null;
+ if (cEquipmentSlot != null) {
+ try {
+ equipmentSlot = EquipmentSlot.valueOf(cEquipmentSlot);
+ } catch (Exception ignored) {
+ // ignored
+ }
+ }
+ AttributeModifier.Operation operation = AttributeModifier.Operation.ADD_NUMBER;
+ try {
+ operation = AttributeModifier.Operation.valueOf(cModifierOperation);
+ } catch (Exception ignored) {
+ // ignored
+ }
+
+ AttributeModifier modifier;
+ if (uuid == null) {
+ modifier = new AttributeModifier(cModifierName, cAmount, operation);
+ } else if (equipmentSlot == null) {
+ modifier = new AttributeModifier(uuid, cModifierName, cAmount, operation);
+ } else {
+ modifier = new AttributeModifier(uuid, cModifierName, cAmount, operation, equipmentSlot);
+ }
+
+ ism.addAttributeModifier(attribute, modifier);
+ }
+ }
+ }
+
+ // item flags
+ if (!filters.contains(Filter.ITEM_FLAGS)) {
+ if (config.isSet(path + "itemflags")) {
+ for (String flag : cItemFlags) {
+ for (ItemFlag iflag : ItemFlag.values()) {
+ if (iflag.toString().equals(flag)) {
+ ism.addItemFlags(iflag);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // custom model data
+ if (!filters.contains(Filter.CUSTOM_MODEL_DATA)) {
+ if (hasCustomModelData) {
+ ism.setCustomModelData(customModelData);
+ }
+ }
+
+ // unbreakable
+ if (!filters.contains(Filter.UNBREAKABLE)) {
+ ism.setUnbreakable(unbreakable);
+ }
+
+ // enchantments
+ if (!filters.contains(Filter.ENCHANTMENTS)) {
+ if (config.isSet(path + "enchantments")) {
+ for (String key : config.getStringList(path + "enchantments")) {
+ String[] split = key.split(":");
+ if (split.length < 2) {
+ continue;
+ }
+ String namespace = split[0];
+ String ench = split[1];
+ String levelName;
+ if (split.length >= 3) {
+ levelName = split[2];
+ } else {
+ levelName = "1";
+ }
+
+ // TODO i don't know how these namespaces work
+// NamespacedKey namespacedKey;
+// try {
+// namespacedKey = new NamespacedKey(namespace, ench);
+// } catch (Exception e) {
+// plugin.getQuestsLogger().debug("Unrecognised namespace: " + namespace);
+// e.printStackTrace();
+// continue;
+// }
+ Enchantment enchantment;
+ if ((enchantment = Enchantment.getByName(ench)) == null) {
+ continue;
+ }
+
+ int level;
+ try {
+ level = Integer.parseInt(levelName);
+ } catch (NumberFormatException e) {
+ level = 1;
+ }
+
+ ism.addEnchant(enchantment, level, true);
+ }
+ }
+ }
+
+ is.setItemMeta(ism);
+ return is;
+ }
+
+ @Override
+ public ItemStack getItemStack(String material) {
+ Material type;
+ try {
+ type = Material.valueOf(material);
+ } catch (Exception e) {
+ type = Material.STONE;
+ }
+ return new ItemStack(type, 1);
+ }
+
+ @Override
+ public boolean isValidMaterial(String material) {
+ try {
+ Material.valueOf(material);
+ return true;
+ } catch (IllegalArgumentException ex) {
+ return false;
+ }
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/itemgetter/ItemGetter_1_13.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/itemgetter/ItemGetter_1_13.java
new file mode 100644
index 00000000..a645f810
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/itemgetter/ItemGetter_1_13.java
@@ -0,0 +1,229 @@
+package com.leonardobishop.quests.bukkit.hook.itemgetter;
+
+import org.bukkit.ChatColor;
+import org.bukkit.Material;
+import org.bukkit.attribute.Attribute;
+import org.bukkit.attribute.AttributeModifier;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.enchantments.Enchantment;
+import org.bukkit.inventory.EquipmentSlot;
+import org.bukkit.inventory.ItemFlag;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+public class ItemGetter_1_13 implements ItemGetter {
+ /*
+ reads the following:
+ - name
+ - material
+ - lore
+ - enchantments (NamespacedKey)
+ - itemflags
+ - unbreakable
+ - attribute modifier
+
+ requires at least API version 1.13
+ */
+ @Override
+ public ItemStack getItem(String path, ConfigurationSection config, ItemGetter.Filter... excludes) {
+ if (path != null && !path.equals("")) {
+ path = path + ".";
+ }
+ List<Filter> filters = Arrays.asList(excludes);
+
+ String cName = config.getString(path + "name", path + "name");
+ String cType = config.getString(path + "item", config.getString(path + "type", path + "item"));
+ boolean unbreakable = config.getBoolean(path + "unbreakable", false);
+ List<String> cLore = config.getStringList(path + "lore");
+ List<String> cItemFlags = config.getStringList(path + "itemflags");
+ boolean hasAttributeModifiers = config.contains(path + "attributemodifiers");
+ List<Map<?, ?>> cAttributeModifiers = config.getMapList(path + "attributemodifiers");
+
+ String name;
+ Material type = null;
+ int data = 0;
+
+ // material
+ ItemStack is = getItemStack(cType);
+ ItemMeta ism = is.getItemMeta();
+
+ // name
+ if (!filters.contains(Filter.DISPLAY_NAME)) {
+ name = ChatColor.translateAlternateColorCodes('&', cName);
+ ism.setDisplayName(name);
+ }
+
+ // lore
+ if (!filters.contains(Filter.LORE)) {
+ List<String> lore = new ArrayList<>();
+ if (cLore != null) {
+ for (String s : cLore) {
+ lore.add(ChatColor.translateAlternateColorCodes('&', s));
+ }
+ }
+ ism.setLore(lore);
+ }
+
+ // attribute modifiers
+ if (!filters.contains(Filter.ATTRIBUTE_MODIFIER)) {
+ if (hasAttributeModifiers) {
+ for (Map<?, ?> attr : cAttributeModifiers) {
+ String cAttribute = (String) attr.get("attribute");
+ Attribute attribute = null;
+ for (Attribute enumattr : Attribute.values()) {
+ if (enumattr.toString().equals(cAttribute)) {
+ attribute = enumattr;
+ break;
+ }
+ }
+
+ if (attribute == null) continue;
+
+ Map<?, ?> configurationSection = (Map<?, ?>) attr.get("modifier");
+
+ String cUUID = (String) configurationSection.get("uuid");
+ String cModifierName = (String) configurationSection.get("name");
+ String cModifierOperation = (String) configurationSection.get("operation");
+ double cAmount;
+ try {
+ Object cAmountObj = configurationSection.get("amount");
+ if (cAmountObj instanceof Integer) {
+ cAmount = ((Integer) cAmountObj).doubleValue();
+ } else {
+ cAmount = (Double) cAmountObj;
+ }
+ } catch (Exception e) {
+ cAmount = 1;
+ }
+ String cEquipmentSlot = (String) configurationSection.get("equipmentslot");
+
+ UUID uuid = null;
+ if (cUUID != null) {
+ try {
+ uuid = UUID.fromString(cUUID);
+ } catch (Exception ignored) {
+ // ignored
+ }
+ }
+ EquipmentSlot equipmentSlot = null;
+ if (cEquipmentSlot != null) {
+ try {
+ equipmentSlot = EquipmentSlot.valueOf(cEquipmentSlot);
+ } catch (Exception ignored) {
+ // ignored
+ }
+ }
+ AttributeModifier.Operation operation = AttributeModifier.Operation.ADD_NUMBER;
+ try {
+ operation = AttributeModifier.Operation.valueOf(cModifierOperation);
+ } catch (Exception ignored) {
+ // ignored
+ }
+
+ AttributeModifier modifier;
+ if (uuid == null) {
+ modifier = new AttributeModifier(cModifierName, cAmount, operation);
+ } else if (equipmentSlot == null) {
+ modifier = new AttributeModifier(uuid, cModifierName, cAmount, operation);
+ } else {
+ modifier = new AttributeModifier(uuid, cModifierName, cAmount, operation, equipmentSlot);
+ }
+
+ ism.addAttributeModifier(attribute, modifier);
+ }
+ }
+ }
+
+ // item flags
+ if (!filters.contains(Filter.ITEM_FLAGS)) {
+ if (config.isSet(path + "itemflags")) {
+ for (String flag : cItemFlags) {
+ for (ItemFlag iflag : ItemFlag.values()) {
+ if (iflag.toString().equals(flag)) {
+ ism.addItemFlags(iflag);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // unbreakable
+ if (!filters.contains(Filter.UNBREAKABLE)) {
+ ism.setUnbreakable(unbreakable);
+ }
+
+ // enchantments
+ if (!filters.contains(Filter.ENCHANTMENTS)) {
+ if (config.isSet(path + "enchantments")) {
+ for (String key : config.getStringList(path + "enchantments")) {
+ String[] split = key.split(":");
+ if (split.length < 2) {
+ continue;
+ }
+ String namespace = split[0];
+ String ench = split[1];
+ String levelName;
+ if (split.length >= 3) {
+ levelName = split[2];
+ } else {
+ levelName = "1";
+ }
+
+ // TODO i don't know how these namespaces work
+// NamespacedKey namespacedKey;
+// try {
+// namespacedKey = new NamespacedKey(namespace, ench);
+// } catch (Exception e) {
+// plugin.getQuestsLogger().debug("Unrecognised namespace: " + namespace);
+// e.printStackTrace();
+// continue;
+// }
+ Enchantment enchantment;
+ if ((enchantment = Enchantment.getByName(ench)) == null) {
+ continue;
+ }
+
+ int level;
+ try {
+ level = Integer.parseInt(levelName);
+ } catch (NumberFormatException e) {
+ level = 1;
+ }
+
+ is.addUnsafeEnchantment(enchantment, level);
+ }
+ }
+ }
+
+ is.setItemMeta(ism);
+ return is;
+ }
+
+ @Override
+ public ItemStack getItemStack(String material) {
+ Material type;
+ try {
+ type = Material.valueOf(material);
+ } catch (Exception e) {
+ type = Material.STONE;
+ }
+ return new ItemStack(type, 1);
+ }
+
+ @Override
+ public boolean isValidMaterial(String material) {
+ try {
+ Material.valueOf(material);
+ return true;
+ } catch (IllegalArgumentException ex) {
+ return false;
+ }
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/itemgetter/ItemGetter_Late_1_8.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/itemgetter/ItemGetter_Late_1_8.java
new file mode 100644
index 00000000..ea80deea
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/itemgetter/ItemGetter_Late_1_8.java
@@ -0,0 +1,158 @@
+package com.leonardobishop.quests.bukkit.hook.itemgetter;
+
+import org.apache.commons.lang.StringUtils;
+import org.bukkit.ChatColor;
+import org.bukkit.Material;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.enchantments.Enchantment;
+import org.bukkit.inventory.ItemFlag;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public class ItemGetter_Late_1_8 implements ItemGetter {
+ /*
+ reads the following:
+ - name
+ - material (+ DATA)
+ - lore
+ - enchantments (NOT NamespacedKey)
+ - itemflags
+
+ requires at least API version 1.8 (?)
+ */
+ @Override
+ public ItemStack getItem(String path, ConfigurationSection config, Filter... excludes) {
+ if (path != null && !path.equals("")) {
+ path = path + ".";
+ }
+ List<Filter> filters = Arrays.asList(excludes);
+
+
+ String cName = config.getString(path + "name", path + "name");
+ String cType = config.getString(path + "item", config.getString(path + "type", path + "item"));
+ List<String> cLore = config.getStringList(path + "lore");
+ List<String> cItemFlags = config.getStringList(path + "itemflags");
+
+ String name;
+ Material type = null;
+ int data = 0;
+
+ // material
+ ItemStack is = getItemStack(cType);
+ ItemMeta ism = is.getItemMeta();
+
+ // lore
+ if (!filters.contains(Filter.LORE)) {
+ List<String> lore = new ArrayList<>();
+ if (cLore != null) {
+ for (String s : cLore) {
+ lore.add(ChatColor.translateAlternateColorCodes('&', s));
+ }
+ }
+ ism.setLore(lore);
+ }
+
+ // name
+ if (!filters.contains(Filter.DISPLAY_NAME)) {
+ name = ChatColor.translateAlternateColorCodes('&', cName);
+ ism.setDisplayName(name);
+ }
+
+
+ // item flags
+ if (!filters.contains(Filter.ITEM_FLAGS)) {
+ if (config.isSet(path + "itemflags")) {
+ for (String flag : cItemFlags) {
+ for (ItemFlag iflag : ItemFlag.values()) {
+ if (iflag.toString().equals(flag)) {
+ ism.addItemFlags(iflag);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // enchantments
+ if (!filters.contains(Filter.ENCHANTMENTS)) {
+ if (config.isSet(path + "enchantments")) {
+ for (String key : config.getStringList(path + "enchantments")) {
+ String[] split = key.split(":");
+ String ench = split[0];
+ String levelName;
+ if (split.length >= 2) {
+ levelName = split[1];
+ } else {
+ levelName = "1";
+ }
+
+ Enchantment enchantment;
+ if ((enchantment = Enchantment.getByName(ench)) == null) {
+ continue;
+ }
+
+ int level;
+ try {
+ level = Integer.parseInt(levelName);
+ } catch (NumberFormatException e) {
+ level = 1;
+ }
+
+ ism.addEnchant(enchantment, level, true);
+ }
+ }
+ }
+
+ is.setItemMeta(ism);
+ return is;
+ }
+
+
+ @Override
+ public ItemStack getItemStack(String material) {
+ Material type = null;
+ int data = 0;
+
+ if (Material.getMaterial(material) != null) {
+ type = Material.getMaterial(material);
+ } else if (material.contains(":")) {
+ String[] parts = material.split(Pattern.quote(":"));
+ if (parts.length > 1) {
+ if (Material.getMaterial(parts[0]) != null) {
+ type = Material.getMaterial(parts[0]);
+ }
+ if (StringUtils.isNumeric(parts[1])) {
+ data = Integer.parseInt(parts[1]);
+ }
+ }
+ }
+
+ if (type == null) {
+ type = Material.STONE;
+ }
+ return new ItemStack(type, 1, (short) data);
+ }
+
+ @Override
+ public boolean isValidMaterial(String material) {
+ Material type = null;
+
+ if (Material.getMaterial(material) != null) {
+ type = Material.getMaterial(material);
+ } else if (material.contains(":")) {
+ String[] parts = material.split(Pattern.quote(":"));
+ if (parts.length > 1) {
+ if (Material.getMaterial(parts[0]) != null) {
+ type = Material.getMaterial(parts[0]);
+ }
+ }
+ }
+
+ return !(type == null);
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/papi/AbstractPlaceholderAPIHook.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/papi/AbstractPlaceholderAPIHook.java
new file mode 100644
index 00000000..144c2ecd
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/papi/AbstractPlaceholderAPIHook.java
@@ -0,0 +1,14 @@
+package com.leonardobishop.quests.bukkit.hook.papi;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import org.bukkit.entity.Player;
+
+public interface AbstractPlaceholderAPIHook {
+
+ String replacePlaceholders(Player player, String text);
+
+ void registerExpansion(BukkitQuestsPlugin plugin);
+
+ void unregisterExpansion();
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/papi/PlaceholderAPIHook.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/papi/PlaceholderAPIHook.java
new file mode 100644
index 00000000..c9102127
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/papi/PlaceholderAPIHook.java
@@ -0,0 +1,27 @@
+package com.leonardobishop.quests.bukkit.hook.papi;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import me.clip.placeholderapi.PlaceholderAPI;
+import org.bukkit.entity.Player;
+
+public class PlaceholderAPIHook implements AbstractPlaceholderAPIHook {
+
+ private QuestsPlaceholders placeholder;
+
+ public String replacePlaceholders(Player player, String text) {
+ return PlaceholderAPI.setPlaceholders(player, text);
+ }
+
+ @Override
+ public void registerExpansion(BukkitQuestsPlugin plugin) {
+ placeholder = new QuestsPlaceholders(plugin);
+ placeholder.register();
+ }
+
+ @Override
+ public void unregisterExpansion() {
+ placeholder.unregister();
+ }
+
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/papi/QuestsPlaceholders.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/papi/QuestsPlaceholders.java
new file mode 100644
index 00000000..7428d860
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/papi/QuestsPlaceholders.java
@@ -0,0 +1,346 @@
+package com.leonardobishop.quests.bukkit.hook.papi;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.menu.itemstack.QItemStack;
+import com.leonardobishop.quests.bukkit.util.Chat;
+import com.leonardobishop.quests.common.enums.QuestStartResult;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgressFile;
+import com.leonardobishop.quests.common.quest.Category;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.util.Format;
+import me.clip.placeholderapi.expansion.Cacheable;
+import me.clip.placeholderapi.expansion.PlaceholderExpansion;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+public class QuestsPlaceholders extends PlaceholderExpansion implements Cacheable {
+
+ private final BukkitQuestsPlugin plugin;
+ private final Map<String, Map<String, String>> cache = new HashMap<>();
+ private final Map<String, SimpleDateFormat> formats = new HashMap<>();
+
+ public QuestsPlaceholders(BukkitQuestsPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void clear() {
+ cache.clear();
+ formats.clear();
+ }
+
+ @Override
+ public String getIdentifier() {
+ return "resources/bukkit/quests";
+ }
+
+ @Override
+ public String getAuthor() {
+ return plugin.getDescription().getAuthors().toString();
+ }
+
+ @Override
+ public String getVersion() {
+ return plugin.getDescription().getVersion();
+ }
+
+ @Override
+ public boolean persist() {
+ return true;
+ }
+
+ @Override
+ public String onPlaceholderRequest(Player p, String params) {
+ if (p == null || !p.isOnline()) return null;
+ if (cache.containsKey(p.getName()) && cache.get(p.getName()).containsKey(params))
+ return cache.get(p.getName()).get(params);
+
+ String[] args = params.split("_", 4);
+ if (args.length < 1) return "Invalid Placeholder";
+
+ final boolean save = args[args.length - 1].toLowerCase().equals("cache");
+ if (save) args = Arrays.copyOf(args, args.length - 1);
+
+ final QPlayer qPlayer = plugin.getPlayerManager().getPlayer(p.getUniqueId());
+ if (qPlayer == null) return "Data not loaded";
+ String split = args[args.length - 1];
+
+ String result = "null";
+ if (!args[0].contains(":") && !args[0].equalsIgnoreCase("tracked")) {
+ if (args.length > 1 && split.equals(args[1])) split = ",";
+
+ switch (args[0].toLowerCase()) {
+ case "all":
+ case "a":
+ final List<Quest> listAll = new ArrayList<>(plugin.getQuestManager().getQuests().values());
+ result = (args.length == 1 ? String.valueOf(listAll.size()) : parseList((List<Quest>) listAll, args[1], split));
+ break;
+ case "completed":
+ case "c":
+ final List<Quest> listCompleted = qPlayer.getQuestProgressFile().getAllQuestsFromProgress(QuestProgressFile.QuestsProgressFilter.COMPLETED);
+ result = (args.length == 1 ? String.valueOf(listCompleted.size()) : parseList(listCompleted, args[1], split));
+ break;
+ case "completedbefore":
+ case "cb":
+ final List<Quest> listCompletedB = qPlayer.getQuestProgressFile().getAllQuestsFromProgress(QuestProgressFile.QuestsProgressFilter.COMPLETED_BEFORE);
+ result = (args.length == 1 ? String.valueOf(listCompletedB.size()) : parseList(listCompletedB, args[1], split));
+ break;
+ case "started":
+ case "s":
+ final List<Quest> listStarted = qPlayer.getQuestProgressFile().getAllQuestsFromProgress(QuestProgressFile.QuestsProgressFilter.STARTED);
+ result = (args.length == 1 ? String.valueOf(listStarted.size()) : parseList(listStarted, args[1], split));
+ break;
+ case "categories":
+ if (args.length == 1) {
+ result = String.valueOf(plugin.getQuestManager().getCategories().size());
+ } else {
+ final List<String> listCategories = new ArrayList<>();
+ switch (args[1].toLowerCase()) {
+ case "list":
+ case "l":
+ plugin.getQuestManager().getCategories().forEach(c -> {
+ ItemStack itemStack = plugin.getQItemStackRegistry().getCategoryItemStack(c);
+ listCategories.add(Chat.strip(itemStack.getItemMeta().getDisplayName()));
+ });
+ break;
+ case "listid":
+ case "lid":
+ plugin.getQuestManager().getCategories().forEach(c -> listCategories.add(c.getId()));
+ break;
+ default:
+ return args[0] + "_" + args[1] + " is not a valid placeholder";
+ }
+ result = String.join(split, listCategories);
+ }
+ break;
+ default:
+ return args[0] + " is not a valid placeholder";
+ }
+ } else {
+ final String[] key = args[0].split(":");
+ switch (key[0].toLowerCase()) {
+ case "quest":
+ case "q":
+ case "tracked":
+ if (!key[0].equalsIgnoreCase("tracked") && key.length == 1) return "Please specify quest name";
+
+ final Quest quest;
+ if (!key[0].equalsIgnoreCase("tracked")) {
+ quest = plugin.getQuestManager().getQuestById(key[1]);
+ if (quest == null) return key[1] + " is not a quest";
+ } else {
+ quest = plugin.getQuestManager().getQuestById(qPlayer.getPlayerPreferences().getTrackedQuestId());
+ if (quest == null) {
+ if (args.length == 1) {
+ return "No tracked quest";
+ } else {
+ return "";
+ }
+ }
+ }
+
+ if (args.length == 1) {
+ result = getQuestDisplayNameStripped(quest);
+ } else {
+ switch (args[1].toLowerCase()) {
+ case "started":
+ case "s":
+ result = (qPlayer.getQuestProgressFile().getQuestProgress(quest).isStarted() ? "true" : "false");
+ break;
+ case "completed":
+ case "c":
+ result = (qPlayer.getQuestProgressFile().getQuestProgress(quest).isCompleted() ? "true" : "false");
+ break;
+ case "completedbefore":
+ case "cb":
+ result = (qPlayer.getQuestProgressFile().getQuestProgress(quest).isCompletedBefore() ? "true" : "false");
+ break;
+ case "completiondate":
+ case "cd":
+ if (qPlayer.getQuestProgressFile().getQuestProgress(quest).isCompleted()) {
+ result = parseDate(args, qPlayer.getQuestProgressFile().getQuestProgress(quest).getCompletionDate());
+ } else {
+ result = "Never";
+ }
+ break;
+ case "cooldown":
+ if (qPlayer.getQuestProgressFile().getQuestProgress(quest).isCompleted()) {
+ final String time = Format.formatTime(TimeUnit.SECONDS.convert(qPlayer.getQuestProgressFile().getCooldownFor(quest), TimeUnit.MILLISECONDS));
+ if (!time.startsWith("-")) result = time;
+ } else {
+ result = "0";
+ }
+ break;
+ case "canaccept":
+ result = (qPlayer.canStartQuest(quest) == QuestStartResult.QUEST_SUCCESS ? "true" : "false");
+ break;
+ case "meetsrequirements":
+ result = (qPlayer.getQuestProgressFile().hasMetRequirements(quest) ? "true" : "false");
+ break;
+ default:
+ if (!args[1].contains(":"))
+ return args[0] + "_" + args[1] + " is not a valid placeholder";
+
+ final String[] t = args[1].split(":");
+ if (t[0].equalsIgnoreCase("task") || t[0].equalsIgnoreCase("t")) {
+ if (t.length == 1) return "Please specify task name";
+
+ if (args.length == 2) {
+ result = qPlayer.getQuestProgressFile().getQuestProgress(quest).getTaskProgress(t[1]).getTaskId();
+ } else {
+ switch (args[2].toLowerCase()) {
+ case "progress":
+ case "p":
+ final Object progress = qPlayer.getQuestProgressFile().getQuestProgress(quest).getTaskProgress(t[1]).getProgress();
+ result = (progress == null ? "0" : String.valueOf(progress));
+ break;
+ case "completed":
+ case "c":
+ result = String.valueOf(qPlayer.getQuestProgressFile().getQuestProgress(quest).getTaskProgress(t[1]).isCompleted());
+ break;
+ default:
+ return args[0] + "_" + args[1] + "_" + args[2] + " is not a valid placeholder";
+ }
+ }
+ } else if (t[0].equalsIgnoreCase("placeholder") || t[0].equalsIgnoreCase("p")) {
+ if (t.length == 1) return "Please specify placeholder name";
+
+ String placeholder = quest.getPlaceholders().get(t[1]);
+ if (placeholder == null) {
+ return t[1] + " is not a valid placeholder within quest " + quest.getId();
+ }
+ placeholder = QItemStack.processPlaceholders(Chat.color(placeholder), qPlayer.getQuestProgressFile().getQuestProgress(quest));
+ return placeholder;
+ } else {
+ return args[0] + "_" + args[1] + " is not a valid placeholder";
+ }
+ }
+ }
+ break;
+ case "category":
+ case "c":
+ if (!plugin.getQuestsConfig().getBoolean("options.categories-enabled")) return "Categories Disabled";
+ if (key.length == 1) return "Please specify category name";
+
+ final Category category = plugin.getQuestManager().getCategoryById(key[1]);
+ if (category == null) return key[1] + " is not a category";
+
+ if (args.length == 1) {
+ ItemStack itemStack = plugin.getQItemStackRegistry().getCategoryItemStack(category);
+ result = Chat.strip(itemStack.getItemMeta().getDisplayName());
+ } else {
+ if (args.length > 2 && split.equals(args[2])) split = ",";
+ switch (args[1].toLowerCase()) {
+ case "all":
+ case "a":
+ final List<Quest> listAll = getCategoryQuests(qPlayer, category, QuestProgressFile.QuestsProgressFilter.ALL);
+ result = (args.length == 2 ? String.valueOf(listAll.size()) : parseList(listAll, args[2], split));
+ break;
+ case "completed":
+ case "c":
+ final List<Quest> listCompleted = getCategoryQuests(qPlayer, category, QuestProgressFile.QuestsProgressFilter.COMPLETED);
+ result = (args.length == 2 ? String.valueOf(listCompleted.size()) : parseList(listCompleted, args[2], split));
+ break;
+ case "completedbefore":
+ case "cb":
+ final List<Quest> listCompletedB = getCategoryQuests(qPlayer, category, QuestProgressFile.QuestsProgressFilter.COMPLETED_BEFORE);
+ result = (args.length == 2 ? String.valueOf(listCompletedB.size()) : parseList(listCompletedB, args[2], split));
+ break;
+ case "started":
+ case "s":
+ final List<Quest> listStarted = getCategoryQuests(qPlayer, category, QuestProgressFile.QuestsProgressFilter.STARTED);
+ result = (args.length == 2 ? String.valueOf(listStarted.size()) : parseList(listStarted, args[2], split));
+ break;
+ default:
+ return args[0] + "_" + args[1] + " is not a valid placeholder";
+ }
+ }
+ break;
+ default:
+ return args[0] + " is not a valid placeholder";
+ }
+ }
+ return (save ? cache(p.getName(), params, result) : result);
+ }
+
+ private String cache(String player, String params, String result) {
+ if (!cache.containsKey(player) || !cache.get(player).containsKey(params)) {
+ final Map<String, String> map = new HashMap<>();
+ map.put(params, result);
+ cache.put(player, map);
+ Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> cache.get(player).remove(params), plugin.getConfig().getInt("options.placeholder-cache-time", 10) * 20);
+ }
+ return result;
+ }
+
+ private String parseDate(String[] args, Long date) {
+ final String format = (args[args.length - 1].equals(args[1]) ? "dd/MM/yyyy" : args[args.length - 1]);
+ SimpleDateFormat sdf;
+ if (formats.containsKey(format)) {
+ sdf = formats.get(format);
+ } else {
+ sdf = new SimpleDateFormat(format);
+ formats.put(format, sdf);
+ }
+ return sdf.format(date);
+ }
+
+ private String parseList(List<Quest> list, String type, String separator) {
+ final List<String> quests = new ArrayList<>();
+ switch (type.toLowerCase()) {
+ case "list":
+ case "l":
+ list.forEach(q -> quests.add(getQuestDisplayNameStripped(q)));
+ break;
+ case "listid":
+ case "lid":
+ list.forEach(q -> quests.add(q.getId()));
+ break;
+ default:
+ return type + "is not a valid placeholder";
+ }
+ return String.join(separator, quests);
+ }
+
+ private String getQuestDisplayNameStripped(Quest quest) {
+ QItemStack qItemStack = plugin.getQItemStackRegistry().getQuestItemStack(quest);
+ if (qItemStack != null) return Chat.strip(qItemStack.getName());
+ return null;
+ }
+
+ private List<Quest> getCategoryQuests(QPlayer questP, Category category, QuestProgressFile.QuestsProgressFilter filter) {
+ final List<Quest> categoryQuests = new ArrayList<>();
+ category.getRegisteredQuestIds().forEach(q -> {
+ Quest quest = plugin.getQuestManager().getQuestById(q);
+ if (quest != null) {
+ switch (filter) {
+ case STARTED:
+ if (questP.getQuestProgressFile().getQuestProgress(quest).isStarted())
+ categoryQuests.add(quest);
+ break;
+ case COMPLETED:
+ if (questP.getQuestProgressFile().getQuestProgress(quest).isCompleted())
+ categoryQuests.add(quest);
+ break;
+ case COMPLETED_BEFORE:
+ if (questP.getQuestProgressFile().getQuestProgress(quest).isCompletedBefore())
+ categoryQuests.add(quest);
+ break;
+ default:
+ categoryQuests.add(quest);
+ }
+ }
+ });
+ return categoryQuests;
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/title/Title.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/title/Title.java
new file mode 100644
index 00000000..49c4613b
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/title/Title.java
@@ -0,0 +1,8 @@
+package com.leonardobishop.quests.bukkit.hook.title;
+
+import org.bukkit.entity.Player;
+
+public interface Title {
+
+ void sendTitle(Player player, String message, String submessage);
+} \ No newline at end of file
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/title/Title_Bukkit.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/title/Title_Bukkit.java
new file mode 100644
index 00000000..6975700e
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/title/Title_Bukkit.java
@@ -0,0 +1,12 @@
+package com.leonardobishop.quests.bukkit.hook.title;
+
+import org.bukkit.entity.Player;
+
+public class Title_Bukkit implements Title {
+
+ // new title function with timings
+ @Override
+ public void sendTitle(Player player, String message, String submessage) {
+ player.sendTitle(message, submessage, 10, 100, 10);
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/title/Title_BukkitNoTimings.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/title/Title_BukkitNoTimings.java
new file mode 100644
index 00000000..64c91ea6
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/title/Title_BukkitNoTimings.java
@@ -0,0 +1,13 @@
+package com.leonardobishop.quests.bukkit.hook.title;
+
+import org.bukkit.entity.Player;
+
+public class Title_BukkitNoTimings implements Title {
+
+ // this one is for 1.8, 1.9 and 1.10 where there was no timings method
+ @Override
+ public void sendTitle(Player player, String message, String submessage) {
+ player.sendTitle(message, submessage);
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/title/Title_Other.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/title/Title_Other.java
new file mode 100644
index 00000000..cdc8d7bf
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/hook/title/Title_Other.java
@@ -0,0 +1,11 @@
+package com.leonardobishop.quests.bukkit.hook.title;
+
+import org.bukkit.entity.Player;
+
+public class Title_Other implements Title {
+
+ @Override
+ public void sendTitle(Player player, String message, String submessage) {
+ // title function does not exist
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/listener/PlayerJoinListener.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/listener/PlayerJoinListener.java
new file mode 100644
index 00000000..2fe15860
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/listener/PlayerJoinListener.java
@@ -0,0 +1,51 @@
+package com.leonardobishop.quests.bukkit.listener;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.util.Messages;
+import com.leonardobishop.quests.common.player.QPlayer;
+import org.bukkit.Bukkit;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
+import org.bukkit.event.player.PlayerJoinEvent;
+
+import java.util.UUID;
+
+public class PlayerJoinListener implements Listener {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public PlayerJoinListener(BukkitQuestsPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void onAsyncJoin(AsyncPlayerPreLoginEvent event) {
+ plugin.getPlayerManager().loadPlayer(event.getUniqueId());
+ }
+
+ @EventHandler
+ public void onEvent(PlayerJoinEvent event) {
+ UUID playerUuid = event.getPlayer().getUniqueId();
+ plugin.getPlayerManager().loadPlayer(playerUuid);
+ if (plugin.getDescription().getVersion().contains("beta") && event.getPlayer().hasPermission("quests.admin")) {
+ event.getPlayer().sendMessage(Messages.BETA_REMINDER.getMessage());
+ }
+ if (plugin.getUpdater().isUpdateReady() && event.getPlayer().hasPermission("quests.admin")) {
+ // delay for a bit so they actually see the message
+ String updateMessage = Messages.QUEST_UPDATER.getMessage()
+ .replace("{newver}", plugin.getUpdater().getReturnedVersion())
+ .replace("{oldver}", plugin.getUpdater().getInstalledVersion())
+ .replace("{link}", plugin.getUpdater().getUpdateLink());
+ Bukkit.getScheduler().runTaskLater(this.plugin, () -> event.getPlayer().sendMessage(updateMessage), 50L);
+ }
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(playerUuid);
+ if (qPlayer == null) return;
+
+ // run a full check to check for any missed quest completions
+ plugin.getQuestCompleter().queueFullCheck(qPlayer.getQuestProgressFile());
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/listener/PlayerLeaveListener.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/listener/PlayerLeaveListener.java
new file mode 100644
index 00000000..1f41344a
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/listener/PlayerLeaveListener.java
@@ -0,0 +1,24 @@
+package com.leonardobishop.quests.bukkit.listener;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.common.player.QPlayer;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.player.PlayerQuitEvent;
+
+public class PlayerLeaveListener implements Listener {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public PlayerLeaveListener(BukkitQuestsPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @EventHandler
+ public void onEvent(PlayerQuitEvent event) {
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(event.getPlayer().getUniqueId());
+ if (qPlayer == null) return;
+ plugin.getPlayerManager().removePlayer(qPlayer.getPlayerUUID());
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/CancelQMenu.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/CancelQMenu.java
new file mode 100644
index 00000000..3c811bff
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/CancelQMenu.java
@@ -0,0 +1,85 @@
+package com.leonardobishop.quests.bukkit.menu;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.config.BukkitQuestsConfig;
+import com.leonardobishop.quests.bukkit.util.Chat;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.quest.Quest;
+import org.bukkit.Bukkit;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+/**
+ * Represents a cancellation confirmation menu for a specific quest.
+ */
+public class CancelQMenu implements QMenu {
+
+ private final BukkitQuestsPlugin plugin;
+ private final BukkitQuestsConfig config;
+ private final QMenu superMenu;
+ private final QPlayer owner;
+ private final Quest quest;
+
+ public CancelQMenu(BukkitQuestsPlugin plugin, QMenu superMenu, QPlayer owner, Quest quest) {
+ this.plugin = plugin;
+ this.config = (BukkitQuestsConfig) plugin.getQuestsConfig();
+ this.superMenu = superMenu;
+ this.owner = owner;
+ this.quest = quest;
+ }
+
+ public Quest getQuest() {
+ return quest;
+ }
+
+ @Override
+ public QPlayer getOwner() {
+ return owner;
+ }
+
+ public Inventory toInventory(int page) {
+ String title = Chat.color(config.getString("options.guinames.quest-cancel"));
+
+ ItemStack yes = config.getItem("gui.quest-cancel-yes");
+ ItemStack no = config.getItem("gui.quest-cancel-no");
+
+ ItemStack background = config.getItem("gui.quest-cancel-background");
+ ItemMeta backgroundMeta = background.getItemMeta();
+ backgroundMeta.setDisplayName(" ");
+ background.setItemMeta(backgroundMeta);
+
+ Inventory inventory = Bukkit.createInventory(null, 27, title);
+
+ for (int i = 0; i < inventory.getSize(); i++) {
+ inventory.setItem(i, background);
+ }
+
+ inventory.setItem(10, no);
+ inventory.setItem(11, no);
+ inventory.setItem(12, no);
+ inventory.setItem(13, plugin.getQItemStackRegistry().getQuestItemStack(quest).toItemStack(quest, owner, owner.getQuestProgressFile().getQuestProgress(quest)));
+ inventory.setItem(14, yes);
+ inventory.setItem(15, yes);
+ inventory.setItem(16, yes);
+
+ return inventory;
+ }
+
+ @Override
+ public void handleClick(InventoryClickEvent event, MenuController controller) {
+ if (event.getSlot() == 10 || event.getSlot() == 11 || event.getSlot() == 12) {
+ controller.openMenu(event.getWhoClicked(), superMenu, 1);
+ } else if (event.getSlot() == 14 || event.getSlot() == 15 || event.getSlot() == 16) {
+ if (owner.cancelQuest(quest)) {
+ event.getWhoClicked().closeInventory();
+ }
+ }
+ }
+
+ public QMenu getSuperMenu() {
+ return superMenu;
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/CategoryQMenu.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/CategoryQMenu.java
new file mode 100644
index 00000000..03693657
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/CategoryQMenu.java
@@ -0,0 +1,171 @@
+package com.leonardobishop.quests.bukkit.menu;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.config.BukkitQuestsConfig;
+import com.leonardobishop.quests.bukkit.menu.element.CategoryMenuElement;
+import com.leonardobishop.quests.bukkit.menu.element.CustomMenuElement;
+import com.leonardobishop.quests.bukkit.menu.element.MenuElement;
+import com.leonardobishop.quests.bukkit.menu.element.SpacerMenuElement;
+import com.leonardobishop.quests.bukkit.util.Chat;
+import com.leonardobishop.quests.bukkit.util.MenuUtils;
+import com.leonardobishop.quests.bukkit.util.Messages;
+import com.leonardobishop.quests.common.player.QPlayer;
+import org.apache.commons.lang.math.NumberUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents a menu which contains a listing of different categories.
+ */
+public class CategoryQMenu implements QMenu {
+
+ private final BukkitQuestsPlugin plugin;
+ private final BukkitQuestsConfig config;
+ private final HashMap<Integer, MenuElement> menuElements = new HashMap<>();
+ private final QPlayer owner;
+
+ private int pageSize = 45;
+ private int maxElement = 0;
+ private int pagePrevLocation = -1;
+ private int pageNextLocation = -1;
+ private int currentPage = -1;
+
+ public CategoryQMenu(BukkitQuestsPlugin plugin, QPlayer owner) {
+ this.plugin = plugin;
+ this.config = (BukkitQuestsConfig) plugin.getQuestsConfig();
+ this.owner = owner;
+ }
+
+ public void populate(List<QuestQMenu> menuQuests) {
+ if (config.getConfig().isConfigurationSection("custom-elements.categories")) {
+ for (String s : config.getConfig().getConfigurationSection("custom-elements.categories").getKeys(false)) {
+ if (!NumberUtils.isNumber(s)) continue;
+ int slot = Integer.parseInt(s);
+ int repeat = config.getInt("custom-elements.categories." + s + ".repeat");
+ MenuElement menuElement;
+ if (config.getConfig().contains("custom-elements.categories." + s + ".display")) {
+ ItemStack is = config.getItem("custom-elements.categories." + s + ".display");
+ menuElement = new CustomMenuElement(is);
+ } else if (config.getBoolean("custom-elements.categories." + s + ".spacer", false)) {
+ menuElement = new SpacerMenuElement();
+ } else continue; // user = idiot
+
+ for (int i = 0; i <= repeat; i++) {
+ menuElements.put(slot + i, menuElement);
+ }
+ }
+ }
+ int slot = 0;
+ for (QuestQMenu questQMenu : menuQuests) {
+ while (menuElements.containsKey(slot)) slot++;
+ if (config.getBoolean("options.gui-hide-categories-nopermission") && plugin.getQuestManager().getCategoryById(questQMenu.getCategoryName()).isPermissionRequired()) {
+ if (!Bukkit.getPlayer(owner.getPlayerUUID()).hasPermission("quests.category." + questQMenu.getCategoryName())) {
+ continue;
+ }
+ }
+ menuElements.put(slot, new CategoryMenuElement(plugin, owner.getPlayerUUID(), questQMenu));
+ slot++;
+ }
+
+ for (Integer integer : menuElements.keySet()) {
+ if (integer + 1 > maxElement) maxElement = integer + 1;
+ }
+
+ // stop bottom row of pg1 going to pg2 if entire inv contents would fit on pg1 perfectly
+ if (maxElement > 45 && maxElement <= 54) {
+ pageSize = 54;
+ } else {
+ pageSize = 45;
+ }
+ }
+
+ @Override
+ public QPlayer getOwner() {
+ return owner;
+ }
+
+ public Inventory toInventory(int page) {
+ currentPage = page;
+ int pageMin = pageSize * (page - 1);
+ int pageMax = pageSize * page;
+ String title = Chat.color(config.getString("options.guinames.quests-category"));
+
+ ItemStack pageIs;
+ ItemStack pagePrevIs;
+ ItemStack pageNextIs;
+ Inventory inventory = Bukkit.createInventory(null, 54, title);
+
+ int highestOnPage = 0;
+ for (int pointer = pageMin; pointer < pageMax; pointer++) {
+ if (menuElements.containsKey(pointer)) {
+ inventory.setItem(pointer - ((page - 1) * pageSize), menuElements.get(pointer).asItemStack());
+ if (pointer + 1 > highestOnPage) highestOnPage = pointer + 1;
+ }
+ }
+
+ pageNextLocation = -1;
+ pagePrevLocation = -1;
+
+ Map<String, String> pageplaceholders = new HashMap<>();
+ pageplaceholders.put("{prevpage}", String.valueOf(page - 1));
+ pageplaceholders.put("{nextpage}", String.valueOf(page + 1));
+ pageplaceholders.put("{page}", String.valueOf(page));
+ pageIs = MenuUtils.applyPlaceholders(plugin, owner.getPlayerUUID(), config.getItem("gui.page-desc"), pageplaceholders);
+ pageIs.setAmount(Math.min(page, 64));
+ pagePrevIs = MenuUtils.applyPlaceholders(plugin, owner.getPlayerUUID(), config.getItem("gui.page-prev"), pageplaceholders);
+ pageNextIs = MenuUtils.applyPlaceholders(plugin, owner.getPlayerUUID(), config.getItem("gui.page-next"), pageplaceholders);
+
+ if (maxElement > pageSize) {
+ inventory.setItem(49, pageIs);
+ if (page != 1) {
+ inventory.setItem(48, pagePrevIs);
+ pagePrevLocation = 48;
+ }
+ if (Math.ceil((double) maxElement / ((double) pageSize)) != page) {
+ inventory.setItem(50, pageNextIs);
+ pageNextLocation = 50;
+ }
+ } else if (config.getBoolean("options.trim-gui-size") && page == 1) {
+ int inventorySize = highestOnPage + (9 - highestOnPage % 9) * Math.min(1, highestOnPage % 9);
+ inventorySize = inventorySize <= 0 ? 9 : inventorySize;
+ if (inventorySize == 54) {
+ return inventory;
+ }
+
+ Inventory trimmedInventory = Bukkit.createInventory(null, inventorySize, title);
+
+ for (int slot = 0; slot < trimmedInventory.getSize(); slot++) {
+ trimmedInventory.setItem(slot, inventory.getItem(slot));
+ }
+ return trimmedInventory;
+ }
+ return inventory;
+ }
+
+ @Override
+ public void handleClick(InventoryClickEvent event, MenuController controller) {
+ if (pagePrevLocation == event.getSlot()) {
+ controller.openMenu(event.getWhoClicked(), this, currentPage - 1);
+
+ } else if (pageNextLocation == event.getSlot()) {
+ controller.openMenu(event.getWhoClicked(), this, currentPage + 1);
+
+ } else if (event.getSlot() < pageSize && menuElements.containsKey(event.getSlot() + (((currentPage) - 1) * pageSize))) {
+ MenuElement element = menuElements.get(event.getSlot() + ((currentPage - 1) * pageSize));
+ if (element instanceof CategoryMenuElement) {
+ CategoryMenuElement categoryMenuElement = (CategoryMenuElement) element;
+ QuestQMenu questQMenu = categoryMenuElement.getQuestMenu();
+ if (plugin.getMenuController().openQuestCategory(owner,
+ plugin.getQuestManager().getCategoryById(questQMenu.getCategoryName()), questQMenu) != 0) {
+ event.getWhoClicked().sendMessage(Messages.QUEST_CATEGORY_PERMISSION.getMessage());
+ }
+ }
+ }
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/DailyQMenu.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/DailyQMenu.java
new file mode 100644
index 00000000..622137a4
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/DailyQMenu.java
@@ -0,0 +1,97 @@
+//package com.leonardobishop.quests.bukkit.menu;
+//
+//import com.leonardobishop.quests.Quests;
+//import com.leonardobishop.quests.common.enums.QuestStartResult;
+//import com.leonardobishop.quests.common.player.QPlayer;
+//import com.leonardobishop.quests.common.quest.Quest;
+//import com.leonardobishop.quests.listener.MenuController;
+//import com.leonardobishop.quests.menu.element.MenuElement;
+//import com.leonardobishop.quests.menu.element.QuestMenuElement;
+//import com.leonardobishop.quests.quest.controller.DailyQuestController;
+//import com.leonardobishop.quests.util.Items;
+//import com.leonardobishop.quests.util.Options;
+//import org.bukkit.Bukkit;
+//import org.bukkit.event.inventory.ClickType;
+//import org.bukkit.event.inventory.InventoryClickEvent;
+//import org.bukkit.inventory.Inventory;
+//import org.bukkit.inventory.ItemStack;
+//import org.bukkit.inventory.meta.ItemMeta;
+//
+//import java.util.HashMap;
+//import java.util.List;
+//import java.util.Map;
+//
+///**
+// * Represents a cancellation confirmation menu for a specific quest.
+// */
+//public class DailyQMenu implements QMenu {
+//
+// private final Map<Integer, MenuElement> menuElements = new HashMap<>();
+// private final Quests plugin;
+// private final QPlayer owner;
+//
+// public DailyQMenu(Quests plugin, QPlayer owner) {
+// this.plugin = plugin;
+// this.owner = owner;
+// }
+//
+// @Override
+// public QPlayer getOwner() {
+// return owner;
+// }
+//
+// public void populate() {
+// if (!(owner.getQuestController() instanceof DailyQuestController)) {
+// return;
+// }
+// DailyQuestController dailyQuestController = (DailyQuestController) owner.getQuestController();
+// List<String> quests = dailyQuestController.getQuests();
+// for (int i = 0; i < quests.size(); i++) {
+// menuElements.put(11 + i, new QuestMenuElement(plugin, owner, quests.get(i)));
+// }
+// }
+//
+// public Inventory toInventory(int page) {
+// String title = Options.color(Options.GUITITLE_DAILY_QUESTS.getStringValue());
+//
+// ItemStack background = Items.QUEST_CANCEL_BACKGROUND.getItem();
+// ItemMeta backgroundMeta = background.getItemMeta();
+// backgroundMeta.setDisplayName(" ");
+// background.setItemMeta(backgroundMeta);
+//
+// Inventory inventory = Bukkit.createInventory(null, 27, title);
+//
+// for (int i = 0; i < inventory.getSize(); i++) {
+// inventory.setItem(i, background);
+// }
+//
+// for (int pointer = 0; pointer < 27; pointer++) {
+// if (menuElements.containsKey(pointer)) {
+// inventory.setItem(pointer, menuElements.get(pointer).asItemStack());
+// }
+// }
+//
+// return inventory;
+// }
+//
+// @Override
+// public void handleClick(InventoryClickEvent event, MenuController controller) {
+// if (menuElements.containsKey(event.getSlot())) {
+// MenuElement menuElement = menuElements.get(event.getSlot());
+// if (menuElement instanceof QuestMenuElement) {
+// QuestMenuElement questMenuElement = (QuestMenuElement) menuElement;
+// Quest quest = plugin.getQuestManager().getQuestById(questMenuElement.getQuestId());
+// if (event.getClick() == ClickType.LEFT) {
+// if (Options.QUEST_AUTOSTART.getBooleanValue()) return;
+// if (owner.startQuest(quest) == QuestStartResult.QUEST_SUCCESS) {
+// event.getWhoClicked().closeInventory(); //TODO Option to keep the menu open
+// }
+// } else if (event.getClick() == ClickType.MIDDLE && Options.ALLOW_QUEST_TRACK.getBooleanValue()) {
+// MenuUtil.handleMiddleClick(this, quest, Bukkit.getPlayer(owner.getPlayerUUID()), controller);
+// }
+// }
+// }
+// }
+//
+//
+//}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/MenuController.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/MenuController.java
new file mode 100644
index 00000000..aeb543d6
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/MenuController.java
@@ -0,0 +1,176 @@
+package com.leonardobishop.quests.bukkit.menu;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.quest.Category;
+import com.leonardobishop.quests.common.quest.Quest;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.HumanEntity;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.Listener;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.event.inventory.InventoryCloseEvent;
+import org.bukkit.event.inventory.InventoryType;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+public class MenuController implements Listener {
+
+ private final HashMap<UUID, QMenu> tracker = new HashMap<>();
+ private final BukkitQuestsPlugin plugin;
+
+ public MenuController(BukkitQuestsPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ public void openMenu(HumanEntity player, QMenu qMenu, int page) {
+ player.openInventory(qMenu.toInventory(page));
+ tracker.put(player.getUniqueId(), qMenu);
+ }
+
+ @EventHandler
+ private void onClose(InventoryCloseEvent event) {
+ tracker.remove(event.getPlayer().getUniqueId());
+ }
+
+ @EventHandler
+ private void onClick(InventoryClickEvent event) {
+ // check if the player has a quest menu open
+ if (tracker.containsKey(event.getWhoClicked().getUniqueId())) {
+ event.setCancelled(true);
+ if (event.getClickedInventory() == null)
+ return; //The player clicked outside the inventory
+ if (event.getClickedInventory().getType() == InventoryType.PLAYER)
+ return; //The clicked inventory is a player inventory type
+
+ QMenu qMenu = tracker.get(event.getWhoClicked().getUniqueId());
+ qMenu.handleClick(event, this);
+ }
+ }
+
+ /**
+ * Opens a quest listing menu for the player.
+ *
+ * @return 0 if success, 1 if no permission, 2 is only data loaded, 3 if player not found
+ */
+ public int openQuestCategory(QPlayer qPlayer, Category category, CategoryQMenu superMenu, boolean backButton) {
+ Player player = Bukkit.getPlayer(qPlayer.getPlayerUUID());
+ if (player == null) {
+ return 3;
+ }
+
+ if (category.isPermissionRequired() && !player.hasPermission("quests.category." + category.getId())) {
+ return 1;
+ }
+
+ // Using `this` instead of searching again for this QPlayer
+ QuestQMenu questQMenu = new QuestQMenu(plugin, qPlayer, category.getId(), superMenu);
+ List<Quest> quests = new ArrayList<>();
+ for (String questid : category.getRegisteredQuestIds()) {
+ Quest quest = plugin.getQuestManager().getQuestById(questid);
+ if (quest != null) {
+ quests.add(quest);
+ }
+ }
+ questQMenu.populate(quests);
+ questQMenu.setBackButtonEnabled(backButton);
+
+ openMenu(player, questQMenu, 1);
+ return 0;
+ }
+
+ /**
+ * Opens a specific quest listing menu for the player.
+ *
+ * @return 0 if success, 1 if no permission, 2 is only data loaded, 3 if player not found
+ */
+ public int openQuestCategory(QPlayer qPlayer, Category category, QuestQMenu questQMenu) {
+ Player player = Bukkit.getPlayer(qPlayer.getPlayerUUID());
+ if (player == null) {
+ return 3;
+ }
+
+ if (category.isPermissionRequired() && !player.hasPermission("quests.category." + category.getId())) {
+ return 1;
+ }
+
+ openMenu(player, questQMenu, 1);
+ return 0;
+ }
+
+ /**
+ * Open the main menu for the player
+ *
+ * @param qPlayer player
+ */
+ public void openMainMenu(QPlayer qPlayer) {
+ Player player = Bukkit.getPlayer(qPlayer.getPlayerUUID());
+ if (player == null) {
+ return;
+ }
+
+ if (plugin.getQuestController().getName().equals("normal")) {
+ if (plugin.getQuestsConfig().getBoolean("options.categories-enabled")) {
+ CategoryQMenu categoryQMenu = new CategoryQMenu(plugin, qPlayer);
+ List<QuestQMenu> questMenus = new ArrayList<>();
+ for (Category category : plugin.getQuestManager().getCategories()) {
+ QuestQMenu questQMenu = new QuestQMenu(plugin, qPlayer, category.getId(), categoryQMenu);
+ List<Quest> quests = new ArrayList<>();
+ for (String questid : category.getRegisteredQuestIds()) {
+ Quest quest = plugin.getQuestManager().getQuestById(questid);
+ if (quest != null) {
+ quests.add(quest);
+ }
+ }
+ questQMenu.populate(quests);
+ questMenus.add(questQMenu);
+ }
+ categoryQMenu.populate(questMenus);
+
+ plugin.getMenuController().openMenu(player, categoryQMenu, 1);
+ } else {
+ QuestQMenu questQMenu = new QuestQMenu(plugin, qPlayer, "", null);
+ List<Quest> quests = new ArrayList<>();
+ for (Map.Entry<String, Quest> entry : plugin.getQuestManager().getQuests().entrySet()) {
+ quests.add(entry.getValue());
+ }
+ questQMenu.populate(quests);
+ questQMenu.setBackButtonEnabled(false);
+
+ plugin.getMenuController().openMenu(player, questQMenu, 1);
+ }
+ }
+// } else {
+// DailyQMenu dailyQMenu = new DailyQMenu(plugin, this);
+// dailyQMenu.populate();
+// plugin.getMenuController().openMenu(player, dailyQMenu, 1);
+// }
+ }
+
+ /**
+ * Open the started menu for the player
+ *
+ * @param qPlayer player
+ */
+ public void openStartedQuests(QPlayer qPlayer) {
+ Player player = Bukkit.getPlayer(qPlayer.getPlayerUUID());
+ if (player == null) {
+ return;
+ }
+
+ StartedQMenu startedQMenu = new StartedQMenu(plugin, qPlayer);
+ List<QuestSortWrapper> quests = new ArrayList<>();
+ for (Map.Entry<String, Quest> entry : plugin.getQuestManager().getQuests().entrySet()) {
+ quests.add(new QuestSortWrapper(plugin, entry.getValue()));
+ }
+ startedQMenu.populate(quests);
+
+ plugin.getMenuController().openMenu(player, startedQMenu, 1);
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/QMenu.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/QMenu.java
new file mode 100644
index 00000000..0e3c9f78
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/QMenu.java
@@ -0,0 +1,13 @@
+package com.leonardobishop.quests.bukkit.menu;
+
+import com.leonardobishop.quests.common.player.QPlayer;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.inventory.Inventory;
+
+public interface QMenu {
+
+ QPlayer getOwner();
+ Inventory toInventory(int page);
+ void handleClick(InventoryClickEvent event, MenuController controller);
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/QuestQMenu.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/QuestQMenu.java
new file mode 100644
index 00000000..be2125e8
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/QuestQMenu.java
@@ -0,0 +1,288 @@
+package com.leonardobishop.quests.bukkit.menu;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.config.BukkitQuestsConfig;
+import com.leonardobishop.quests.bukkit.menu.element.CustomMenuElement;
+import com.leonardobishop.quests.bukkit.menu.element.MenuElement;
+import com.leonardobishop.quests.bukkit.menu.element.QuestMenuElement;
+import com.leonardobishop.quests.bukkit.menu.element.SpacerMenuElement;
+import com.leonardobishop.quests.bukkit.util.Chat;
+import com.leonardobishop.quests.bukkit.util.MenuUtils;
+import com.leonardobishop.quests.common.enums.QuestStartResult;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import org.apache.commons.lang.math.NumberUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents a menu for a specified category (or all if they are disabled),
+ * which contains a listing of different quests.
+ */
+public class QuestQMenu implements QMenu {
+
+ private final BukkitQuestsPlugin plugin;
+ private final BukkitQuestsConfig config;
+ private final HashMap<Integer, MenuElement> menuElements = new HashMap<>();
+ private final CategoryQMenu superMenu;
+ private final String categoryName;
+ private final int pageSize = 45;
+ private final QPlayer owner;
+
+ private int maxElement = 0;
+ private int backButtonLocation = -1;
+ private int pagePrevLocation = -1;
+ private int pageNextLocation = -1;
+ private int currentPage = -1;
+ private boolean backButtonEnabled = true;
+
+ public QuestQMenu(BukkitQuestsPlugin plugin, QPlayer owner, String categoryName, CategoryQMenu superMenu) {
+ this.plugin = plugin;
+ this.config = (BukkitQuestsConfig) plugin.getQuestsConfig();
+ this.owner = owner;
+ this.categoryName = categoryName;
+ this.superMenu = superMenu;
+ }
+
+ public void populate(List<Quest> quests) {
+ String path;
+ if (config.getBoolean("options.categories-enabled")) {
+ path = "custom-elements.c:" + categoryName;
+ } else {
+ path = "custom-elements.quests";
+ }
+ if (plugin.getConfig().isConfigurationSection(path)) {
+ for (String s : plugin.getConfig().getConfigurationSection(path).getKeys(false)) {
+ if (!NumberUtils.isNumber(s)) continue;
+ int slot = Integer.parseInt(s);
+ int repeat = plugin.getConfig().getInt(path + "." + s + ".repeat");
+ MenuElement menuElement;
+ if (plugin.getConfig().contains(path + "." + s + ".display")) {
+ ItemStack is = plugin.getItemStack(path + "." + s + ".display", plugin.getConfig());
+ menuElement = new CustomMenuElement(is);
+ } else if (plugin.getConfig().getBoolean(path + "." + s + ".spacer", false)) {
+ menuElement = new SpacerMenuElement();
+ } else continue; // user = idiot
+
+ for (int i = 0; i <= repeat; i++) {
+ menuElements.put(slot + i, menuElement);
+ }
+ }
+ }
+
+ Collections.sort(quests);
+ int slot = 0;
+ for (Quest quest : quests) {
+ while (menuElements.containsKey(slot)) slot++;
+ if (config.getBoolean("options.gui-hide-locked")) {
+ QuestProgress questProgress = owner.getQuestProgressFile().getQuestProgress(quest);
+ long cooldown = owner.getQuestProgressFile().getCooldownFor(quest);
+ if (!owner.getQuestProgressFile().hasMetRequirements(quest) || (!quest.isRepeatable() && questProgress.isCompletedBefore()) || cooldown > 0) {
+ continue;
+ }
+ }
+ if (config.getBoolean("options.gui-hide-quests-nopermission") && quest.isPermissionRequired()) {
+ if (!Bukkit.getPlayer(owner.getPlayerUUID()).hasPermission("quests.quest." + quest.getId())) {
+ continue;
+ }
+ }
+ menuElements.put(slot, new QuestMenuElement(plugin, owner, quest.getId()));
+ slot++;
+ }
+
+ for (Integer integer : menuElements.keySet()) {
+ if (integer + 1 > maxElement) maxElement = integer + 1;
+ }
+ }
+
+ @Override
+ public QPlayer getOwner() {
+ return owner;
+ }
+
+ public String getCategoryName() {
+ return categoryName;
+ }
+
+ public int getPagePrevLocation() {
+ return pagePrevLocation;
+ }
+
+ public int getPageNextLocation() {
+ return pageNextLocation;
+ }
+
+ public int getCurrentPage() {
+ return currentPage;
+ }
+
+ public int getPageSize() {
+ return pageSize;
+ }
+
+ public Inventory toInventory(int page) {
+ currentPage = page;
+ int pageMin = pageSize * (page - 1);
+ int pageMax = pageSize * page;
+ String title = Chat.color(config.getString("options.guinames.quests-menu"));
+
+ ItemStack pageIs;
+ ItemStack pagePrevIs;
+ ItemStack pageNextIs;
+ ItemStack back = config.getItem("gui.back-button");
+
+ Inventory inventory = Bukkit.createInventory(null, 54, title);
+
+ int highestOnPage = 0;
+ for (int pointer = pageMin; pointer < pageMax; pointer++) {
+ if (menuElements.containsKey(pointer)) {
+ inventory.setItem(pointer - ((page - 1) * pageSize), menuElements.get(pointer).asItemStack());
+ if (pointer + 1 > highestOnPage) highestOnPage = pointer + 1;
+ }
+ }
+
+ pageNextLocation = -1;
+ pagePrevLocation = -1;
+
+ Map<String, String> pageplaceholders = new HashMap<>();
+ pageplaceholders.put("{prevpage}", String.valueOf(page - 1));
+ pageplaceholders.put("{nextpage}", String.valueOf(page + 1));
+ pageplaceholders.put("{page}", String.valueOf(page));
+ pageIs = replaceItemStack(config.getItem("gui.page-description"), pageplaceholders);
+ pageIs.setAmount(Math.min(page, 64));
+ pagePrevIs = replaceItemStack(config.getItem("gui.page-prev"), pageplaceholders);
+ pageNextIs = replaceItemStack(config.getItem("gui.page-next"), pageplaceholders);
+
+ if (config.getBoolean("options.categories-enabled") && backButtonEnabled) {
+ inventory.setItem(45, back);
+ backButtonLocation = 45;
+ }
+ if (maxElement > pageSize) {
+ inventory.setItem(49, pageIs);
+ if (page != 1) {
+ inventory.setItem(48, pagePrevIs);
+ pagePrevLocation = 48;
+ }
+ if (Math.ceil((double) maxElement / ((double) pageSize)) != page) {
+ inventory.setItem(50, pageNextIs);
+ pageNextLocation = 50;
+ }
+ } else if (config.getBoolean("options.trim-gui-size") && page == 1) {
+ int inventorySize = highestOnPage + (9 - highestOnPage % 9) * Math.min(1, highestOnPage % 9);
+ inventorySize = inventorySize <= 0 ? 9 : inventorySize;
+ if (inventorySize == 54) {
+ return inventory;
+ } else if (config.getBoolean("options.categories-enabled") && backButtonEnabled) {
+ inventorySize += 9;
+ }
+
+ Inventory trimmedInventory = Bukkit.createInventory(null, inventorySize, title);
+
+ for (int slot = 0; slot < trimmedInventory.getSize(); slot++) {
+ if (slot >= (trimmedInventory.getSize() - 9) && backButtonEnabled){
+ if (config.getBoolean("options.categories-enabled")) {
+ trimmedInventory.setItem(slot, back);
+ backButtonLocation = slot;
+ }
+ break;
+ }
+ trimmedInventory.setItem(slot, inventory.getItem(slot));
+ }
+ return trimmedInventory;
+ }
+
+ return inventory;
+ }
+
+ @Override
+ public void handleClick(InventoryClickEvent event, MenuController controller) {
+ //TODO maybe redo this maybe
+ if (pagePrevLocation == event.getSlot()) {
+ controller.openMenu(event.getWhoClicked(), this, currentPage - 1);
+
+ } else if (pageNextLocation == event.getSlot()) {
+ controller.openMenu(event.getWhoClicked(), this, currentPage + 1);
+
+ } else if (config.getBoolean("options.categories-enabled") && backButtonLocation == event.getSlot()) {
+ controller.openMenu(event.getWhoClicked(), superMenu, 1);
+
+ } else if (event.getSlot() < pageSize && menuElements.containsKey(event.getSlot() + (((currentPage) - 1) * pageSize))) {
+ MenuElement menuElement = menuElements.get(event.getSlot() + ((currentPage - 1) * pageSize));
+ if (menuElement instanceof QuestMenuElement) {
+ QuestMenuElement questMenuElement = (QuestMenuElement) menuElement;
+ Quest quest = plugin.getQuestManager().getQuestById(questMenuElement.getQuestId());
+ if (event.getClick() == ClickType.LEFT) {
+ if (config.getBoolean("options.quest-autostart")) return;
+ if (owner.startQuest(quest) == QuestStartResult.QUEST_SUCCESS) {
+ event.getWhoClicked().closeInventory(); //TODO Option to keep the menu open
+ }
+ } else if (event.getClick() == ClickType.MIDDLE && config.getBoolean("options.quest-autostart")) {
+ MenuUtils.handleMiddleClick(plugin, this, quest, Bukkit.getPlayer(owner.getPlayerUUID()), controller);
+ } else if (event.getClick() == ClickType.RIGHT && config.getBoolean("options.allow-quest-cancel")
+ && owner.hasStartedQuest(quest)) {
+ MenuUtils.handleRightClick(plugin, this, quest, Bukkit.getPlayer(owner.getPlayerUUID()), controller);
+ }
+ }
+ }
+ }
+
+ public boolean isBackButtonEnabled() {
+ return backButtonEnabled;
+ }
+
+ public void setBackButtonEnabled(boolean backButtonEnabled) {
+ this.backButtonEnabled = backButtonEnabled;
+ }
+
+ public int getBackButtonLocation() {
+ return backButtonLocation;
+ }
+
+ public CategoryQMenu getSuperMenu() {
+ return superMenu;
+ }
+
+ public ItemStack replaceItemStack(ItemStack is) {
+ return replaceItemStack(is, Collections.emptyMap());
+ }
+
+ public ItemStack replaceItemStack(ItemStack is, Map<String, String> placeholders) {
+ ItemStack newItemStack = is.clone();
+ List<String> lore = newItemStack.getItemMeta().getLore();
+ List<String> newLore = new ArrayList<>();
+ ItemMeta ism = newItemStack.getItemMeta();
+ Player player = Bukkit.getPlayer(owner.getPlayerUUID());
+ if (lore != null) {
+ for (String s : lore) {
+ for (Map.Entry<String, String> entry : placeholders.entrySet()) {
+ s = s.replace(entry.getKey(), entry.getValue());
+ if (plugin.getPlaceholderAPIHook() != null && plugin.getQuestsConfig().getBoolean("options.gui-use-placeholderapi")) {
+ s = plugin.getPlaceholderAPIHook().replacePlaceholders(player, s);
+ }
+ }
+ newLore.add(s);
+ }
+ }
+ for (Map.Entry<String, String> entry : placeholders.entrySet()) {
+ ism.setDisplayName(ism.getDisplayName().replace(entry.getKey(), entry.getValue()));
+ if (plugin.getPlaceholderAPIHook() != null && plugin.getQuestsConfig().getBoolean("options.gui-use-placeholderapi")) {
+ ism.setDisplayName(plugin.getPlaceholderAPIHook().replacePlaceholders(player, ism.getDisplayName()));
+ }
+ }
+ ism.setLore(newLore);
+ newItemStack.setItemMeta(ism);
+ return newItemStack;
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/QuestSortWrapper.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/QuestSortWrapper.java
new file mode 100644
index 00000000..31d4f214
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/QuestSortWrapper.java
@@ -0,0 +1,44 @@
+package com.leonardobishop.quests.bukkit.menu;
+
+import com.leonardobishop.quests.common.plugin.Quests;
+import com.leonardobishop.quests.common.quest.Category;
+import com.leonardobishop.quests.common.quest.Quest;
+
+public class QuestSortWrapper implements Comparable<QuestSortWrapper> {
+
+ private int weightedSortOrder;
+ private final Quest quest;
+
+ public QuestSortWrapper(Quests plugin, Quest quest) {
+ this.quest = quest;
+ if (quest.getCategoryId() == null) {
+ weightedSortOrder = quest.getSortOrder();
+ return;
+ }
+ Category c = plugin.getQuestManager().getCategoryById(quest.getCategoryId());
+ if (c != null) {
+ int index = plugin.getQuestManager().getCategories().indexOf(c);
+ int amountBelow = 0;
+ //TODO precalculate
+ for (int i = index; i > 0; i--) {
+ Category below = plugin.getQuestManager().getCategories().get(i - 1);
+ amountBelow += below.getRegisteredQuestIds().size();
+ }
+ weightedSortOrder = amountBelow + quest.getSortOrder();
+ }
+ }
+
+ public int getWeightedSortOrder() {
+ return weightedSortOrder;
+ }
+
+ public Quest getQuest() {
+ return quest;
+ }
+
+ @Override
+ public int compareTo(QuestSortWrapper quest) {
+ return (weightedSortOrder - quest.weightedSortOrder);
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/StartedQMenu.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/StartedQMenu.java
new file mode 100644
index 00000000..01f99c79
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/StartedQMenu.java
@@ -0,0 +1,175 @@
+package com.leonardobishop.quests.bukkit.menu;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.config.BukkitQuestsConfig;
+import com.leonardobishop.quests.bukkit.util.Chat;
+import com.leonardobishop.quests.bukkit.util.MenuUtils;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import org.bukkit.Bukkit;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents a menu listing quests the player has started.
+ */
+public class StartedQMenu implements QMenu {
+
+ private final BukkitQuestsPlugin plugin;
+ private final BukkitQuestsConfig config;
+ private final HashMap<Integer, String> slotsToQuestIds = new HashMap<>();
+ private final int pageSize = 45;
+ private final QPlayer owner;
+
+ private int pagePrevLocation = -1;
+ private int pageNextLocation = -1;
+ private int currentPage = -1;
+
+ public StartedQMenu(BukkitQuestsPlugin plugin, QPlayer owner) {
+ this.plugin = plugin;
+ this.config = (BukkitQuestsConfig) plugin.getQuestsConfig();
+ this.owner = owner;
+ }
+
+ public void populate(List<QuestSortWrapper> quests) {
+ Collections.sort(quests);
+ int slot = 0;
+ for (QuestSortWrapper quest : quests) {
+ if (owner.hasStartedQuest(quest.getQuest())) {
+ slotsToQuestIds.put(slot, quest.getQuest().getId());
+ slot++;
+ }
+ }
+ }
+
+ public HashMap<Integer, String> getSlotsToMenu() {
+ return slotsToQuestIds;
+ }
+
+ @Override
+ public QPlayer getOwner() {
+ return owner;
+ }
+
+ public int getPagePrevLocation() {
+ return pagePrevLocation;
+ }
+
+ public int getPageNextLocation() {
+ return pageNextLocation;
+ }
+
+ public int getCurrentPage() {
+ return currentPage;
+ }
+
+ public int getPageSize() {
+ return pageSize;
+ }
+
+ public Inventory toInventory(int page) {
+ currentPage = page;
+ int pageMin = pageSize * (page - 1);
+ int pageMax = pageSize * page;
+ String title = Chat.color(config.getString("options.guinames.quests-started-menu"));
+
+ ItemStack pageIs;
+ ItemStack pagePrevIs;
+ ItemStack pageNextIs;
+ ItemStack none = config.getItem("gui.no-started-quests");
+
+ Inventory inventory = Bukkit.createInventory(null, 54, title);
+
+ int invSlot = 0;
+ if (!slotsToQuestIds.isEmpty()) {
+ for (int pointer = pageMin; pointer < pageMax; pointer++) {
+ if (slotsToQuestIds.containsKey(pointer)) {
+ Quest quest = plugin.getQuestManager().getQuestById(slotsToQuestIds.get(pointer));
+ QuestProgress questProgress = owner.getQuestProgressFile().getQuestProgress(quest);
+
+ inventory.setItem(invSlot, MenuUtils.applyPlaceholders(plugin, owner.getPlayerUUID(),
+ plugin.getQItemStackRegistry().getQuestItemStack(quest).toItemStack(quest, owner, questProgress)));
+ }
+ invSlot++;
+ }
+ } else {
+ inventory.setItem(4, none);
+ }
+
+ pageNextLocation = -1;
+ pagePrevLocation = -1;
+
+ Map<String, String> pageplaceholders = new HashMap<>();
+ pageplaceholders.put("{prevpage}", String.valueOf(page - 1));
+ pageplaceholders.put("{nextpage}", String.valueOf(page + 1));
+ pageplaceholders.put("{page}", String.valueOf(page));
+ pageIs = MenuUtils.applyPlaceholders(plugin, owner.getPlayerUUID(), config.getItem("gui.page-description"), pageplaceholders);
+ pageIs.setAmount(Math.min(page, 64));
+ pagePrevIs = MenuUtils.applyPlaceholders(plugin, owner.getPlayerUUID(), config.getItem("gui.page-prev"), pageplaceholders);
+ pageNextIs = MenuUtils.applyPlaceholders(plugin, owner.getPlayerUUID(), config.getItem("gui.page-next"), pageplaceholders);
+
+ if (slotsToQuestIds.size() > pageSize) {
+ inventory.setItem(49, pageIs);
+ if (page != 1) {
+ inventory.setItem(48, pagePrevIs);
+ pagePrevLocation = 48;
+ }
+ if (Math.ceil((double) slotsToQuestIds.size() / ((double) 45)) != page) {
+ inventory.setItem(50, pageNextIs);
+ pageNextLocation = 50;
+ }
+ } else if (config.getBoolean("options.trim-gui-size") && page == 1) {
+ int slotsUsed = 0;
+ for (int pointer = 0; pointer < pageMax; pointer++) {
+ if (inventory.getItem(pointer) != null) {
+ slotsUsed++;
+ }
+ }
+
+ int inventorySize = (slotsUsed >= 54) ? 54 : slotsUsed + (9 - slotsUsed % 9) * Math.min(1, slotsUsed % 9);
+ inventorySize = inventorySize <= 0 ? 9 : inventorySize;
+ if (inventorySize == 54) {
+ return inventory;
+ }
+
+ Inventory trimmedInventory = Bukkit.createInventory(null, inventorySize, title);
+
+ for (int slot = 0; slot < trimmedInventory.getSize(); slot++) {
+ trimmedInventory.setItem(slot, inventory.getItem(slot));
+ }
+ return trimmedInventory;
+ }
+
+ return inventory;
+ }
+
+ @Override
+ public void handleClick(InventoryClickEvent event, MenuController controller) {
+ if (pagePrevLocation == event.getSlot()) {
+ controller.openMenu(event.getWhoClicked(), this, currentPage - 1);
+
+ } else if (pageNextLocation == event.getSlot()) {
+ controller.openMenu(event.getWhoClicked(), this, currentPage + 1);
+
+ } else if (event.getSlot() < pageSize && slotsToQuestIds.containsKey(event.getSlot() + ((currentPage) - 1) * pageSize)) {
+
+ // repeat from above
+ String questid = slotsToQuestIds.get(event.getSlot() + (((currentPage) - 1) * pageSize));
+ Quest quest = plugin.getQuestManager().getQuestById(questid);
+ if (event.getClick() == ClickType.MIDDLE && config.getBoolean("options.allow-quest-track")) {
+ MenuUtils.handleMiddleClick(plugin, this, quest, Bukkit.getPlayer(owner.getPlayerUUID()), controller);
+ } else if (event.getClick() == ClickType.RIGHT && config.getBoolean("options.allow-quest-cancel")
+ && owner.hasStartedQuest(quest)) {
+ MenuUtils.handleRightClick(plugin, this, quest, Bukkit.getPlayer(owner.getPlayerUUID()), controller);
+ }
+ }
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/element/CategoryMenuElement.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/element/CategoryMenuElement.java
new file mode 100644
index 00000000..9aa2efb5
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/element/CategoryMenuElement.java
@@ -0,0 +1,64 @@
+package com.leonardobishop.quests.bukkit.menu.element;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.menu.QuestQMenu;
+import com.leonardobishop.quests.common.quest.Category;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+public class CategoryMenuElement extends MenuElement {
+
+ private final BukkitQuestsPlugin plugin;
+ private final UUID owner;
+ private final QuestQMenu questMenu;
+
+ public CategoryMenuElement(BukkitQuestsPlugin plugin, UUID owner, QuestQMenu questMenu) {
+ this.plugin = plugin;
+ this.owner = owner;
+ this.questMenu = questMenu;
+ }
+
+ public UUID getOwner() {
+ return owner;
+ }
+
+ public QuestQMenu getQuestMenu() {
+ return questMenu;
+ }
+
+ @Override
+ public ItemStack asItemStack() {
+ Category category = plugin.getQuestManager().getCategoryById(questMenu.getCategoryName());
+ if (category != null) {
+ return replaceItemStack(plugin.getQItemStackRegistry().getCategoryItemStack(category));
+ }
+ return null;
+ }
+
+ private ItemStack replaceItemStack(ItemStack is) {
+ if (plugin.getPlaceholderAPIHook() != null && plugin.getQuestsConfig().getBoolean("options.gui-use-placeholderapi")) {
+ ItemStack newItemStack = is.clone();
+ List<String> lore = newItemStack.getItemMeta().getLore();
+ List<String> newLore = new ArrayList<>();
+ ItemMeta ism = newItemStack.getItemMeta();
+ Player player = Bukkit.getPlayer(owner);
+ ism.setDisplayName(plugin.getPlaceholderAPIHook().replacePlaceholders(player, ism.getDisplayName()));
+ if (lore != null) {
+ for (String s : lore) {
+ s = plugin.getPlaceholderAPIHook().replacePlaceholders(player, s);
+ newLore.add(s);
+ }
+ }
+ ism.setLore(newLore);
+ newItemStack.setItemMeta(ism);
+ return newItemStack;
+ }
+ return is;
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/element/CustomMenuElement.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/element/CustomMenuElement.java
new file mode 100644
index 00000000..0eee9e92
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/element/CustomMenuElement.java
@@ -0,0 +1,17 @@
+package com.leonardobishop.quests.bukkit.menu.element;
+
+import org.bukkit.inventory.ItemStack;
+
+public class CustomMenuElement extends MenuElement{
+
+ private ItemStack itemStack;
+
+ public CustomMenuElement(ItemStack itemStack) {
+ this.itemStack = itemStack;
+ }
+
+ @Override
+ public ItemStack asItemStack() {
+ return itemStack;
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/element/MenuElement.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/element/MenuElement.java
new file mode 100644
index 00000000..aa7ed46d
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/element/MenuElement.java
@@ -0,0 +1,9 @@
+package com.leonardobishop.quests.bukkit.menu.element;
+
+import org.bukkit.inventory.ItemStack;
+
+public abstract class MenuElement {
+
+ public abstract ItemStack asItemStack();
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/element/QuestMenuElement.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/element/QuestMenuElement.java
new file mode 100644
index 00000000..215a93a1
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/element/QuestMenuElement.java
@@ -0,0 +1,120 @@
+package com.leonardobishop.quests.bukkit.menu.element;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.config.BukkitQuestsConfig;
+import com.leonardobishop.quests.bukkit.menu.itemstack.QItemStack;
+import com.leonardobishop.quests.bukkit.util.Chat;
+import com.leonardobishop.quests.common.enums.QuestStartResult;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.util.Format;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+public class QuestMenuElement extends MenuElement {
+
+ private final BukkitQuestsPlugin plugin;
+ private final BukkitQuestsConfig config;
+ private final QPlayer owner;
+ private final String questId;
+
+ public QuestMenuElement(BukkitQuestsPlugin plugin, QPlayer owner, String questId) {
+ this.plugin = plugin;
+ this.config = (BukkitQuestsConfig) plugin.getQuestsConfig();
+ this.owner = owner;
+ this.questId = questId;
+ }
+
+ public QPlayer getOwner() {
+ return owner;
+ }
+
+ public String getQuestId() {
+ return questId;
+ }
+
+ @Override
+ public ItemStack asItemStack() {
+ Quest quest = plugin.getQuestManager().getQuestById(questId);
+ QuestProgress questProgress = owner.getQuestProgressFile().getQuestProgress(quest);
+ QuestStartResult status = owner.canStartQuest(quest);
+ long cooldown = owner.getQuestProgressFile().getCooldownFor(quest);
+ QItemStack qItemStack = plugin.getQItemStackRegistry().getQuestItemStack(quest);
+
+ if (status == QuestStartResult.QUEST_LOCKED) {
+ List<String> quests = new ArrayList<>();
+ for (String requirement : quest.getRequirements()) {
+ Quest requirementQuest = plugin.getQuestManager().getQuestById(requirement);
+ if (!owner.getQuestProgressFile().hasQuestProgress(requirementQuest) ||
+ !owner.getQuestProgressFile().getQuestProgress(requirementQuest).isCompletedBefore()) {
+ quests.add(Chat.strip(plugin.getQItemStackRegistry().getQuestItemStack(requirementQuest).getName()));
+ }
+ }
+ Map<String, String> placeholders = new HashMap<>();
+ placeholders.put("{quest}", Chat.strip(qItemStack.getName()));
+ placeholders.put("{requirements}", String.join(", ", quests));
+ ItemStack is = replaceItemStack(config.getItem("gui.quest-locked-display"), placeholders);
+ return is;
+ } else if (status == QuestStartResult.QUEST_ALREADY_COMPLETED) {
+ Map<String, String> placeholders = new HashMap<>();
+ placeholders.put("{quest}", Chat.strip(qItemStack.getName()));
+ ItemStack is = replaceItemStack(config.getItem("gui.quest-completed-display"), placeholders);
+ return is;
+ } else if (status == QuestStartResult.QUEST_NO_PERMISSION) {
+ Map<String, String> placeholders = new HashMap<>();
+ placeholders.put("{quest}", Chat.strip(qItemStack.getName()));
+ ItemStack is = replaceItemStack(config.getItem("gui.quest-permission-display"), placeholders);
+ return is;
+ } else if (cooldown > 0) {
+ Map<String, String> placeholders = new HashMap<>();
+ placeholders.put("{time}", Format.formatTime(TimeUnit.SECONDS.convert(cooldown, TimeUnit.MILLISECONDS)));
+ placeholders.put("{quest}", Chat.strip(qItemStack.getName()));
+ ItemStack is = replaceItemStack(config.getItem("gui.quest-cooldown-display"), placeholders);
+ return is;
+ } else {
+ return replaceItemStack(qItemStack.toItemStack(quest, owner, questProgress));
+ }
+ }
+
+ private ItemStack replaceItemStack(ItemStack is) {
+ return replaceItemStack(is, Collections.emptyMap());
+ }
+
+ private ItemStack replaceItemStack(ItemStack is, Map<String, String> placeholders) {
+ ItemStack newItemStack = is.clone();
+ List<String> lore = newItemStack.getItemMeta().getLore();
+ List<String> newLore = new ArrayList<>();
+ ItemMeta ism = newItemStack.getItemMeta();
+ Player player = Bukkit.getPlayer(owner.getPlayerUUID());
+ if (lore != null) {
+ for (String s : lore) {
+ for (Map.Entry<String, String> entry : placeholders.entrySet()) {
+ s = s.replace(entry.getKey(), entry.getValue());
+ if (plugin.getPlaceholderAPIHook() != null && plugin.getQuestsConfig().getBoolean("options.gui-use-placeholderapi")) {
+ s = plugin.getPlaceholderAPIHook().replacePlaceholders(player, s);
+ }
+ }
+ newLore.add(s);
+ }
+ }
+ for (Map.Entry<String, String> entry : placeholders.entrySet()) {
+ ism.setDisplayName(ism.getDisplayName().replace(entry.getKey(), entry.getValue()));
+ if (plugin.getPlaceholderAPIHook() != null && plugin.getQuestsConfig().getBoolean("options.gui-use-placeholderapi")) {
+ ism.setDisplayName(plugin.getPlaceholderAPIHook().replacePlaceholders(player, ism.getDisplayName()));
+ }
+ }
+ ism.setLore(newLore);
+ newItemStack.setItemMeta(ism);
+ return newItemStack;
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/element/SpacerMenuElement.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/element/SpacerMenuElement.java
new file mode 100644
index 00000000..a4c5d69a
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/element/SpacerMenuElement.java
@@ -0,0 +1,15 @@
+package com.leonardobishop.quests.bukkit.menu.element;
+
+import org.bukkit.Material;
+import org.bukkit.inventory.ItemStack;
+
+/**
+ * literally has the sole purpose of returning Material.AIR
+ */
+public class SpacerMenuElement extends MenuElement {
+
+ @Override
+ public ItemStack asItemStack() {
+ return new ItemStack(Material.AIR);
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/itemstack/QItemStack.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/itemstack/QItemStack.java
new file mode 100644
index 00000000..52059b29
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/itemstack/QItemStack.java
@@ -0,0 +1,149 @@
+package com.leonardobishop.quests.bukkit.menu.itemstack;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.util.Chat;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import org.bukkit.Bukkit;
+import org.bukkit.enchantments.Enchantment;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemFlag;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class QItemStack {
+
+ private final BukkitQuestsPlugin plugin;
+
+ private String name;
+ private List<String> loreNormal;
+ private List<String> loreStarted;
+ private final List<String> globalLoreAppendNormal;
+ private final List<String> globalLoreAppendNotStarted;
+ private final List<String> globalLoreAppendStarted;
+ private final List<String> globalLoreAppendTracked;
+ private ItemStack startingItemStack;
+
+ public QItemStack(BukkitQuestsPlugin plugin, String name, List<String> loreNormal, List<String> loreStarted, ItemStack startingItemStack) {
+ this.plugin = plugin;
+ this.name = name;
+ this.loreNormal = loreNormal;
+ this.loreStarted = loreStarted;
+ this.startingItemStack = startingItemStack;
+
+ this.globalLoreAppendNormal = Chat.color(plugin.getQuestsConfig().getStringList("global-quest-display.lore.append-normal"));
+ this.globalLoreAppendNotStarted = Chat.color(plugin.getQuestsConfig().getStringList("global-quest-display.lore.append-not-started"));
+ this.globalLoreAppendStarted = Chat.color(plugin.getQuestsConfig().getStringList("global-quest-display.lore.append-started"));
+ this.globalLoreAppendTracked = Chat.color(plugin.getQuestsConfig().getStringList("global-quest-display.lore.append-tracked"));
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public List<String> getLoreNormal() {
+ return loreNormal;
+ }
+
+ public void setLoreNormal(List<String> loreNormal) {
+ this.loreNormal = loreNormal;
+ }
+
+ public List<String> getLoreStarted() {
+ return loreStarted;
+ }
+
+ public void setLoreStarted(List<String> loreStarted) {
+ this.loreStarted = loreStarted;
+ }
+
+ public ItemStack getStartingItemStack() {
+ return startingItemStack;
+ }
+
+ public void setStartingItemStack(ItemStack startingItemStack) {
+ this.startingItemStack = startingItemStack;
+ }
+
+ @SuppressWarnings("deprecation")
+ public ItemStack toItemStack(Quest quest, QPlayer qPlayer, QuestProgress questProgress) {
+ ItemStack is = new ItemStack(startingItemStack);
+ ItemMeta ism = is.getItemMeta();
+ ism.setDisplayName(name);
+ List<String> formattedLore = new ArrayList<>();
+ List<String> tempLore = new ArrayList<>();
+
+ if (!plugin.getQuestsConfig().getBoolean("options.global-task-configuration-override") || globalLoreAppendNormal.isEmpty()) {
+ tempLore.addAll(loreNormal);
+ }
+ tempLore.addAll(globalLoreAppendNormal);
+
+ Player player = Bukkit.getPlayer(qPlayer.getPlayerUUID());
+ if (qPlayer.hasStartedQuest(quest)) {
+ boolean tracked = quest.getId().equals(qPlayer.getPlayerPreferences().getTrackedQuestId());
+ if (!plugin.getQuestsConfig().getBoolean("options.global-task-configuration-override")|| globalLoreAppendStarted.isEmpty()) {
+ tempLore.addAll(loreStarted);
+ }
+ if (tracked) {
+ tempLore.addAll(globalLoreAppendTracked);
+ } else {
+ tempLore.addAll(globalLoreAppendStarted);
+ }
+ ism.addEnchant(Enchantment.ARROW_INFINITE, 1, true);
+ try {
+ ism.addItemFlags(ItemFlag.HIDE_ENCHANTS);
+ ism.addItemFlags(ItemFlag.HIDE_ATTRIBUTES);
+ } catch (Exception ignored) {
+
+ }
+ } else {
+ tempLore.addAll(globalLoreAppendNotStarted);
+ }
+ if (plugin.getPlaceholderAPIHook() != null && plugin.getQuestsConfig().getBoolean("options.gui-use-placeholderapi")) {
+ ism.setDisplayName(plugin.getPlaceholderAPIHook().replacePlaceholders(player, ism.getDisplayName()));
+ }
+ if (questProgress != null) {
+ for (String s : tempLore) {
+ s = processPlaceholders(s, questProgress);
+ if (plugin.getPlaceholderAPIHook() != null && plugin.getQuestsConfig().getBoolean("options.gui-use-placeholderapi")) {
+ s = plugin.getPlaceholderAPIHook().replacePlaceholders(player, s);
+ }
+ formattedLore.add(s);
+ }
+ }
+ ism.setLore(formattedLore);
+ is.setItemMeta(ism);
+ return is;
+ }
+
+ public static String processPlaceholders(String s, QuestProgress questProgress) {
+ Matcher m = Pattern.compile("\\{([^}]+)}").matcher(s);
+ while (m.find()) {
+ String[] parts = m.group(1).split(":");
+ if (parts.length > 1) {
+ if (questProgress.getTaskProgress(parts[0]) == null) {
+ continue;
+ }
+ if (parts[1].equals("progress")) {
+ String str = String.valueOf(questProgress.getTaskProgress(parts[0]).getProgress());
+ s = s.replace("{" + m.group(1) + "}", (str.equals("null") ? String.valueOf(0) : str));
+ }
+ if (parts[1].equals("complete")) {
+ String str = String.valueOf(questProgress.getTaskProgress(parts[0]).isCompleted());
+ s = s.replace("{" + m.group(1) + "}", str);
+ }
+ }
+ }
+ return s;
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/itemstack/QItemStackRegistry.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/itemstack/QItemStackRegistry.java
new file mode 100644
index 00000000..703b8d21
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/menu/itemstack/QItemStackRegistry.java
@@ -0,0 +1,36 @@
+package com.leonardobishop.quests.bukkit.menu.itemstack;
+
+import com.leonardobishop.quests.common.quest.Category;
+import com.leonardobishop.quests.common.quest.Quest;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class QItemStackRegistry {
+
+ private final Map<String, QItemStack> questRegistry = new HashMap<>();
+ private final Map<String, ItemStack> categoryRegistry = new HashMap<>();
+
+ public QItemStack getQuestItemStack(Quest quest) {
+ return questRegistry.get(quest.getId());
+ }
+
+ public ItemStack getCategoryItemStack(Category category) {
+ return categoryRegistry.get(category.getId());
+ }
+
+ public void clearRegistry() {
+ questRegistry.clear();
+ categoryRegistry.clear();
+ }
+
+ public void register(Quest quest, QItemStack qItemStack) {
+ questRegistry.put(quest.getId(), qItemStack);
+ }
+
+ public void register(Category quest, ItemStack itemStack) {
+ categoryRegistry.put(quest.getId(), itemStack);
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/questcompleter/BukkitQuestCompleter.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/questcompleter/BukkitQuestCompleter.java
new file mode 100644
index 00000000..32365a17
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/questcompleter/BukkitQuestCompleter.java
@@ -0,0 +1,106 @@
+package com.leonardobishop.quests.bukkit.questcompleter;
+
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgressFile;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.QuestCompleter;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+
+import java.util.LinkedList;
+import java.util.Queue;
+
+//TODO move complete effects here ?
+public class BukkitQuestCompleter implements QuestCompleter, Runnable {
+
+ private final Queue<QuestProgress> completionQueue = new LinkedList<>();
+ private final Queue<QuestProgressFile> fullCheckQueue = new LinkedList<>();
+ private final BukkitQuestsPlugin plugin;
+
+ public BukkitQuestCompleter(BukkitQuestsPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void run() {
+ this.processCompletionQueue();
+ this.processFullCheckQueue();
+ }
+
+ private void processCompletionQueue() {
+ QuestProgress questProgress = completionQueue.poll();
+ if (questProgress == null) return;
+
+ Player player = Bukkit.getPlayer(questProgress.getPlayer());
+ if (player != null && player.isOnline()) {
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) return;
+
+ plugin.getQuestsLogger().debug("Processing player (singular: " + questProgress.getQuestId() + ") " + qPlayer.getPlayerUUID());
+ Quest quest = plugin.getQuestManager().getQuestById(questProgress.getQuestId());
+
+ if (!qPlayer.hasStartedQuest(quest)) return;
+
+ if (checkComplete(quest, questProgress)) {
+ qPlayer.completeQuest(quest);
+ }
+ }
+ }
+
+ private void processFullCheckQueue() {
+ QuestProgressFile questProgressFile = fullCheckQueue.poll();
+ if (questProgressFile == null) return;
+
+ Player player = Bukkit.getPlayer(questProgressFile.getPlayerUUID());
+ if (player != null && player.isOnline()) {
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) return;
+ plugin.getQuestsLogger().debug("Processing player (full check) " + qPlayer.getPlayerUUID());
+ for (QuestProgress questProgress : questProgressFile.getAllQuestProgress()) {
+ Quest quest = plugin.getQuestManager().getQuestById(questProgress.getQuestId());
+ if (quest == null) continue;
+ if (!qPlayer.hasStartedQuest(quest)) continue;
+
+ boolean complete = true;
+ for (Task task : quest.getTasks()) {
+ TaskProgress taskProgress;
+ if ((taskProgress = questProgress.getTaskProgress(task.getId())) == null || !taskProgress.isCompleted()) {
+ complete = false;
+ break;
+ }
+ }
+ if (complete) {
+ qPlayer.completeQuest(quest);
+ }
+ }
+ }
+ }
+
+ private boolean checkComplete(Quest quest, QuestProgress questProgress) {
+ boolean complete = true;
+ for (Task task : quest.getTasks()) {
+ TaskProgress taskProgress;
+ if ((taskProgress = questProgress.getTaskProgress(task.getId())) == null || !taskProgress.isCompleted()) {
+ complete = false;
+ break;
+ }
+ }
+
+ return complete;
+ }
+
+ @Override
+ public void queueSingular(QuestProgress questProgress) {
+ completionQueue.add(questProgress);
+ }
+
+ @Override
+ public void queueFullCheck(QuestProgressFile questProgressFile) {
+ fullCheckQueue.add(questProgressFile);
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/questcontroller/DailyQuestController.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/questcontroller/DailyQuestController.java
new file mode 100644
index 00000000..c6e103bc
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/questcontroller/DailyQuestController.java
@@ -0,0 +1,184 @@
+//package com.leonardobishop.quests.bukkit.questcontroller;
+//
+//import com.leonardobishop.quests.common.enums.QuestStartResult;
+//import com.leonardobishop.quests.common.player.QPlayer;
+//import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+//import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+//import com.leonardobishop.quests.common.plugin.Quests;
+//import com.leonardobishop.quests.common.quest.Quest;
+//import com.leonardobishop.quests.common.questcontroller.QuestController;
+//import org.bukkit.Bukkit;
+//import org.bukkit.ChatColor;
+//import org.bukkit.entity.Player;
+//import org.bukkit.scheduler.BukkitRunnable;
+//import org.bukkit.scheduler.BukkitTask;
+//
+//import java.util.ArrayList;
+//import java.util.List;
+//import java.util.Random;
+//import java.util.stream.Collectors;
+//
+////TODO finish this
+//public class DailyQuestController implements QuestController {
+//
+// private int refreshTaskId = -1;
+// private long refreshTime;
+// private long startTime;
+// private Quests plugin;
+// private Random random;
+// private List<String> quests;
+//
+// public DailyQuestController(Quests plugin) {
+// this.plugin = plugin;
+// refreshDailyQuests();
+// scheduleNewTask();
+// }
+//
+// public void cancel() {
+// Bukkit.getScheduler().cancelTask(refreshTaskId);
+// }
+//
+// public List<String> getQuests() {
+// return quests;
+// }
+//
+// private void scheduleNewTask() {
+// long diff = refreshTime - System.currentTimeMillis();
+// BukkitTask refreshTask;
+// if (diff <= 10000) { //10 sec
+// refreshTask = new DailyQuestRefreshTask(true).runTaskTimer(plugin, 1L, 1L);
+// plugin.getQuestsLogger().debug("DailyQuestRefreshTask set repeating (diff=" + diff + ")");
+// } else {
+// long sleepTime = diff >> 6;
+// plugin.getQuestsLogger().debug("DailyQuestRefreshTask slept for " + sleepTime + " ticks (diff=" + diff + ")");
+// refreshTask = new DailyQuestRefreshTask(false).runTaskLater(plugin, sleepTime);
+// }
+// refreshTaskId = refreshTask.getTaskId();
+// }
+//
+// private void refreshDailyQuests() {
+//// refreshTime = ((System.currentTimeMillis() / (86400000)) + 1) * 86400000;
+// refreshTime = ((System.currentTimeMillis() / (300000)) + 1) * 300000;
+// startTime = (System.currentTimeMillis() / (300000)) * 300000;
+// random = new Random(refreshTime);
+// quests = new ArrayList<>();
+//
+// List<String> questIds = new ArrayList<>(plugin.getQuestManager().getQuests().keySet());
+// for (int i = 0; i < 5; i++) {
+// int randInt = random.nextInt(questIds.size());
+// quests.add(questIds.get(randInt));
+// questIds = questIds.stream().filter(s -> !quests.contains(s)).collect(Collectors.toList());
+// }
+// }
+//
+// @Override
+// public QuestStartResult startQuestForPlayer(QPlayer qPlayer, Quest quest) {
+// if (quests.contains(quest.getId())) {
+// return QuestStartResult.QUEST_ALREADY_STARTED;
+// } else {
+// return QuestStartResult.QUEST_LIMIT_REACHED;
+// }
+// }
+//
+// @Override
+// public QuestStartResult canPlayerStartQuest(QPlayer qPlayer, Quest quest) {
+// if (!quests.contains(quest.getId())) return QuestStartResult.OTHER;
+// long completionDate = qPlayer.getQuestProgressFile().getQuestProgress(quest).getCompletionDate();
+// if (Options.QUEST_AUTOSTART.getBooleanValue()) {
+// if (completionDate > startTime && completionDate <= refreshTime) {
+// return QuestStartResult.QUEST_ALREADY_COMPLETED;
+// }
+// } else {
+// if (qPlayer.getQuestProgressFile().hasQuestProgress(quest)) {
+// QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+// if (questProgress.isStarted() && completionDate > startTime && completionDate <= refreshTime) {
+// return QuestStartResult.QUEST_ALREADY_STARTED;
+// }
+// }
+// }
+// return QuestStartResult.QUEST_SUCCESS;
+// }
+//
+// @Override
+// public boolean completeQuestForPlayer(QPlayer qPlayer, Quest quest) {
+// QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+// questProgress.setStarted(false);
+// for (TaskProgress taskProgress : questProgress.getTaskProgress()) {
+// taskProgress.setCompleted(false);
+// taskProgress.setProgress(null);
+// }
+// questProgress.setCompleted(true);
+// questProgress.setCompletedBefore(true);
+// questProgress.setCompletionDate(System.currentTimeMillis());
+//
+// boolean trackedReset = quest.getId().equals(qPlayer.getPlayerPreferences().getTrackedQuestId());
+// if (trackedReset) {
+// qPlayer.trackQuest(null);
+// }
+//
+// Player player = Bukkit.getPlayer(qPlayer.getPlayerUUID());
+// if (player != null) {
+// String questFinishMessage = Messages.QUEST_COMPLETE.getMessage().replace("{quest}", quest.getDisplayNameStripped());
+// // PlayerFinishQuestEvent -- start
+// PlayerFinishQuestEvent questFinishEvent = new PlayerFinishQuestEvent(player, qPlayer, questProgress, questFinishMessage);
+// Bukkit.getPluginManager().callEvent(questFinishEvent);
+// // PlayerFinishQuestEvent -- end
+// Bukkit.getServer().getScheduler().runTask(plugin, () -> {
+// for (String s : quest.getRewards()) {
+// Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), s.replace("{player}", player.getName())); //TODO PlaceholderAPI support
+// }
+// });
+// if (questFinishEvent.getQuestFinishMessage() != null)
+// player.sendMessage(questFinishEvent.getQuestFinishMessage());
+// if (Options.TITLES_ENABLED.getBooleanValue()) {
+// plugin.getTitleHandle().sendTitle(player, Messages.TITLE_QUEST_COMPLETE_TITLE.getMessage().replace("{quest}", quest
+// .getDisplayNameStripped()), Messages.TITLE_QUEST_COMPLETE_SUBTITLE.getMessage().replace("{quest}", quest
+// .getDisplayNameStripped()));
+// }
+// for (String s : quest.getRewardString()) {
+// player.sendMessage(ChatColor.translateAlternateColorCodes('&', s));
+// }
+// }
+// if (Options.QUEST_AUTOTRACK.getBooleanValue() && trackedReset) {
+// for (String s : quests) {
+// Quest nextQuest = plugin.getQuestManager().getQuestById(s);
+// if (nextQuest != null && canPlayerStartQuest(qPlayer, nextQuest) == QuestStartResult.QUEST_SUCCESS) {
+// qPlayer.trackQuest(nextQuest);
+// break;
+// }
+// }
+// }
+// return true;
+// }
+//
+// @Override
+// public boolean hasPlayerStartedQuest(QPlayer qPlayer, Quest quest) {
+// return canPlayerStartQuest(qPlayer, quest) == QuestStartResult.QUEST_SUCCESS;
+// }
+//
+// @Override
+// public boolean cancelQuestForPlayer(QPlayer qPlayer, Quest quest) {
+// return false;
+// }
+//
+// public class DailyQuestRefreshTask extends BukkitRunnable {
+//
+// private final boolean repeating;
+//
+// public DailyQuestRefreshTask(boolean repeating) {
+// this.repeating = repeating;
+// }
+//
+// @Override
+// public void run() {
+// if (System.currentTimeMillis() >= refreshTime) {
+// this.cancel();
+// refreshDailyQuests();
+// } else {
+// if (repeating) return;
+// this.cancel();
+// }
+// scheduleNewTask();
+// }
+// }
+//}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/questcontroller/NormalQuestController.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/questcontroller/NormalQuestController.java
new file mode 100644
index 00000000..e385ba9f
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/questcontroller/NormalQuestController.java
@@ -0,0 +1,285 @@
+package com.leonardobishop.quests.bukkit.questcontroller;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.api.event.PlayerCancelQuestEvent;
+import com.leonardobishop.quests.bukkit.api.event.PlayerFinishQuestEvent;
+import com.leonardobishop.quests.bukkit.api.event.PlayerStartQuestEvent;
+import com.leonardobishop.quests.bukkit.api.event.PlayerStartTrackQuestEvent;
+import com.leonardobishop.quests.bukkit.api.event.PlayerStopTrackQuestEvent;
+import com.leonardobishop.quests.bukkit.api.event.PreStartQuestEvent;
+import com.leonardobishop.quests.bukkit.config.BukkitQuestsConfig;
+import com.leonardobishop.quests.bukkit.menu.itemstack.QItemStack;
+import com.leonardobishop.quests.bukkit.util.Chat;
+import com.leonardobishop.quests.bukkit.util.Messages;
+import com.leonardobishop.quests.common.enums.QuestStartResult;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import com.leonardobishop.quests.common.questcontroller.QuestController;
+import com.leonardobishop.quests.common.util.Format;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+
+import java.util.concurrent.TimeUnit;
+
+public class NormalQuestController implements QuestController {
+
+ private final BukkitQuestsPlugin plugin;
+ private final BukkitQuestsConfig config;
+
+ public NormalQuestController(BukkitQuestsPlugin plugin) {
+ this.plugin = plugin;
+ this.config = (BukkitQuestsConfig) plugin.getQuestsConfig();
+ }
+
+ @Override
+ public String getName() {
+ return "normal";
+ }
+
+ @Override
+ public QuestStartResult startQuestForPlayer(QPlayer qPlayer, Quest quest) {
+ Player player = Bukkit.getPlayer(qPlayer.getPlayerUUID());
+ QuestStartResult code = canPlayerStartQuest(qPlayer, quest);
+ if (player != null) {
+ String questResultMessage = null;
+ switch (code) {
+ case QUEST_SUCCESS:
+ // This one is hacky
+ break;
+ case QUEST_LIMIT_REACHED:
+ questResultMessage = Messages.QUEST_START_LIMIT.getMessage().replace("{limit}", String.valueOf(config.getInt("options.quest-started-limit")));
+ break;
+ case QUEST_ALREADY_COMPLETED:
+ questResultMessage = Messages.QUEST_START_DISABLED.getMessage();
+ break;
+ case QUEST_COOLDOWN:
+ long cooldown = qPlayer.getQuestProgressFile().getCooldownFor(quest);
+ questResultMessage = Messages.QUEST_START_COOLDOWN.getMessage().replace("{time}", Format.formatTime(TimeUnit.SECONDS.convert
+ (cooldown, TimeUnit.MILLISECONDS)));
+ break;
+ case QUEST_LOCKED:
+ questResultMessage = Messages.QUEST_START_LOCKED.getMessage();
+ break;
+ case QUEST_ALREADY_STARTED:
+ questResultMessage = Messages.QUEST_START_STARTED.getMessage();
+ break;
+ case QUEST_NO_PERMISSION:
+ questResultMessage = Messages.QUEST_START_PERMISSION.getMessage();
+ break;
+ case NO_PERMISSION_FOR_CATEGORY:
+ questResultMessage = Messages.QUEST_CATEGORY_QUEST_PERMISSION.getMessage();
+ break;
+ }
+ // PreStartQuestEvent -- start
+ PreStartQuestEvent preStartQuestEvent = new PreStartQuestEvent(player, qPlayer, questResultMessage, code);
+ Bukkit.getPluginManager().callEvent(preStartQuestEvent);
+ // PreStartQuestEvent -- end
+ if (preStartQuestEvent.getQuestResultMessage() != null && code != QuestStartResult.QUEST_SUCCESS)
+ player.sendMessage(preStartQuestEvent.getQuestResultMessage());
+ }
+ if (code == QuestStartResult.QUEST_SUCCESS) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+ questProgress.setStarted(true);
+ for (TaskProgress taskProgress : questProgress.getTaskProgress()) {
+ taskProgress.setCompleted(false);
+ taskProgress.setProgress(null);
+ }
+ if (config.getBoolean("options.allow-quest-track")
+ && config.getBoolean("options.quest-autotrack")) {
+ qPlayer.trackQuest(quest);
+ }
+ questProgress.setCompleted(false);
+ if (player != null) {
+ QItemStack qItemStack = plugin.getQItemStackRegistry().getQuestItemStack(quest);
+ String displayName = Chat.strip(qItemStack.getName());
+ String questStartMessage = Messages.QUEST_START.getMessage().replace("{quest}", displayName);
+ // PlayerStartQuestEvent -- start
+ PlayerStartQuestEvent questStartEvent = new PlayerStartQuestEvent(player, qPlayer, questProgress, questStartMessage);
+ Bukkit.getPluginManager().callEvent(questStartEvent);
+ // PlayerStartQuestEvent -- end
+ if (questStartEvent.getQuestStartMessage() != null)
+ player.sendMessage(questStartEvent.getQuestStartMessage()); //Don't send a message if the event message is null
+ if (config.getBoolean("options.titles-enabled")) {
+ plugin.getTitleHandle().sendTitle(player, Messages.TITLE_QUEST_START_TITLE.getMessage().replace("{quest}", displayName),
+ Messages.TITLE_QUEST_START_SUBTITLE.getMessage().replace("{quest}", displayName));
+ }
+ for (String s : quest.getStartString()) {
+ player.sendMessage(ChatColor.translateAlternateColorCodes('&', s));
+ }
+ }
+ for (Task task : quest.getTasks()) {
+ try {
+ plugin.getTaskTypeManager().getTaskType(task.getType()).onStart(quest, task, qPlayer.getPlayerUUID());
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+ return code;
+ }
+
+ @Override
+ public QuestStartResult canPlayerStartQuest(QPlayer qPlayer, Quest quest) {
+ Player p = Bukkit.getPlayer(qPlayer.getPlayerUUID());
+ if (qPlayer.getQuestProgressFile().getStartedQuests().size() >= config.getInt("options.quest-started-limit") && !config.getBoolean("options.quest-autostart")) {
+ return QuestStartResult.QUEST_LIMIT_REACHED;
+ }
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+ if (!quest.isRepeatable() && questProgress.isCompletedBefore()) {
+ //if (playerUUID != null) {
+ // ???
+ //}
+ return QuestStartResult.QUEST_ALREADY_COMPLETED;
+ }
+ long cooldown = qPlayer.getQuestProgressFile().getCooldownFor(quest);
+ if (cooldown > 0) {
+ return QuestStartResult.QUEST_COOLDOWN;
+ }
+ if (!qPlayer.getQuestProgressFile().hasMetRequirements(quest)) {
+ return QuestStartResult.QUEST_LOCKED;
+ }
+ if (questProgress.isStarted()) {
+ return QuestStartResult.QUEST_ALREADY_STARTED;
+ }
+ if (quest.isPermissionRequired()) {
+ if (p != null) {
+ if (!p.hasPermission("quests.quest." + quest.getId())) {
+ return QuestStartResult.QUEST_NO_PERMISSION;
+ }
+ } else {
+ return QuestStartResult.QUEST_NO_PERMISSION;
+ }
+ }
+ if (quest.getCategoryId() != null && plugin.getQuestManager().getCategoryById(quest.getCategoryId()) != null && plugin.getQuestManager()
+ .getCategoryById(quest.getCategoryId()).isPermissionRequired()) {
+ if (p != null) {
+ if (!p.hasPermission("quests.category." + quest.getCategoryId())) {
+ return QuestStartResult.NO_PERMISSION_FOR_CATEGORY;
+ }
+ } else {
+ return QuestStartResult.NO_PERMISSION_FOR_CATEGORY;
+ }
+ }
+ return QuestStartResult.QUEST_SUCCESS;
+ }
+
+ @Override
+ public boolean completeQuestForPlayer(QPlayer qPlayer, Quest quest) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+ questProgress.setStarted(false);
+ for (TaskProgress taskProgress : questProgress.getTaskProgress()) {
+ taskProgress.setCompleted(false);
+ taskProgress.setProgress(null);
+ }
+ questProgress.setCompleted(true);
+ questProgress.setCompletedBefore(true);
+ questProgress.setCompletionDate(System.currentTimeMillis());
+ if (config.getBoolean("options.allow-quest-track") && config.getBoolean("options.quest-autotrack")
+ && !(quest.isRepeatable() && !quest.isCooldownEnabled())) {
+ qPlayer.trackQuest(null);
+ }
+ Player player = Bukkit.getPlayer(qPlayer.getPlayerUUID());
+ if (player != null) {
+ QItemStack qItemStack = plugin.getQItemStackRegistry().getQuestItemStack(quest);
+ String displayName = Chat.strip(qItemStack.getName());
+ String questFinishMessage = Messages.QUEST_COMPLETE.getMessage().replace("{quest}", displayName);
+ // PlayerFinishQuestEvent -- start
+ PlayerFinishQuestEvent questFinishEvent = new PlayerFinishQuestEvent(player, qPlayer, questProgress, questFinishMessage);
+ Bukkit.getPluginManager().callEvent(questFinishEvent);
+ // PlayerFinishQuestEvent -- end
+ Bukkit.getServer().getScheduler().runTask(plugin, () -> {
+ for (String s : quest.getRewards()) {
+ Bukkit.getServer().dispatchCommand(Bukkit.getConsoleSender(), s.replace("{player}", player.getName())); //TODO PlaceholderAPI support
+ }
+ });
+ if (questFinishEvent.getQuestFinishMessage() != null)
+ player.sendMessage(questFinishEvent.getQuestFinishMessage());
+ if (config.getBoolean("options.titles-enabled")) {
+ plugin.getTitleHandle().sendTitle(player, Messages.TITLE_QUEST_COMPLETE_TITLE.getMessage().replace("{quest}", displayName),
+ Messages.TITLE_QUEST_COMPLETE_SUBTITLE.getMessage().replace("{quest}", displayName));
+ }
+ for (String s : quest.getRewardString()) {
+ player.sendMessage(ChatColor.translateAlternateColorCodes('&', s));
+ }
+ }
+ if ((config.getBoolean("options.allow-quest-track") && config.getBoolean("options.quest-autotrack") && !(quest.isRepeatable() && !quest.isCooldownEnabled()))
+ || (!config.getBoolean("options.allow-quest-track") && config.getBoolean("options.quest-autotrack"))) {
+ Quest nextQuest;
+ if (qPlayer.getQuestProgressFile().getStartedQuests().size() > 0) {
+ nextQuest = qPlayer.getQuestProgressFile().getStartedQuests().get(0);
+ qPlayer.trackQuest(nextQuest);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean hasPlayerStartedQuest(QPlayer qPlayer, Quest quest) {
+ if (config.getBoolean("options.quest-autostart")) {
+ QuestStartResult response = canPlayerStartQuest(qPlayer, quest);
+ return response == QuestStartResult.QUEST_SUCCESS || response == QuestStartResult.QUEST_ALREADY_STARTED;
+ } else {
+ return qPlayer.getQuestProgressFile().hasQuestProgress(quest) && qPlayer.getQuestProgressFile().getQuestProgress(quest).isStarted();
+ }
+ }
+
+ @Override
+ public boolean cancelQuestForPlayer(QPlayer qPlayer, Quest quest) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+ Player player = Bukkit.getPlayer(qPlayer.getPlayerUUID());
+ if (!questProgress.isStarted()) {
+ if (player != null) {
+ player.sendMessage(Messages.QUEST_CANCEL_NOTSTARTED.getMessage());
+ }
+ return false;
+ }
+ questProgress.setStarted(false);
+ for (TaskProgress taskProgress : questProgress.getTaskProgress()) {
+ taskProgress.setProgress(null);
+ }
+ if (player != null) {
+ QItemStack qItemStack = plugin.getQItemStackRegistry().getQuestItemStack(quest);
+ String displayName = Chat.strip(qItemStack.getName());
+ String questCancelMessage = Messages.QUEST_CANCEL.getMessage().replace("{quest}", displayName);
+ // PlayerCancelQuestEvent -- start
+ PlayerCancelQuestEvent questCancelEvent = new PlayerCancelQuestEvent(player, qPlayer, questProgress, questCancelMessage);
+ Bukkit.getPluginManager().callEvent(questCancelEvent);
+ // PlayerCancelQuestEvent -- end
+ if (questCancelEvent.getQuestCancelMessage() != null)
+ player.sendMessage(questCancelEvent.getQuestCancelMessage());
+ }
+ return true;
+ }
+
+ @Override
+ public void trackQuestForPlayer(QPlayer qPlayer, Quest quest) {
+ Player player = Bukkit.getPlayer(qPlayer.getPlayerUUID());
+
+ if (quest == null) {
+ String currentTrackedQuestId = qPlayer.getPlayerPreferences().getTrackedQuestId();
+ qPlayer.getPlayerPreferences().setTrackedQuestId(null);
+ if (player != null) {
+ Bukkit.getPluginManager().callEvent(new PlayerStopTrackQuestEvent(player, qPlayer));
+ Quest currentTrackedQuest;
+ if (currentTrackedQuestId != null && (currentTrackedQuest = plugin.getQuestManager().getQuestById(currentTrackedQuestId)) != null) {
+ QItemStack qItemStack = plugin.getQItemStackRegistry().getQuestItemStack(currentTrackedQuest);
+ String displayName = Chat.strip(qItemStack.getName());
+ player.sendMessage(Messages.QUEST_TRACK_STOP.getMessage().replace("{quest}", displayName));
+ }
+ }
+ } else if (qPlayer.hasStartedQuest(quest)) {
+ QItemStack qItemStack = plugin.getQItemStackRegistry().getQuestItemStack(quest);
+ String displayName = Chat.strip(qItemStack.getName());
+ qPlayer.getPlayerPreferences().setTrackedQuestId(quest.getId());
+ if (player != null) {
+ Bukkit.getPluginManager().callEvent(new PlayerStartTrackQuestEvent(player, qPlayer));
+ player.sendMessage(Messages.QUEST_TRACK.getMessage().replace("{quest}", displayName));
+ }
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/runnable/QuestsAutoSaveRunnable.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/runnable/QuestsAutoSaveRunnable.java
new file mode 100644
index 00000000..34c1a5f6
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/runnable/QuestsAutoSaveRunnable.java
@@ -0,0 +1,42 @@
+package com.leonardobishop.quests.bukkit.runnable;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitRunnable;
+
+import java.util.LinkedList;
+import java.util.Queue;
+import java.util.UUID;
+
+public class QuestsAutoSaveRunnable extends BukkitRunnable {
+
+ private final Queue<UUID> queue = new LinkedList<>();
+ private final BukkitQuestsPlugin plugin;
+
+ public QuestsAutoSaveRunnable(BukkitQuestsPlugin plugin) {
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ queue.add(player.getUniqueId());
+ }
+
+ this.plugin = plugin;
+
+ this.runTaskTimer(plugin, 2L, 2L);
+ }
+
+ @Override
+ public void run() {
+ UUID player = queue.poll();
+ if (player == null) {
+ try {
+ super.cancel();
+ } catch (Exception ignored) {}
+ return;
+ }
+
+ if (Bukkit.getPlayer(player) != null) {
+ plugin.getPlayerManager().savePlayer(player);
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/storage/MySqlStorageProvider.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/storage/MySqlStorageProvider.java
new file mode 100644
index 00000000..fd473af6
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/storage/MySqlStorageProvider.java
@@ -0,0 +1,276 @@
+package com.leonardobishop.quests.bukkit.storage;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgressFile;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.storage.StorageProvider;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.function.Function;
+
+public class MySqlStorageProvider implements StorageProvider {
+
+ private static final String CREATE_TABLE_QUEST_PROGRESS =
+ "CREATE TABLE IF NOT EXISTS `{prefix}quest_progress` (" +
+ " `uuid` VARCHAR(36) NOT NULL," +
+ " `quest_id` VARCHAR(50) NOT NULL," +
+ " `started` BOOL NOT NULL," +
+ " `completed` BOOL NOT NULL," +
+ " `completed_before` BOOL NOT NULL," +
+ " `completion_date` BIGINT NOT NULL," +
+ " PRIMARY KEY (`uuid`, `quest_id`));";
+ private static final String CREATE_TABLE_TASK_PROGRESS =
+ "CREATE TABLE IF NOT EXISTS `{prefix}task_progress` (" +
+ " `uuid` VARCHAR(36) NOT NULL," +
+ " `quest_id` VARCHAR(50) NOT NULL," +
+ " `task_id` VARCHAR(50) NOT NULL," +
+ " `completed` BOOL NOT NULL," +
+ " `progress` VARCHAR(64) NULL," +
+ " `data_type` VARCHAR(10) NULL," +
+ " PRIMARY KEY (`uuid`, `quest_id`, `task_id`));";
+ private static final String SELECT_PLAYER_QUEST_PROGRESS =
+ "SELECT quest_id, started, completed, completed_before, completion_date FROM `{prefix}quest_progress` WHERE uuid=?;";
+ private static final String SELECT_PLAYER_TASK_PROGRESS =
+ "SELECT quest_id, task_id, completed, progress, data_type FROM `{prefix}task_progress` WHERE uuid=?;";
+ private static final String SELECT_KNOWN_PLAYER_QUEST_PROGRESS =
+ "SELECT quest_id FROM `{prefix}quest_progress` WHERE uuid=?;";
+ private static final String SELECT_KNOWN_PLAYER_TASK_PROGRESS =
+ "SELECT quest_id, task_id FROM `{prefix}task_progress` WHERE uuid=?;";
+ private static final String WRITE_PLAYER_QUEST_PROGRESS =
+ "INSERT INTO `{prefix}quest_progress` (uuid, quest_id, started, completed, completed_before, completion_date) VALUES (?,?,?,?,?,?) ON DUPLICATE KEY UPDATE started=?, completed=?, completed_before=?, completion_date=?";
+ private static final String WRITE_PLAYER_TASK_PROGRESS =
+ "INSERT INTO `{prefix}task_progress` (uuid, quest_id, task_id, completed, progress, data_type) VALUES (?,?,?,?,?,?) ON DUPLICATE KEY UPDATE completed=?, progress=?, data_type=?";
+
+ private final ConfigurationSection configuration;
+ private final BukkitQuestsPlugin plugin;
+ private HikariDataSource hikari;
+ private String prefix;
+ private Function<String, String> statementProcessor;
+ private boolean fault;
+
+ public MySqlStorageProvider(BukkitQuestsPlugin plugin, ConfigurationSection configuration) {
+ this.plugin = plugin;
+ if (configuration == null) {
+ configuration = new YamlConfiguration();
+ }
+ this.configuration = configuration;
+ }
+
+ @Override
+ public void init() {
+ String address = configuration.getString("network.address", "localhost:3306");
+ String database = configuration.getString("network.database", "minecraft");
+ String url = "jdbc:mysql://" + address + "/" + database;
+
+ HikariConfig config = new HikariConfig();
+ config.setPoolName("quests-hikari");
+
+ config.setUsername(configuration.getString("network.username", "root"));
+ config.setPassword(configuration.getString("network.password", ""));
+ config.setJdbcUrl(url);
+ config.setMaximumPoolSize(configuration.getInt("connection-pool-settings.maximum-pool-size", 8));
+ config.setMinimumIdle(configuration.getInt("connection-pool-settings.minimum-idle", 8));
+ config.setMaxLifetime(configuration.getInt("connection-pool-settings.maximum-lifetime", 1800000));
+ config.setConnectionTimeout(configuration.getInt("connection-pool-settings.connection-timeout", 5000));
+
+ config.addDataSourceProperty("cachePrepStmts", true);
+ config.addDataSourceProperty("prepStmtCacheSize", 250);
+ config.addDataSourceProperty("prepStmtCacheSqlLimit", 2048);
+ config.addDataSourceProperty("useServerPrepStmts", true);
+ config.addDataSourceProperty("useLocalSessionState", true);
+ config.addDataSourceProperty("rewriteBatchedStatements", true);
+ config.addDataSourceProperty("cacheResultSetMetadata", true);
+ config.addDataSourceProperty("cacheServerConfiguration", true);
+ config.addDataSourceProperty("elideSetAutoCommits", true);
+ config.addDataSourceProperty("maintainTimeStats", false);
+
+ try {
+ this.hikari = new HikariDataSource(config);
+ } catch (Exception e) {
+ e.printStackTrace();
+ fault = true;
+ }
+ this.prefix = configuration.getString("database-settings.table-prefix", "quests_");
+ this.statementProcessor = s -> s.replace("{prefix}", prefix);
+ try (Connection connection = hikari.getConnection()) {
+ try (Statement s = connection.createStatement()) {
+ plugin.getQuestsLogger().debug("Creating default tables");
+ s.addBatch(this.statementProcessor.apply(CREATE_TABLE_QUEST_PROGRESS));
+ s.addBatch(this.statementProcessor.apply(CREATE_TABLE_TASK_PROGRESS));
+
+ s.executeBatch();
+ }
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ }
+
+ @Override
+ public void shutdown() {
+ if (hikari != null) hikari.close();
+ }
+
+ @Override
+ public QuestProgressFile loadProgressFile(UUID uuid) {
+ if (fault) return null;
+ QuestProgressFile questProgressFile = new QuestProgressFile(uuid, plugin);
+ try (Connection connection = hikari.getConnection()) {
+ plugin.getQuestsLogger().debug("Querying player " + uuid);
+ Map<String, QuestProgress> questProgressMap = new HashMap<>();
+ try (PreparedStatement ps = connection.prepareStatement(this.statementProcessor.apply(SELECT_PLAYER_QUEST_PROGRESS))) {
+ ps.setString(1, uuid.toString());
+
+ try (ResultSet rs = ps.executeQuery()) {
+ while (rs.next()) {
+ String questId = rs.getString(1);
+ boolean started = rs.getBoolean(2);
+ boolean completed = rs.getBoolean(3);
+ boolean completedBefore = rs.getBoolean(4);
+ long completionDate = rs.getLong(5);
+
+ QuestProgress questProgress = new QuestProgress(plugin, questId, completed, completedBefore, completionDate, uuid, started);
+ questProgressMap.put(questId, questProgress);
+ }
+ }
+ }
+ try (PreparedStatement ps = connection.prepareStatement(this.statementProcessor.apply(SELECT_PLAYER_TASK_PROGRESS))) {
+ ps.setString(1, uuid.toString());
+
+ try (ResultSet rs = ps.executeQuery()) {
+ while (rs.next()) {
+ String questId = rs.getString(1);
+ String taskId = rs.getString(2);
+ boolean completed = rs.getBoolean(3);
+ String encodedProgress = rs.getString(4);
+ String type = rs.getString(5);
+ Object progress;
+ try {
+ if (type == null) {
+ progress = null;
+ } else if (type.equals("double")) {
+ progress = Double.valueOf(encodedProgress);
+ } else if (type.equals("float")) {
+ progress = Float.valueOf(encodedProgress);
+ } else if (type.equals("int")) {
+ progress = Integer.valueOf(encodedProgress);
+ } else {
+ throw new RuntimeException("unknown data type '" + type + "'");
+ }
+ } catch (NumberFormatException ex) {
+ plugin.getQuestsLogger().warning("Cannot retrieve progress for task '"
+ + taskId + "' in quest '" + questId + "' for player " + uuid
+ + " since data is malformed!");
+ continue;
+ } catch (RuntimeException ex) {
+ if (ex.getMessage().startsWith("unknown data type ")) {
+ plugin.getQuestsLogger().warning("Cannot retrieve progress for task '"
+ + taskId + "' in quest '" + questId + "' for player " + uuid
+ + ": " + ex.getMessage());
+ continue;
+ } else {
+ throw ex;
+ }
+ }
+
+ QuestProgress linkedQuestProgress = questProgressMap.get(questId);
+ if (linkedQuestProgress == null) continue; // lost quest progress ?
+ TaskProgress questProgress = new TaskProgress(linkedQuestProgress, taskId, progress, uuid, completed);
+ linkedQuestProgress.addTaskProgress(questProgress);
+ }
+ }
+ }
+ for (QuestProgress questProgress : questProgressMap.values()) {
+ questProgressFile.addQuestProgress(questProgress);
+ }
+ } catch (SQLException e) {
+ plugin.getQuestsLogger().severe("Failed to load player: " + uuid + "!");
+ e.printStackTrace();
+ return null;
+ }
+ return questProgressFile;
+ }
+
+ @Override
+ public void saveProgressFile(UUID uuid, QuestProgressFile questProgressFile) {
+ if (fault) return;
+ try (Connection connection = hikari.getConnection()) {
+ try (PreparedStatement writeQuestProgress = connection.prepareStatement(this.statementProcessor.apply(WRITE_PLAYER_QUEST_PROGRESS));
+ PreparedStatement writeTaskProgress = connection.prepareStatement(this.statementProcessor.apply(WRITE_PLAYER_TASK_PROGRESS))) {
+
+ List<QuestProgress> questProgressValues = new ArrayList<>(questProgressFile.getAllQuestProgress());
+ for (QuestProgress questProgress : questProgressValues) {
+ if (!questProgress.isModified()) continue;
+
+ String questId = questProgress.getQuestId();
+ writeQuestProgress.setString(1, uuid.toString());
+ writeQuestProgress.setString(2, questProgress.getQuestId());
+ writeQuestProgress.setBoolean(3, questProgress.isStarted());
+ writeQuestProgress.setBoolean(4, questProgress.isCompleted());
+ writeQuestProgress.setBoolean(5, questProgress.isCompletedBefore());
+ writeQuestProgress.setLong(6, questProgress.getCompletionDate());
+ writeQuestProgress.setBoolean(7, questProgress.isStarted());
+ writeQuestProgress.setBoolean(8, questProgress.isCompleted());
+ writeQuestProgress.setBoolean(9, questProgress.isCompletedBefore());
+ writeQuestProgress.setLong(10, questProgress.getCompletionDate());
+ writeQuestProgress.addBatch();
+
+ for (TaskProgress taskProgress : questProgress.getTaskProgress()) {
+ String taskId = taskProgress.getTaskId();
+
+ String encodedProgress;
+ Object progress = taskProgress.getProgress();
+ String type;
+ if (progress == null) {
+ type = null;
+ encodedProgress = null;
+ } else if (progress instanceof Double) {
+ type = "double";
+ encodedProgress = String.valueOf(progress);
+ } else if (progress instanceof Integer) {
+ type = "int";
+ encodedProgress = String.valueOf(progress);
+ } else if (progress instanceof Float) {
+ type = "float";
+ encodedProgress = String.valueOf(progress);
+ } else {
+ plugin.getQuestsLogger().warning("Cannot store progress for task '"
+ + taskId + "' in quest '" + questId + "' for player " + uuid
+ + " since type " + progress.getClass().getName() + " cannot be encoded!");
+ continue;
+ }
+ writeTaskProgress.setString(1, uuid.toString());
+ writeTaskProgress.setString(2, questId);
+ writeTaskProgress.setString(3, taskProgress.getTaskId());
+ writeTaskProgress.setBoolean(4, taskProgress.isCompleted());
+ writeTaskProgress.setString(5, encodedProgress);
+ writeTaskProgress.setString(6, type);
+ writeTaskProgress.setBoolean(7, taskProgress.isCompleted());
+ writeTaskProgress.setString(8, encodedProgress);
+ writeTaskProgress.setString(9, type);
+ writeTaskProgress.addBatch();
+ }
+ }
+
+ writeQuestProgress.executeBatch();
+ writeTaskProgress.executeBatch();
+ }
+ } catch (SQLException e) {
+ plugin.getQuestsLogger().severe("Failed to save player: " + uuid + "!");
+ e.printStackTrace();
+ }
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/storage/YamlStorageProvider.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/storage/YamlStorageProvider.java
new file mode 100644
index 00000000..75e271f9
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/storage/YamlStorageProvider.java
@@ -0,0 +1,138 @@
+package com.leonardobishop.quests.bukkit.storage;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgressFile;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.storage.StorageProvider;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReentrantLock;
+
+public class YamlStorageProvider implements StorageProvider {
+
+ private final Map<UUID, ReentrantLock> locks = new ConcurrentHashMap<>();
+ private BukkitQuestsPlugin plugin;
+
+ public YamlStorageProvider(BukkitQuestsPlugin plugin) {
+ this.plugin = plugin;
+ }
+
+ private ReentrantLock lock(UUID uuid) {
+ locks.putIfAbsent(uuid, new ReentrantLock());
+ ReentrantLock lock = locks.get(uuid);
+ lock.lock();
+ return lock;
+ }
+
+ @Override
+ public void init() {
+ File directory = new File(plugin.getDataFolder() + File.separator + "playerdata");
+ directory.mkdirs();
+ }
+
+ @Override
+ public void shutdown() {
+ // no impl
+ }
+
+ public QuestProgressFile loadProgressFile(UUID uuid) {
+ ReentrantLock lock = lock(uuid);
+ QuestProgressFile questProgressFile = new QuestProgressFile(uuid, plugin);
+ try {
+ File directory = new File(plugin.getDataFolder() + File.separator + "playerdata");
+ if (directory.exists() && directory.isDirectory()) {
+ File file = new File(plugin.getDataFolder() + File.separator + "playerdata" + File.separator + uuid.toString() + ".yml");
+ if (file.exists()) {
+ YamlConfiguration data = YamlConfiguration.loadConfiguration(file);
+ plugin.getQuestsLogger().debug("Player " + uuid + " has a valid quest progress file.");
+ if (data.isConfigurationSection("quest-progress")) { //Same job as "isSet" + it checks if is CfgSection
+ for (String id : data.getConfigurationSection("quest-progress").getKeys(false)) {
+ boolean started = data.getBoolean("quest-progress." + id + ".started");
+ boolean completed = data.getBoolean("quest-progress." + id + ".completed");
+ boolean completedBefore = data.getBoolean("quest-progress." + id + ".completed-before");
+ long completionDate = data.getLong("quest-progress." + id + ".completion-date");
+
+ QuestProgress questProgress = new QuestProgress(plugin, id, completed, completedBefore, completionDate, uuid, started, true);
+
+ if (data.isConfigurationSection("quest-progress." + id + ".task-progress")) {
+ for (String taskid : data.getConfigurationSection("quest-progress." + id + ".task-progress").getKeys(false)) {
+ boolean taskCompleted = data.getBoolean("quest-progress." + id + ".task-progress." + taskid + ".completed");
+ Object taskProgression = data.get("quest-progress." + id + ".task-progress." + taskid + ".progress");
+
+ TaskProgress taskProgress = new TaskProgress(questProgress, taskid, taskProgression, uuid, taskCompleted, false);
+ questProgress.addTaskProgress(taskProgress);
+ }
+ }
+
+ questProgressFile.addQuestProgress(questProgress);
+ }
+ }
+ } else {
+ plugin.getQuestsLogger().debug("Player " + uuid + " does not have a quest progress file.");
+ }
+ }
+ } catch (Exception ex) {
+ plugin.getQuestsLogger().severe("Failed to load player: " + uuid + "! This WILL cause errors.");
+ ex.printStackTrace();
+ // fuck
+ } finally {
+ lock.unlock();
+ }
+
+ return questProgressFile;
+ }
+
+ public void saveProgressFile(UUID uuid, QuestProgressFile questProgressFile) {
+ ReentrantLock lock = lock(uuid);
+ try {
+ List<QuestProgress> questProgressValues = new ArrayList<>(questProgressFile.getAllQuestProgress());
+ File directory = new File(plugin.getDataFolder() + File.separator + "playerdata");
+ if (!directory.exists() && !directory.isDirectory()) {
+ directory.mkdirs();
+ }
+
+ File file = new File(plugin.getDataFolder() + File.separator + "playerdata" + File.separator + uuid.toString() + ".yml");
+ if (!file.exists()) {
+ try {
+ file.createNewFile();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ YamlConfiguration data = YamlConfiguration.loadConfiguration(file);
+ for (QuestProgress questProgress : questProgressValues) {
+ if (!questProgress.isModified()) continue;
+ data.set("quest-progress." + questProgress.getQuestId() + ".started", questProgress.isStarted());
+ data.set("quest-progress." + questProgress.getQuestId() + ".completed", questProgress.isCompleted());
+ data.set("quest-progress." + questProgress.getQuestId() + ".completed-before", questProgress.isCompletedBefore());
+ data.set("quest-progress." + questProgress.getQuestId() + ".completion-date", questProgress.getCompletionDate());
+ for (TaskProgress taskProgress : questProgress.getTaskProgress()) {
+ data.set("quest-progress." + questProgress.getQuestId() + ".task-progress." + taskProgress.getTaskId() + ".completed", taskProgress
+ .isCompleted());
+ data.set("quest-progress." + questProgress.getQuestId() + ".task-progress." + taskProgress.getTaskId() + ".progress", taskProgress
+ .getProgress());
+ }
+ }
+
+ plugin.getQuestsLogger().debug("Writing player " + uuid + " to disk.");
+ try {
+ data.save(file);
+ plugin.getQuestsLogger().debug("Write of player " + uuid + " to disk complete.");
+ } catch (IOException e) {
+ plugin.getQuestsLogger().debug("Failed to write player: " + uuid + "!.");
+ e.printStackTrace();
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/BukkitTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/BukkitTaskType.java
new file mode 100644
index 00000000..81f312c7
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/BukkitTaskType.java
@@ -0,0 +1,16 @@
+package com.leonardobishop.quests.bukkit.tasktype;
+
+import com.leonardobishop.quests.common.tasktype.TaskType;
+import org.bukkit.event.Listener;
+
+public abstract class BukkitTaskType extends TaskType implements Listener {
+
+ public BukkitTaskType(String type, String author, String description) {
+ super(type, author, description);
+ }
+
+ public BukkitTaskType(String type) {
+ super(type);
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/BukkitTaskTypeManager.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/BukkitTaskTypeManager.java
new file mode 100644
index 00000000..67c9ab7d
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/BukkitTaskTypeManager.java
@@ -0,0 +1,25 @@
+package com.leonardobishop.quests.bukkit.tasktype;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.common.tasktype.TaskType;
+import com.leonardobishop.quests.common.tasktype.TaskTypeManager;
+
+public class BukkitTaskTypeManager extends TaskTypeManager {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public BukkitTaskTypeManager(BukkitQuestsPlugin plugin) {
+ super(plugin);
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void registerTaskType(TaskType taskType) {
+ if (!(taskType instanceof BukkitTaskType)) throw new RuntimeException("task type must be instance of BukkitTaskType!");
+
+ BukkitTaskType bukkitTaskType = (BukkitTaskType) taskType;
+ super.registerTaskType(taskType);
+ plugin.getServer().getPluginManager().registerEvents(bukkitTaskType, plugin);
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/BreedingTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/BreedingTaskType.java
new file mode 100644
index 00000000..c4d05eed
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/BreedingTaskType.java
@@ -0,0 +1,94 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.entity.CreatureSpawnEvent;
+import org.bukkit.event.entity.CreatureSpawnEvent.SpawnReason;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class BreedingTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public BreedingTaskType(BukkitQuestsPlugin plugin) {
+ super("breeding", TaskUtils.TASK_ATTRIBUTION_STRING, "Breed a set amount of animals.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, true, "amount");
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onBreed(CreatureSpawnEvent e) {
+ if (!e.getSpawnReason().equals(SpawnReason.BREEDING)) {
+ return;
+ }
+
+ Entity ent = e.getEntity();
+ List<Entity> entList = ent.getNearbyEntities(10, 10, 10);
+
+ if (entList.isEmpty()) {
+ return;
+ }
+ // Check if there is a player in the list, otherwise: return.
+ for (Entity current : entList) {
+ if (current instanceof Player && !current.hasMetadata("NPC")) {
+ Player player = (Player) current;
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) {
+ continue;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(player, task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ int breedingNeeded = (int) task.getConfigValue("amount");
+ int breedingProgress;
+
+ if (taskProgress.getProgress() == null) {
+ breedingProgress = 0;
+ } else {
+ breedingProgress = (int) taskProgress.getProgress();
+ }
+
+ taskProgress.setProgress(breedingProgress + 1);
+
+ if (((int) taskProgress.getProgress()) >= breedingNeeded) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/BrewingCertainTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/BrewingCertainTaskType.java
new file mode 100644
index 00000000..aeda362c
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/BrewingCertainTaskType.java
@@ -0,0 +1,114 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;// TODO: fix
+//
+//package me.fatpigsarefat.quests.quests.tasktypes.types;
+//
+//import Quests;
+//import QPlayer;
+//import QuestProgress;
+//import QuestProgressFile;
+//import TaskProgress;
+//import Quest;
+//import Task;
+//import ConfigValue;
+//import TaskType;
+//import org.bukkit.Bukkit;
+//import org.bukkit.Location;
+//import org.bukkit.Material;
+//import org.bukkit.entity.Player;
+//import org.bukkit.event.EventHandler;
+//import org.bukkit.event.EventPriority;
+//import org.bukkit.event.block.Action;
+//import org.bukkit.event.inventory.BrewEvent;
+//import org.bukkit.event.player.PlayerInteractEvent;
+//import org.bukkit.inventory.ItemStack;
+//
+//import java.util.ArrayList;
+//import java.util.HashMap;
+//import java.util.List;
+//import java.util.UUID;
+//
+//public final class BrewingCertainTaskType extends TaskType {
+//
+// private List<ConfigValue> creatorConfigValues = new ArrayList<>();
+// private HashMap<Location, UUID> brewingStands = new HashMap<>();
+//
+// public BrewingCertainTaskType() {
+// super("brewingcertain", "fatpigsarefat", "Brew a certain type of potion.");
+// this.creatorConfigValues.add(new ConfigValue("amount", true, "Amount of potions to be brewed."));
+// this.creatorConfigValues.add(new ConfigValue("potion", true, "ID of potion to be brewed."));
+// }
+//
+// @Override
+// public List<ConfigValue> getCreatorConfigValues() {
+// return creatorConfigValues;
+// }
+//
+// @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+// public void onBlockPlace(PlayerInteractEvent event) {
+// if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
+// if (event.getClickedBlock().getType() == Material.BREWING_STAND) {
+// brewingStands.put(event.getClickedBlock().getLocation(), event.getPlayer().getUniqueId());
+// }
+// }
+// }
+//
+// @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+// public void onBlockPlace(BrewEvent event) {
+// UUID uuid;
+// if ((uuid = brewingStands.get(event.getBlock().getLocation())) != null) {
+// Player player = Bukkit.getPlayer(uuid);
+//
+// if (player == null) {
+// return;
+// }
+//
+// QPlayer qPlayer = Quests.getPlayerManager().getPlayer(player.getUniqueId());
+// QuestProgressFile questProgressFile = qPlayer.getQuestProgressFile();
+//
+// for (Quest quest : super.getRegisteredQuests()) {
+// if (questProgressFile.hasStartedQuest(quest)) {
+// QuestProgress questProgress = questProgressFile.getQuestProgress(quest);
+//
+// for (Task task : quest.getTasksOfType(super.getType())) {
+// TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+//
+// if (taskProgress.isCompleted()) {
+// continue;
+// }
+//
+// int potionsNeeded = (int) task.getConfigValue("amount");
+//
+// int progress;
+// if (taskProgress.getProgress() == null) {
+// progress = 0;
+// } else {
+// progress = (int) taskProgress.getProgress();
+// }
+//
+// int potionType = (int) task.getConfigValue("potion");
+//
+// ItemStack potion1 = event.getContents().getItem(0);
+// if (potion1.getDurability() != potionType) {
+// potion1 = null;
+// }
+// ItemStack potion2 = event.getContents().getItem(1);
+// if (potion2.getDurability() != potionType) {
+// potion2 = null;
+// }
+// ItemStack potion3 = event.getContents().getItem(2);
+// if (potion3.getDurability() != potionType) {
+// potion3 = null;
+// }
+//
+// taskProgress.setProgress(progress + (potion1 == null ? 0 : 1) + (potion2 == null ? 0 : 1) + (potion3 == null ? 0 : 1));
+//
+// if (((int) taskProgress.getProgress()) >= potionsNeeded) {
+// taskProgress.setCompleted(true);
+// }
+// }
+// }
+// }
+// }
+// }
+//
+//}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/BrewingTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/BrewingTaskType.java
new file mode 100644
index 00000000..d6ede688
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/BrewingTaskType.java
@@ -0,0 +1,107 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.block.Action;
+import org.bukkit.event.inventory.BrewEvent;
+import org.bukkit.event.player.PlayerInteractEvent;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.UUID;
+
+public final class BrewingTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+ private final HashMap<Location, UUID> brewingStands = new HashMap<>();
+
+ public BrewingTaskType(BukkitQuestsPlugin plugin) {
+ super("brewing", TaskUtils.TASK_ATTRIBUTION_STRING, "Brew a potion.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, true, "amount");
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onBlockPlace(PlayerInteractEvent event) {
+ if (event.getAction() == Action.RIGHT_CLICK_BLOCK) {
+ if (event.getClickedBlock().getType() == Material.BREWING_STAND) {
+ brewingStands.put(event.getClickedBlock().getLocation(), event.getPlayer().getUniqueId());
+ }
+ }
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onBlockPlace(BrewEvent event) {
+ UUID uuid;
+ if ((uuid = brewingStands.get(event.getBlock().getLocation())) != null) {
+ Player player = Bukkit.getPlayer(uuid);
+
+ if (player == null || player.hasMetadata("NPC")) {
+ return;
+ }
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(player, task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ int potionsNeeded = (int) task.getConfigValue("amount");
+
+ int progress;
+ if (taskProgress.getProgress() == null) {
+ progress = 0;
+ } else {
+ progress = (int) taskProgress.getProgress();
+ }
+
+ ItemStack potion1 = event.getContents().getItem(0);
+ ItemStack potion2 = event.getContents().getItem(1);
+ ItemStack potion3 = event.getContents().getItem(2);
+
+ taskProgress.setProgress(progress + (potion1 == null ? 0 : 1) + (potion2 == null ? 0 : 1) + (potion3 == null ? 0 : 1));
+
+ if (((int) taskProgress.getProgress()) >= potionsNeeded) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/BuildingCertainTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/BuildingCertainTaskType.java
new file mode 100644
index 00000000..550e6435
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/BuildingCertainTaskType.java
@@ -0,0 +1,187 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.config.ConfigProblemDescriptions;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.Material;
+import org.bukkit.block.Block;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.block.BlockBreakEvent;
+import org.bukkit.event.block.BlockPlaceEvent;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class BuildingCertainTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public BuildingCertainTaskType(BukkitQuestsPlugin plugin) {
+ super("blockplacecertain", TaskUtils.TASK_ATTRIBUTION_STRING, "Place a set amount of a specific block.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, true, "amount");
+ if (config.get("block") == null && config.get("blocks") == null) {
+ TaskUtils.configValidateExists(root + ".block", config.get("block"), problems, "block", super.getType());
+ } else {
+ Object configBlock;
+ String source;
+ if (config.containsKey("block")) {
+ source = "block";
+ } else {
+ source = "blocks";
+ }
+ configBlock = config.get(source);
+ List<String> checkBlocks = new ArrayList<>();
+ if (configBlock instanceof List) {
+ checkBlocks.addAll((List) configBlock);
+ } else {
+ checkBlocks.add(String.valueOf(configBlock));
+ }
+
+ for (String materialName : checkBlocks) {
+ String[] split = materialName.split(":");
+ if (Material.getMaterial(String.valueOf(split[0])) == null) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.WARNING,
+ ConfigProblemDescriptions.UNKNOWN_MATERIAL.getDescription(materialName), root + "." + source));
+ }
+ }
+ }
+ TaskUtils.configValidateBoolean(root + ".reverse-if-broken", config.get("reverse-if-broken"), problems, true,"reverse-if-broken");
+ TaskUtils.configValidateBoolean(root + ".use-similar-blocks", config.get("use-similar-blocks"), problems, true,"use-similar-blocks");
+ TaskUtils.configValidateInt(root + ".data", config.get("data"), problems, true,true, "data");
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onBlockPlace(BlockPlaceEvent event) {
+ if (event.getPlayer().hasMetadata("NPC")) return;
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(event.getPlayer().getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(event.getPlayer(), task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ if (matchBlock(task, event.getBlock())) {
+ increment(task, taskProgress, 1);
+ }
+ }
+ }
+ }
+ }
+
+ // subtract if enabled
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onBlockBreak(BlockBreakEvent event) {
+ if (event.getPlayer().hasMetadata("NPC")) return;
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(event.getPlayer().getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ if (task.getConfigValue("reverse-if-placed") != null && ((boolean) task.getConfigValue("reverse-if-placed"))) {
+ if (matchBlock(task, event.getBlock())) {
+ increment(task, taskProgress, -1);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private boolean matchBlock(Task task, Block block) {
+ Material material;
+
+ Object configBlock = task.getConfigValues().containsKey("block") ? task.getConfigValue("block") : task.getConfigValue("blocks");
+ Object configData = task.getConfigValue("data");
+ Object configSimilarBlocks = task.getConfigValue("use-similar-blocks");
+
+ List<String> checkBlocks = new ArrayList<>();
+ if (configBlock instanceof List) {
+ checkBlocks.addAll((List) configBlock);
+ } else {
+ checkBlocks.add(String.valueOf(configBlock));
+ }
+
+ for (String materialName : checkBlocks) {
+ // LOG:1 LOG:2 LOG should all be supported with this
+ String[] split = materialName.split(":");
+ int comparableData = 0;
+ if (configData != null) {
+ comparableData = (int) configData;
+ }
+ if (split.length > 1) {
+ comparableData = Integer.parseInt(split[1]);
+ }
+
+ material = Material.getMaterial(String.valueOf(split[0]));
+ Material blockType = block.getType();
+
+ short blockData = block.getData();
+
+ if (blockType == material) {
+ if (((split.length == 1 && configData == null) || ((int) blockData) == comparableData))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void increment(Task task, TaskProgress taskProgress, int amount) {
+ int brokenBlocksNeeded = (int) task.getConfigValue("amount");
+
+ int progressBlocksBroken;
+ if (taskProgress.getProgress() == null) {
+ progressBlocksBroken = 0;
+ } else {
+ progressBlocksBroken = (int) taskProgress.getProgress();
+ }
+
+ taskProgress.setProgress(progressBlocksBroken + amount);
+
+ if (((int) taskProgress.getProgress()) >= brokenBlocksNeeded) {
+ taskProgress.setCompleted(true);
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/BuildingTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/BuildingTaskType.java
new file mode 100644
index 00000000..fe0e3ee7
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/BuildingTaskType.java
@@ -0,0 +1,78 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.block.BlockPlaceEvent;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class BuildingTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public BuildingTaskType(BukkitQuestsPlugin plugin) {
+ super("blockplace", TaskUtils.TASK_ATTRIBUTION_STRING, "Place a set amount of blocks.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, true, "amount");
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onBlockPlace(BlockPlaceEvent event) {
+ if (event.getPlayer().hasMetadata("NPC")) return;
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(event.getPlayer().getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(event.getPlayer(), task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ int brokenBlocksNeeded = (int) task.getConfigValue("amount");
+
+ int progressBlocksBroken;
+ if (taskProgress.getProgress() == null) {
+ progressBlocksBroken = 0;
+ } else {
+ progressBlocksBroken = (int) taskProgress.getProgress();
+ }
+
+ taskProgress.setProgress(progressBlocksBroken + 1);
+
+ if (((int) taskProgress.getProgress()) >= brokenBlocksNeeded) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/CommandTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/CommandTaskType.java
new file mode 100644
index 00000000..db44f8d6
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/CommandTaskType.java
@@ -0,0 +1,91 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.player.PlayerCommandPreprocessEvent;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class CommandTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public CommandTaskType(BukkitQuestsPlugin plugin) {
+ super("command", TaskUtils.TASK_ATTRIBUTION_STRING, "Execute a certain command.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ TaskUtils.configValidateExists(root + ".command", config.get("command"), problems, "command", super.getType());
+ TaskUtils.configValidateBoolean(root + ".ignore-case", config.get("ignore-case"), problems, true, "ignore-case", super.getType());
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void onCommand(PlayerCommandPreprocessEvent e) {
+ if (e.getPlayer().hasMetadata("NPC")) return;
+
+ Player player = e.getPlayer();
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(player, task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+ Object configCommand = task.getConfigValue("command");
+ Object configIgnoreCase = task.getConfigValue("ignore-case");
+
+ List<String> commands = new ArrayList<>();
+ if (configCommand instanceof List) {
+ commands.addAll((List) configCommand);
+ } else {
+ commands.add(String.valueOf(configCommand));
+ }
+
+ boolean ignoreCasing = false;
+ if (configIgnoreCase != null) {
+ ignoreCasing = (boolean) task.getConfigValue("ignore-case");
+ }
+ String message = e.getMessage();
+ if (message.length() >= 1) {
+ message = message.substring(1);
+ }
+
+ for (String command : commands) {
+ if (ignoreCasing && command.equalsIgnoreCase(message)) {
+ taskProgress.setCompleted(true);
+ } else if (!ignoreCasing && command.equals(message)) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/DealDamageTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/DealDamageTaskType.java
new file mode 100644
index 00000000..950a50ad
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/DealDamageTaskType.java
@@ -0,0 +1,86 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.entity.EntityDamageByEntityEvent;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class DealDamageTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public DealDamageTaskType(BukkitQuestsPlugin plugin) {
+ super("dealdamage", TaskUtils.TASK_ATTRIBUTION_STRING, "Deal a certain amount of damage.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, true, "amount");
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onDamage(EntityDamageByEntityEvent e) {
+ if (!(e.getDamager() instanceof Player)) {
+ return;
+ }
+
+ Player player = (Player) e.getDamager();
+ double damage = e.getDamage();
+
+ if (player.hasMetadata("NPC")) return;
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(player, task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ double progressDamage;
+ int damageNeeded = (int) task.getConfigValue("amount");
+
+ if (taskProgress.getProgress() == null) {
+ progressDamage = 0.0;
+ } else {
+ progressDamage = (double) taskProgress.getProgress();
+ }
+
+ taskProgress.setProgress(progressDamage + damage);
+
+ if (((double) taskProgress.getProgress()) >= (double) damageNeeded) {
+ taskProgress.setProgress(damageNeeded);
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/DistancefromTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/DistancefromTaskType.java
new file mode 100644
index 00000000..b0b4b9e9
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/DistancefromTaskType.java
@@ -0,0 +1,108 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.player.PlayerMoveEvent;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class DistancefromTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public DistancefromTaskType(BukkitQuestsPlugin plugin) {
+ super("distancefrom", TaskUtils.TASK_ATTRIBUTION_STRING, "Distance yourself from a set of co-ordinates.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".x", config.get("x"), problems, "x", super.getType()))
+ TaskUtils.configValidateInt(root + ".x", config.get("x"), problems, false, false, "x");
+ if (TaskUtils.configValidateExists(root + ".y", config.get("y"), problems, "y", super.getType()))
+ TaskUtils.configValidateInt(root + ".y", config.get("y"), problems, false, false, "y");
+ if (TaskUtils.configValidateExists(root + ".z", config.get("z"), problems, "z", super.getType()))
+ TaskUtils.configValidateInt(root + ".z", config.get("z"), problems, false, false, "z");
+ if (TaskUtils.configValidateExists(root + ".distance", config.get("distance"), problems, "distance", super.getType()))
+ TaskUtils.configValidateInt(root + ".distance", config.get("distance"), problems, false, true, "distance");
+ return problems;
+ }
+
+// private HashMap<String, HashMap<String, Integer>> distanceSquaredCache = new HashMap<>();
+//
+// @Override
+// public void onReady() {
+// distanceSquaredCache.clear();
+// for (Quest quest : super.getRegisteredQuests()) {
+// HashMap<String, Integer> squaredDistances = new HashMap<>();
+// for (Task task : quest.getTasksOfType(super.getType())) {
+// int distance = (int) task.getConfigValue("distance");
+// squaredDistances.put(task.getId(), distance);
+// }
+// distanceSquaredCache.put(quest.getId(), squaredDistances);
+// }
+// }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onMove(PlayerMoveEvent event) {
+ if (event.getFrom().getBlockX() == event.getTo().getBlockX() && event.getFrom().getBlockZ() == event.getTo().getBlockZ()) {
+ return;
+ }
+
+ if (event.getPlayer().hasMetadata("NPC")) return;
+ Player player = event.getPlayer();
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ int x = (int) task.getConfigValue("x");
+ int y = (int) task.getConfigValue("y");
+ int z = (int) task.getConfigValue("z");
+ String worldString = (String) task.getConfigValue("world");
+ int distance = (int) task.getConfigValue("distance");
+ int distanceSquared = distance * distance;
+
+ World world = Bukkit.getWorld(worldString);
+ if (world == null) {
+ continue;
+ }
+
+ Location location = new Location(world, x, y, z);
+ if (player.getWorld().equals(world) && player.getLocation().distanceSquared(location) > distanceSquared) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/EnchantingTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/EnchantingTaskType.java
new file mode 100644
index 00000000..5293960f
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/EnchantingTaskType.java
@@ -0,0 +1,80 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.enchantment.EnchantItemEvent;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class EnchantingTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public EnchantingTaskType(BukkitQuestsPlugin plugin) {
+ super("enchanting", TaskUtils.TASK_ATTRIBUTION_STRING, "Enchant a certain amount of items.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, true, "amount");
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onEnchant(EnchantItemEvent e) {
+ if (e.getEnchanter().hasMetadata("NPC")) return;
+
+ Player player = e.getEnchanter();
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(player, task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ int enchantsNeeded = (int) task.getConfigValue("amount");
+
+ int progressEnchant;
+ if (taskProgress.getProgress() == null) {
+ progressEnchant = 0;
+ } else {
+ progressEnchant = (int) taskProgress.getProgress();
+ }
+
+ taskProgress.setProgress(progressEnchant + 1);
+
+ if (((int) taskProgress.getProgress()) >= enchantsNeeded) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/ExpEarnTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/ExpEarnTaskType.java
new file mode 100644
index 00000000..81653d86
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/ExpEarnTaskType.java
@@ -0,0 +1,78 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.player.PlayerExpChangeEvent;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class ExpEarnTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public ExpEarnTaskType(BukkitQuestsPlugin plugin) {
+ super("expearn", TaskUtils.TASK_ATTRIBUTION_STRING, "Earn a set amount of exp.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, true, "amount");
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onExpEarn(PlayerExpChangeEvent e) {
+ if (e.getPlayer().hasMetadata("NPC")) return;
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(e.getPlayer().getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(e.getPlayer(), task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+ int amount = e.getAmount();
+ int expNeeded = (int) task.getConfigValue("amount");
+
+ int progressExp;
+ if (taskProgress.getProgress() == null) {
+ progressExp = 0;
+ } else {
+ progressExp = (int) taskProgress.getProgress();
+ }
+
+ taskProgress.setProgress(progressExp + amount);
+
+ if (((int) taskProgress.getProgress()) >= expNeeded) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/FarmingTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/FarmingTaskType.java
new file mode 100644
index 00000000..cdc92930
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/FarmingTaskType.java
@@ -0,0 +1,85 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.Material;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.block.BlockBreakEvent;
+import org.bukkit.material.Crops;
+
+public final class FarmingTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public FarmingTaskType(BukkitQuestsPlugin plugin) {
+ super("farming", TaskUtils.TASK_ATTRIBUTION_STRING, "Break a set amount of a crop.");
+ this.plugin = plugin;
+ }
+
+ @SuppressWarnings("deprecation")
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onBlockBreak(BlockBreakEvent event) {
+ //TODO: finish this
+ if (!(event.getBlock().getState() instanceof Crops)) {
+ return;
+ }
+ Crops crop = (Crops) event.getBlock().getState();
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(event.getPlayer().getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(event.getPlayer(), task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ Material material;
+ Object configBlock = task.getConfigValue("block");
+ Object configData = task.getConfigValue("data");
+
+ material = Material.matchMaterial(String.valueOf(configBlock));
+
+
+ if (material != null && event.getBlock().getType().equals(material)) {
+
+ if (configData != null && (((int) event.getBlock().getData()) != ((int) configData))) {
+ continue;
+ }
+ int brokenBlocksNeeded = (int) task.getConfigValue("amount");
+
+ int progressBlocksBroken;
+ if (taskProgress.getProgress() == null) {
+ progressBlocksBroken = 0;
+ } else {
+ progressBlocksBroken = (int) taskProgress.getProgress();
+ }
+
+ taskProgress.setProgress(progressBlocksBroken + 1);
+
+ if (((int) taskProgress.getProgress()) >= brokenBlocksNeeded) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/FishingTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/FishingTaskType.java
new file mode 100644
index 00000000..bbaa59c7
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/FishingTaskType.java
@@ -0,0 +1,90 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.player.PlayerFishEvent;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class FishingTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public FishingTaskType(BukkitQuestsPlugin plugin) {
+ super("fishing", TaskUtils.TASK_ATTRIBUTION_STRING, "Catch a set amount of items from the sea.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, true, "amount");
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onFishCaught(PlayerFishEvent event) {
+ if (event.getPlayer().hasMetadata("NPC")) return;
+
+ if (event.getState() != PlayerFishEvent.State.CAUGHT_FISH) {
+ return;
+ }
+
+// Location hookLocation = event.getHook().getLocation().add(0, -1, 0);
+// if (!(hookLocation.getBlock().getType() == Material.WATER)) {
+// return;
+// }
+
+ Player player = event.getPlayer();
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(player, task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ int catchesNeeded = (int) task.getConfigValue("amount");
+
+ int progressCatches;
+ if (taskProgress.getProgress() == null) {
+ progressCatches = 0;
+ } else {
+ progressCatches = (int) taskProgress.getProgress();
+ }
+
+ taskProgress.setProgress(progressCatches + 1);
+
+ if (((int) taskProgress.getProgress()) >= catchesNeeded) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/InventoryTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/InventoryTaskType.java
new file mode 100644
index 00000000..55df10de
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/InventoryTaskType.java
@@ -0,0 +1,168 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.config.ConfigProblemDescriptions;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.inventory.InventoryCloseEvent;
+import org.bukkit.event.player.PlayerPickupItemEvent;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class InventoryTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public InventoryTaskType(BukkitQuestsPlugin plugin) {
+ super("inventory", TaskUtils.TASK_ATTRIBUTION_STRING, "Obtain a set of items.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".item", config.get("item"), problems, "item", super.getType())) {
+ Object configBlock = config.get("item");
+ if (configBlock instanceof ConfigurationSection) {
+ ConfigurationSection section = (ConfigurationSection) configBlock;
+ String itemloc = "item";
+ if (!section.contains("item")) {
+ itemloc = "type";
+ }
+ if (!section.contains(itemloc)) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.WARNING,
+ ConfigProblemDescriptions.UNKNOWN_MATERIAL.getDescription(""), root + ".item.type"));
+ } else {
+ String type = String.valueOf(section.get(itemloc));
+ if (!plugin.getItemGetter().isValidMaterial(type)) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.WARNING,
+ ConfigProblemDescriptions.UNKNOWN_MATERIAL.getDescription(type), root + ".item." + itemloc));
+ }
+ }
+ } else {
+ if (Material.getMaterial(String.valueOf(configBlock)) == null) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.WARNING,
+ ConfigProblemDescriptions.UNKNOWN_MATERIAL.getDescription(String.valueOf(configBlock)), root + ".item.item"));
+ }
+ }
+ }
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, true, "amount");
+ TaskUtils.configValidateInt(root + ".data", config.get("data"), problems, true, false, "data");
+ TaskUtils.configValidateBoolean(root + ".remove-items-when-complete", config.get("remove-items-when-complete"), problems, true, "remove-items-when-complete", super.getType());
+ TaskUtils.configValidateBoolean(root + ".update-progress", config.get("update-progress"), problems, true, "update-progress", super.getType());
+ return problems;
+ }
+
+ @SuppressWarnings("deprecation")
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onItemPickup(PlayerPickupItemEvent event) {
+ if (event.getPlayer().hasMetadata("NPC")) return;
+
+ Bukkit.getScheduler().runTaskLater(plugin, () -> this.checkInventory(event.getPlayer()), 1L);
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR)
+ public void onInventoryClick(InventoryCloseEvent event) {
+ Bukkit.getScheduler().runTaskLater(plugin, () -> checkInventory((Player) event.getPlayer()), 1L); //Still some work to do as it doesn't really work
+ }
+
+ @SuppressWarnings("deprecation")
+ private void checkInventory(Player player) {
+ if (player == null || !player.isOnline()) {
+ return;
+ }
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(player, task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ Material material;
+ int amount = (int) task.getConfigValue("amount");
+ Object configBlock = task.getConfigValue("item");
+ Object configData = task.getConfigValue("data");
+ Object remove = task.getConfigValue("remove-items-when-complete");
+
+ ItemStack is;
+ if (configBlock instanceof ConfigurationSection) {
+ is = plugin.getItemStack("", (ConfigurationSection) configBlock);
+ } else {
+ material = Material.getMaterial(String.valueOf(configBlock));
+
+ if (material == null) {
+ continue;
+ }
+ if (configData != null) {
+ is = new ItemStack(material, 1, ((Integer) configData).shortValue());
+ } else {
+ is = new ItemStack(material, 1);
+ }
+ }
+
+ if (task.getConfigValue("update-progress") != null
+ && (Boolean) task.getConfigValue("update-progress")) {
+ int inInv = getAmount(player, is, amount);
+ if (taskProgress.getProgress() != null && (int) taskProgress.getProgress() != inInv) {
+ taskProgress.setProgress(inInv);
+ } else if (taskProgress.getProgress() == null) {
+ taskProgress.setProgress(inInv);
+ }
+ }
+
+ if (player.getInventory().containsAtLeast(is, amount)) {
+ is.setAmount(amount);
+ taskProgress.setCompleted(true);
+
+ if (remove != null && ((Boolean) remove)) {
+ player.getInventory().removeItem(is);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private int getAmount(Player player, ItemStack is, int max) {
+ if (is == null) {
+ return 0;
+ }
+ int amount = 0;
+ for (int i = 0; i < 36; i++) {
+ ItemStack slot = player.getInventory().getItem(i);
+ if (slot == null || !slot.isSimilar(is))
+ continue;
+ amount += slot.getAmount();
+ }
+ return Math.min(amount, max);
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MilkingTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MilkingTaskType.java
new file mode 100644
index 00000000..79dc975b
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MilkingTaskType.java
@@ -0,0 +1,91 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgressFile;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.Material;
+import org.bukkit.entity.Cow;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.player.PlayerInteractEntityEvent;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class MilkingTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public MilkingTaskType(BukkitQuestsPlugin plugin) {
+ super("milking", TaskUtils.TASK_ATTRIBUTION_STRING, "Milk a set amount of cows.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, true, "amount");
+ return problems;
+ }
+
+ @SuppressWarnings("deprecation")
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onMilk(PlayerInteractEntityEvent event) {
+ if (!(event.getRightClicked() instanceof Cow) || (event.getPlayer().getItemInHand().getType() != Material.BUCKET)) {
+ return;
+ }
+
+ if (event.getPlayer().hasMetadata("NPC")) return;
+
+ Player player = event.getPlayer();
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ QuestProgressFile questProgressFile = qPlayer.getQuestProgressFile();
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(player, task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ int cowsNeeded = (int) task.getConfigValue("amount");
+
+ int progressMilked;
+ if (taskProgress.getProgress() == null) {
+ progressMilked = 0;
+ } else {
+ progressMilked = (int) taskProgress.getProgress();
+ }
+
+ taskProgress.setProgress(progressMilked + 1);
+
+ if (((int) taskProgress.getProgress()) >= cowsNeeded) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MiningCertainTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MiningCertainTaskType.java
new file mode 100644
index 00000000..9c881f01
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MiningCertainTaskType.java
@@ -0,0 +1,195 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.config.ConfigProblemDescriptions;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.Material;
+import org.bukkit.block.Block;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.block.BlockBreakEvent;
+import org.bukkit.event.block.BlockPlaceEvent;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class MiningCertainTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public MiningCertainTaskType(BukkitQuestsPlugin plugin) {
+ super("blockbreakcertain", TaskUtils.TASK_ATTRIBUTION_STRING, "Break a set amount of a specific block.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, true, "amount");
+ if (config.get("block") == null && config.get("blocks") == null) {
+ TaskUtils.configValidateExists(root + ".block", config.get("block"), problems, "block", super.getType());
+ } else {
+ Object configBlock;
+ String source;
+ if (config.containsKey("block")) {
+ source = "block";
+ } else {
+ source = "blocks";
+ }
+ configBlock = config.get(source);
+ List<String> checkBlocks = new ArrayList<>();
+ if (configBlock instanceof List) {
+ checkBlocks.addAll((List) configBlock);
+ } else {
+ checkBlocks.add(String.valueOf(configBlock));
+ }
+
+ for (String materialName : checkBlocks) {
+ String[] split = materialName.split(":");
+ if (Material.getMaterial(String.valueOf(split[0])) == null) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.WARNING,
+ ConfigProblemDescriptions.UNKNOWN_MATERIAL.getDescription(materialName), root + "." + source));
+ }
+ }
+ }
+ TaskUtils.configValidateBoolean(root + ".reverse-if-broken", config.get("reverse-if-broken"), problems, true,"reverse-if-broken");
+ TaskUtils.configValidateBoolean(root + ".check-coreprotect", config.get("check-coreprotect"), problems, true,"check-coreprotect");
+ TaskUtils.configValidateInt(root + ".check-coreprotect-time", config.get("check-coreprotect-time"), problems, true,true, "check-coreprotect-time");
+ TaskUtils.configValidateBoolean(root + ".use-similar-blocks", config.get("use-similar-blocks"), problems, true,"use-similar-blocks");
+ TaskUtils.configValidateInt(root + ".data", config.get("data"), problems, true,true, "data");
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onBlockBreak(BlockBreakEvent event) {
+ if (event.getPlayer().hasMetadata("NPC")) return;
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(event.getPlayer().getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(event.getPlayer(), task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ if (matchBlock(task, event.getBlock())) {
+ boolean coreProtectEnabled = (boolean) task.getConfigValue("check-coreprotect", false);
+ int coreProtectTime = (int) task.getConfigValue("check-coreprotect-time", 3600);
+
+ if (coreProtectEnabled && plugin.getCoreProtectHook().checkBlock(event.getBlock(), coreProtectTime)) {
+ continue;
+ }
+ increment(task, taskProgress, 1);
+ }
+ }
+ }
+ }
+ }
+
+ // subtract if enabled
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onBlockPlace(BlockPlaceEvent event) {
+ if (event.getPlayer().hasMetadata("NPC")) return;
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(event.getPlayer().getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ if (task.getConfigValue("reverse-if-placed") != null && ((boolean) task.getConfigValue("reverse-if-placed"))) {
+ if (matchBlock(task, event.getBlock())) {
+ increment(task, taskProgress, -1);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @SuppressWarnings("deprecation")
+ private boolean matchBlock(Task task, Block block) {
+ Material material;
+
+ Object configBlock = task.getConfigValues().containsKey("block") ? task.getConfigValue("block") : task.getConfigValue("blocks");
+ Object configData = task.getConfigValue("data");
+ Object configSimilarBlocks = task.getConfigValue("use-similar-blocks");
+
+ List<String> checkBlocks = new ArrayList<>();
+ if (configBlock instanceof List) {
+ checkBlocks.addAll((List) configBlock);
+ } else {
+ checkBlocks.add(String.valueOf(configBlock));
+ }
+
+ for (String materialName : checkBlocks) {
+ // LOG:1 LOG:2 LOG should all be supported with this
+ String[] split = materialName.split(":");
+ int comparableData = 0;
+ if (configData != null) {
+ comparableData = (int) configData;
+ }
+ if (split.length > 1) {
+ comparableData = Integer.parseInt(split[1]);
+ }
+
+ material = Material.getMaterial(String.valueOf(split[0]));
+ Material blockType = block.getType();
+
+ short blockData = block.getData();
+
+ if (blockType == material) {
+ if (((split.length == 1 && configData == null) || ((int) blockData) == comparableData))
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void increment(Task task, TaskProgress taskProgress, int amount) {
+ int brokenBlocksNeeded = (int) task.getConfigValue("amount");
+
+ int progressBlocksBroken;
+ if (taskProgress.getProgress() == null) {
+ progressBlocksBroken = 0;
+ } else {
+ progressBlocksBroken = (int) taskProgress.getProgress();
+ }
+
+ taskProgress.setProgress(progressBlocksBroken + amount);
+
+ if (((int) taskProgress.getProgress()) >= brokenBlocksNeeded) {
+ taskProgress.setCompleted(true);
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MiningTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MiningTaskType.java
new file mode 100644
index 00000000..519cf87a
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MiningTaskType.java
@@ -0,0 +1,79 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.block.BlockBreakEvent;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class MiningTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public MiningTaskType(BukkitQuestsPlugin plugin) {
+ // type, author, description
+ super("blockbreak", TaskUtils.TASK_ATTRIBUTION_STRING, "Break a set amount of blocks.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, true, "amount");
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onBlockBreak(BlockBreakEvent event) {
+ if (event.getPlayer().hasMetadata("NPC")) return; // citizens also causes these events to fire
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(event.getPlayer().getUniqueId()); // get the qplayer so you can get their progress
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) { // iterate through all quests which are registered to use this task type
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) { // get all tasks of this type
+ if (!TaskUtils.validateWorld(event.getPlayer(), task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId()); // get the task progress and increment progress by 1
+
+ if (taskProgress.isCompleted()) { // dont need to increment a completed task
+ continue;
+ }
+
+ int brokenBlocksNeeded = (int) task.getConfigValue("amount"); // this will retrieve a value from the config under the key "value"
+
+ int progressBlocksBroken;
+ if (taskProgress.getProgress() == null) { // note: if the player has never progressed before, getProgress() will return null
+ progressBlocksBroken = 0;
+ } else {
+ progressBlocksBroken = (int) taskProgress.getProgress();
+ }
+
+ taskProgress.setProgress(progressBlocksBroken + 1); // the progress does not have to be an int, although must be serializable by the yaml provider
+
+ if (((int) taskProgress.getProgress()) >= brokenBlocksNeeded) { // completion statement, if true the task is complete
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MobkillingCertainTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MobkillingCertainTaskType.java
new file mode 100644
index 00000000..1dca3e8b
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MobkillingCertainTaskType.java
@@ -0,0 +1,137 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.config.ConfigProblemDescriptions;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.entity.EntityDeathEvent;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class MobkillingCertainTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public MobkillingCertainTaskType(BukkitQuestsPlugin plugin) {
+ super("mobkillingcertain", TaskUtils.TASK_ATTRIBUTION_STRING, "Kill a set amount of a specific entity type.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".mob", config.get("mob"), problems, "mob", super.getType())) {
+ try {
+ EntityType.valueOf(String.valueOf(config.get("mob")));
+ } catch (IllegalArgumentException ex) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.WARNING,
+ ConfigProblemDescriptions.UNKNOWN_ENTITY_TYPE.getDescription(String.valueOf(config.get("mob"))), root + ".mob"));
+ }
+ }
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, true, "amount");
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onMobKill(EntityDeathEvent event) {
+ Player killer = event.getEntity().getKiller();
+ Entity mob = event.getEntity();
+
+ if (mob == null || mob instanceof Player) {
+ return;
+ }
+
+ if (killer == null) {
+ return;
+ }
+
+ if (killer.hasMetadata("NPC")) return;
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(killer.getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(killer, task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ String configEntity = (String) task.getConfigValue("mob");
+
+ EntityType entity;
+ try {
+ entity = EntityType.valueOf(configEntity);
+ } catch (IllegalArgumentException ex) {
+ continue;
+ }
+
+ Object configName = task.getConfigValues().containsKey("name") ? task.getConfigValue("name") : task.getConfigValue("names");
+
+ if (configName != null) {
+ List<String> configNames = new ArrayList<>();
+ if (configName instanceof List) {
+ configNames.addAll((List) configName);
+ } else {
+ configNames.add(String.valueOf(configName));
+ }
+
+ boolean validName = false;
+ for (String name : configNames) {
+ name = ChatColor.translateAlternateColorCodes('&', name);
+ if (mob.getCustomName() == null || !mob.getCustomName().equals(name)) {
+ validName = true;
+ break;
+ }
+ }
+
+ if (!validName) continue;
+ }
+
+ if (mob.getType() != entity) {
+ continue;
+ }
+
+ int mobKillsNeeded = (int) task.getConfigValue("amount");
+
+ int progressKills;
+ if (taskProgress.getProgress() == null) {
+ progressKills = 0;
+ } else {
+ progressKills = (int) taskProgress.getProgress();
+ }
+
+ taskProgress.setProgress(progressKills + 1);
+
+ if (((int) taskProgress.getProgress()) >= mobKillsNeeded) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MobkillingTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MobkillingTaskType.java
new file mode 100644
index 00000000..c1dfbe2a
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/MobkillingTaskType.java
@@ -0,0 +1,109 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.entity.Animals;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Monster;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.entity.EntityDeathEvent;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class MobkillingTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public MobkillingTaskType(BukkitQuestsPlugin plugin) {
+ super("mobkilling", TaskUtils.TASK_ATTRIBUTION_STRING, "Kill a set amount of entities.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, true, "amount");
+ TaskUtils.configValidateBoolean(root + ".hostile", config.get("hostile"), problems, true, "hostile");
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onMobKill(EntityDeathEvent event) {
+ Player killer = event.getEntity().getKiller(); //The killer is a player
+ Entity mob = event.getEntity();
+
+ if (mob == null || mob instanceof Player) {
+ return;
+ }
+
+ if (killer == null) {
+ return;
+ }
+
+ if (killer.hasMetadata("NPC")) return;
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(killer.getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(killer, task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ boolean hostilitySpecified = false;
+ boolean hostile = false;
+ if (task.getConfigValue("hostile") != null) {
+ hostilitySpecified = true;
+ hostile = (boolean) task.getConfigValue("hostile");
+ }
+
+ if (hostilitySpecified) {
+ if (!hostile && !(mob instanceof Animals)) {
+ continue;
+ } else if (hostile && !(mob instanceof Monster)) {
+ continue;
+ }
+ }
+
+ int mobKillsNeeded = (int) task.getConfigValue("amount");
+
+ int progressKills;
+ if (taskProgress.getProgress() == null) {
+ progressKills = 0;
+ } else {
+ progressKills = (int) taskProgress.getProgress();
+ }
+
+ taskProgress.setProgress(progressKills + 1);
+
+ if (((int) taskProgress.getProgress()) >= mobKillsNeeded) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/PermissionTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/PermissionTaskType.java
new file mode 100644
index 00000000..33dc0db9
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/PermissionTaskType.java
@@ -0,0 +1,65 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitRunnable;
+import org.bukkit.scheduler.BukkitTask;
+
+public final class PermissionTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+ private BukkitTask poll;
+
+ public PermissionTaskType(BukkitQuestsPlugin plugin) {
+ super("permission", TaskUtils.TASK_ATTRIBUTION_STRING, "Test if a player has a permission");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public void onReady() {
+ this.poll = new BukkitRunnable() {
+ @Override
+ public void run() {
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) {
+ continue;
+ }
+ for (Quest quest : PermissionTaskType.super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+ for (Task task : quest.getTasksOfType(PermissionTaskType.super.getType())) {
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+ String permission = (String) task.getConfigValue("permission");
+ if (permission != null) {
+ if (player.hasPermission(permission)) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }.runTaskTimer(plugin, 30L, 30L);
+ }
+
+ @Override
+ public void onDisable() {
+ if (this.poll != null) {
+ this.poll.cancel();
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/PlayerkillingTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/PlayerkillingTaskType.java
new file mode 100644
index 00000000..db9fda8b
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/PlayerkillingTaskType.java
@@ -0,0 +1,91 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.entity.EntityDeathEvent;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class PlayerkillingTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public PlayerkillingTaskType(BukkitQuestsPlugin plugin) {
+ super("playerkilling", TaskUtils.TASK_ATTRIBUTION_STRING, "Kill a set amount of players.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, true, "amount");
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onMobKill(EntityDeathEvent event) {
+ Player killer = event.getEntity().getKiller();
+ Entity mob = event.getEntity();
+
+ if (!(mob instanceof Player)) {
+ return;
+ }
+
+ if (killer == null) {
+ return;
+ }
+
+ if (killer.hasMetadata("NPC")) return;
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(killer.getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(killer, task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ int playerKillsNeeded = (int) task.getConfigValue("amount");
+
+ int progressKills;
+ if (taskProgress.getProgress() == null) {
+ progressKills = 0;
+ } else {
+ progressKills = (int) taskProgress.getProgress();
+ }
+
+ taskProgress.setProgress(progressKills + 1);
+
+ if (((int) taskProgress.getProgress()) >= playerKillsNeeded) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/PlaytimeTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/PlaytimeTaskType.java
new file mode 100644
index 00000000..28ecced4
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/PlaytimeTaskType.java
@@ -0,0 +1,87 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitRunnable;
+import org.bukkit.scheduler.BukkitTask;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class PlaytimeTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+ private BukkitTask poll;
+
+ public PlaytimeTaskType(BukkitQuestsPlugin plugin) {
+ super("playtime", TaskUtils.TASK_ATTRIBUTION_STRING, "Track the amount of playing time a user has been on");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".minutes", config.get("minutes"), problems, "minutes", super.getType()))
+ TaskUtils.configValidateInt(root + ".minutes", config.get("minutes"), problems, false, true, "minutes");
+ return problems;
+ }
+
+
+ @Override
+ public void onReady() {
+ if (this.poll == null) {
+ this.poll = new BukkitRunnable() {
+ @Override
+ public void run() {
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) {
+ continue;
+ }
+
+ for (Quest quest : PlaytimeTaskType.super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+ for (Task task : quest.getTasksOfType(PlaytimeTaskType.super.getType())) {
+ if (!TaskUtils.validateWorld(player, task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+ int minutes = (int) task.getConfigValue("minutes");
+ if (taskProgress.getProgress() == null) {
+ taskProgress.setProgress(1);
+ } else {
+ taskProgress.setProgress((int) taskProgress.getProgress() + 1);
+ }
+ if (((int) taskProgress.getProgress()) >= minutes) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+ }
+ }.runTaskTimer(plugin, 1200L, 1200L);
+ }
+ }
+
+ @Override
+ public void onDisable() {
+// if (this.poll != null) {
+// this.poll.cancel();
+// }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/PositionTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/PositionTaskType.java
new file mode 100644
index 00000000..876081ae
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/PositionTaskType.java
@@ -0,0 +1,98 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.player.PlayerMoveEvent;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class PositionTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public PositionTaskType(BukkitQuestsPlugin plugin) {
+ super("position", TaskUtils.TASK_ATTRIBUTION_STRING, "Reach a set of co-ordinates.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ TaskUtils.configValidateExists(root + ".world", config.get("world"), problems, "world", super.getType());
+ if (TaskUtils.configValidateExists(root + ".x", config.get("x"), problems, "x", super.getType()))
+ TaskUtils.configValidateInt(root + ".x", config.get("x"), problems, false, false, "x");
+ if (TaskUtils.configValidateExists(root + ".y", config.get("y"), problems, "y", super.getType()))
+ TaskUtils.configValidateInt(root + ".y", config.get("y"), problems, false, false, "y");
+ if (TaskUtils.configValidateExists(root + ".z", config.get("z"), problems, "z", super.getType()))
+ TaskUtils.configValidateInt(root + ".z", config.get("z"), problems, false, false, "z");
+ TaskUtils.configValidateInt(root + ".distance-padding", config.get("distance-padding"), problems, true, true, "distance-padding");
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onMove(PlayerMoveEvent event) {
+ if (event.getFrom().getBlockX() == event.getTo().getBlockX() && event.getFrom().getBlockZ() == event.getTo().getBlockZ()) {
+ return;
+ }
+
+ if (event.getPlayer().hasMetadata("NPC")) return;
+
+ Player player = event.getPlayer();
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ int x = (int) task.getConfigValue("x");
+ int y = (int) task.getConfigValue("y");
+ int z = (int) task.getConfigValue("z");
+ String worldString = (String) task.getConfigValue("world");
+ int padding = 0;
+ if (task.getConfigValue("distance-padding") != null) {
+ padding = (int) task.getConfigValue("distance-padding");
+ }
+ int paddingSquared = padding * padding;
+ World world = Bukkit.getWorld(worldString);
+ if (world == null) {
+ continue;
+ }
+
+ Location location = new Location(world, x, y, z);
+ if (player.getWorld().equals(world) && player.getLocation().getBlockX() == location.getBlockX() && player.getLocation().getBlockY() == location.getBlockY() && player.getLocation().getBlockZ() == location.getBlockZ()) {
+ taskProgress.setCompleted(true);
+ } else if (padding != 0 && player.getWorld().equals(world) && player.getLocation().distanceSquared(location) < paddingSquared) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/ShearingTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/ShearingTaskType.java
new file mode 100644
index 00000000..6778ae69
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/ShearingTaskType.java
@@ -0,0 +1,86 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.entity.Player;
+import org.bukkit.entity.Sheep;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.player.PlayerShearEntityEvent;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class ShearingTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public ShearingTaskType(BukkitQuestsPlugin plugin) {
+ super("shearing", TaskUtils.TASK_ATTRIBUTION_STRING, "Shear a set amount of sheep.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, true, "amount");
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onShear(PlayerShearEntityEvent event) {
+ if (!(event.getEntity() instanceof Sheep)) {
+ return;
+ }
+
+ if (event.getPlayer().hasMetadata("NPC")) return;
+
+ Player player = event.getPlayer();
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(player, task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ int sheepNeeded = (int) task.getConfigValue("amount");
+
+ int progressSheared;
+ if (taskProgress.getProgress() == null) {
+ progressSheared = 0;
+ } else {
+ progressSheared = (int) taskProgress.getProgress();
+ }
+
+ taskProgress.setProgress(progressSheared + 1);
+
+ if (((int) taskProgress.getProgress()) >= sheepNeeded) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/TamingTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/TamingTaskType.java
new file mode 100644
index 00000000..7555e60c
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/TamingTaskType.java
@@ -0,0 +1,85 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.entity.EntityTameEvent;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class TamingTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public TamingTaskType(BukkitQuestsPlugin plugin) {
+ super("taming", TaskUtils.TASK_ATTRIBUTION_STRING, "Tame a set amount of animals.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, true, "amount");
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onTame(EntityTameEvent event) {
+ if (!(event.getOwner() instanceof Player)) {
+ return;
+ }
+
+ Player player = (Player) event.getOwner();
+
+ if (player.hasMetadata("NPC")) return;
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(player, task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ int tamesNeeded = (int) task.getConfigValue("amount");
+
+ int progressTamed;
+ if (taskProgress.getProgress() == null) {
+ progressTamed = 0;
+ } else {
+ progressTamed = (int) taskProgress.getProgress();
+ }
+
+ taskProgress.setProgress(progressTamed + 1);
+
+ if (((int) taskProgress.getProgress()) >= tamesNeeded) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/WalkingTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/WalkingTaskType.java
new file mode 100644
index 00000000..cc0db63d
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/WalkingTaskType.java
@@ -0,0 +1,85 @@
+package com.leonardobishop.quests.bukkit.tasktype.type;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.player.PlayerMoveEvent;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class WalkingTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public WalkingTaskType(BukkitQuestsPlugin plugin) {
+ super("walking", TaskUtils.TASK_ATTRIBUTION_STRING, "Walk a set distance.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".distance", config.get("distance"), problems, "distance", super.getType()))
+ TaskUtils.configValidateInt(root + ".distance", config.get("distance"), problems, false, true, "distance");
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onMove(PlayerMoveEvent event) {
+ if (event.getFrom().getBlockX() == event.getTo().getBlockX() && event.getFrom().getBlockZ() == event.getTo().getBlockZ()) {
+ return;
+ }
+
+ Player player = event.getPlayer();
+
+ if (player.hasMetadata("NPC")) return;
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(player, task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ int distanceNeeded = (int) task.getConfigValue("distance");
+
+ int progressDistance;
+ if (taskProgress.getProgress() == null) {
+ progressDistance = 0;
+ } else {
+ progressDistance = (int) taskProgress.getProgress();
+ }
+
+ taskProgress.setProgress(progressDistance + 1);
+
+ if (((int) taskProgress.getProgress()) >= distanceNeeded) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/ASkyBlockLevelTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/ASkyBlockLevelTaskType.java
new file mode 100644
index 00000000..3953a95f
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/ASkyBlockLevelTaskType.java
@@ -0,0 +1,66 @@
+package com.leonardobishop.quests.bukkit.tasktype.type.dependent;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import com.wasteofplastic.askyblock.events.IslandPostLevelEvent;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class ASkyBlockLevelTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public ASkyBlockLevelTaskType(BukkitQuestsPlugin plugin) {
+ super("askyblock_level", TaskUtils.TASK_ATTRIBUTION_STRING, "Reach a certain island level for ASkyBlock.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".level", config.get("level"), problems, "level", super.getType()))
+ TaskUtils.configValidateInt(root + ".level", config.get("level"), problems, false, false, "level");
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onIslandLevel(IslandPostLevelEvent event) {
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(event.getPlayer());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ long islandLevelNeeded = (long) (int) task.getConfigValue("level");
+
+ taskProgress.setProgress(event.getLongLevel());
+
+ if (((long) taskProgress.getProgress()) >= islandLevelNeeded) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/BentoBoxLevelTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/BentoBoxLevelTaskType.java
new file mode 100644
index 00000000..ba662166
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/BentoBoxLevelTaskType.java
@@ -0,0 +1,100 @@
+package com.leonardobishop.quests.bukkit.tasktype.type.dependent;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import com.leonardobishop.quests.common.tasktype.TaskTypeManager;
+import org.bukkit.event.EventHandler;
+import world.bentobox.bentobox.BentoBox;
+import world.bentobox.bentobox.api.events.BentoBoxEvent;
+import world.bentobox.bentobox.database.objects.Island;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicLong;
+
+public final class BentoBoxLevelTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+ private Field levelField = null;
+
+ public BentoBoxLevelTaskType(BukkitQuestsPlugin plugin) {
+ super("bentobox_level", TaskUtils.TASK_ATTRIBUTION_STRING, "Reach a certain island level in the level addon for BentoBox.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".level", config.get("level"), problems, "level", super.getType()))
+ TaskUtils.configValidateInt(root + ".level", config.get("level"), problems, false, false, "level");
+ return problems;
+ }
+
+
+ public static void register(BukkitQuestsPlugin plugin, TaskTypeManager manager) {
+ if (BentoBox.getInstance().getAddonsManager().getAddonByName("Level").isPresent()) {
+ manager.registerTaskType(new BentoBoxLevelTaskType(plugin));
+ }
+ }
+
+ @EventHandler
+ public void onBentoBoxIslandLevelCalculated(BentoBoxEvent event) {
+ Map<String, Object> keyValues = event.getKeyValues();
+
+ if ("IslandLevelCalculatedEvent".equalsIgnoreCase(event.getEventName())) {
+ Island island = (Island) keyValues.get("island");
+
+ for (UUID member : island.getMemberSet()) {
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(member);
+ if (qPlayer == null) {
+ continue;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ long islandLevelNeeded = (long) (int) task.getConfigValue("level");
+
+ Object results = keyValues.get("results");
+
+ try {
+ if (levelField == null) {
+ levelField = results.getClass().getDeclaredField("level");
+ levelField.setAccessible(true);
+ }
+
+ AtomicLong level = (AtomicLong) levelField.get(results);
+ taskProgress.setProgress(level.get());
+ } catch (NoSuchFieldException | IllegalAccessException e) {
+ e.printStackTrace();
+ }
+
+ if (((long) taskProgress.getProgress()) >= islandLevelNeeded) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/CitizensDeliverTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/CitizensDeliverTaskType.java
new file mode 100644
index 00000000..cc4aafa4
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/CitizensDeliverTaskType.java
@@ -0,0 +1,138 @@
+package com.leonardobishop.quests.bukkit.tasktype.type.dependent;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.config.ConfigProblemDescriptions;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import net.citizensnpcs.api.event.NPCRightClickEvent;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.Material;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.inventory.ItemStack;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class CitizensDeliverTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public CitizensDeliverTaskType(BukkitQuestsPlugin plugin) {
+ super("citizens_deliver", TaskUtils.TASK_ATTRIBUTION_STRING, "Deliver a set of items to a NPC.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".item", config.get("item"), problems, "item", super.getType())) {
+ Object configBlock = config.get("item");
+ if (configBlock instanceof ConfigurationSection) {
+ ConfigurationSection section = (ConfigurationSection) configBlock;
+ String itemloc = "item";
+ if (!section.contains("item")) {
+ itemloc = "type";
+ }
+ if (!section.contains(itemloc)) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.WARNING,
+ ConfigProblemDescriptions.UNKNOWN_MATERIAL.getDescription(""), root + ".item.type"));
+ } else {
+ String type = String.valueOf(section.get(itemloc));
+ if (!plugin.getItemGetter().isValidMaterial(type)) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.WARNING,
+ ConfigProblemDescriptions.UNKNOWN_MATERIAL.getDescription(type), root + ".item." + itemloc));
+ }
+ }
+ } else {
+ if (Material.getMaterial(String.valueOf(configBlock)) == null) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.WARNING,
+ ConfigProblemDescriptions.UNKNOWN_MATERIAL.getDescription(String.valueOf(configBlock)), root + ".item.item"));
+ }
+ }
+ }
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, true, "amount");
+ TaskUtils.configValidateExists(root + ".npc-name", config.get("npc-name"), problems, "npc-name", super.getType());
+ TaskUtils.configValidateBoolean(root + ".remove-items-when-complete", config.get("remove-items-when-complete"), problems, true, "remove-items-when-complete", super.getType());
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onNPCClick(NPCRightClickEvent event) {
+ Bukkit.getScheduler().runTaskLater(plugin, () -> checkInventory(event.getClicker(), event.getNPC().getName()), 1L);
+ }
+
+ @SuppressWarnings("deprecation")
+ private void checkInventory(Player player, String citizenName) {
+ if (player == null || !player.isOnline()) {
+ return;
+ }
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!ChatColor.stripColor(ChatColor.translateAlternateColorCodes('&', String.valueOf(task.getConfigValue("npc-name")))).equals(ChatColor
+ .stripColor(ChatColor.translateAlternateColorCodes('&', citizenName)))) {
+ return;
+ }
+ if (!TaskUtils.validateWorld(player, task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ Material material;
+ int amount = (int) task.getConfigValue("amount");
+ Object configBlock = task.getConfigValue("item");
+ Object configData = task.getConfigValue("data");
+ Object remove = task.getConfigValue("remove-items-when-complete");
+
+ ItemStack is;
+ if (configBlock instanceof ConfigurationSection) {
+ is = plugin.getItemStack("", (ConfigurationSection) configBlock);
+ } else {
+ material = Material.getMaterial(String.valueOf(configBlock));
+
+ if (material == null) {
+ continue;
+ }
+ if (configData != null) {
+ is = new ItemStack(material, 1, ((Integer) configData).shortValue());
+ } else {
+ is = new ItemStack(material, 1);
+ }
+ }
+
+ if (player.getInventory().containsAtLeast(is, amount)) {
+ is.setAmount(amount);
+ taskProgress.setCompleted(true);
+
+ if (remove != null && ((Boolean) remove)) {
+ player.getInventory().removeItem(is);
+ }
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/CitizensInteractTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/CitizensInteractTaskType.java
new file mode 100644
index 00000000..5c6e6be2
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/CitizensInteractTaskType.java
@@ -0,0 +1,68 @@
+package com.leonardobishop.quests.bukkit.tasktype.type.dependent;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import net.citizensnpcs.api.event.NPCRightClickEvent;
+import org.bukkit.ChatColor;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class CitizensInteractTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public CitizensInteractTaskType(BukkitQuestsPlugin plugin) {
+ super("citizens_interact", TaskUtils.TASK_ATTRIBUTION_STRING, "Interact with an NPC to complete the quest.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ TaskUtils.configValidateExists(root + ".npc-name", config.get("npc-name"), problems, "npc-name", super.getType());
+ TaskUtils.configValidateBoolean(root + ".remove-items-when-complete", config.get("remove-items-when-complete"), problems, true, "remove-items-when-complete", super.getType());
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onNPCClick(NPCRightClickEvent event) {
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(event.getClicker().getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(event.getClicker(), task)) continue;
+
+ if (!ChatColor.stripColor(ChatColor.translateAlternateColorCodes('&', String.valueOf(task.getConfigValue("npc-name")))).equals(ChatColor
+ .stripColor(ChatColor.translateAlternateColorCodes('&', event.getNPC().getName())))) {
+ return;
+ }
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/EssentialsBalanceTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/EssentialsBalanceTaskType.java
new file mode 100644
index 00000000..f207a499
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/EssentialsBalanceTaskType.java
@@ -0,0 +1,95 @@
+package com.leonardobishop.quests.bukkit.tasktype.type.dependent;
+
+import com.earth2me.essentials.Essentials;
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgressFile;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import net.ess3.api.events.UserBalanceUpdateEvent;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.UUID;
+
+public class EssentialsBalanceTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public EssentialsBalanceTaskType(BukkitQuestsPlugin plugin) {
+ super("essentials_balance", TaskUtils.TASK_ATTRIBUTION_STRING, "Reach a set amount of money.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, false, "amount");
+ return problems;
+ }
+
+ @Override
+ public void onStart(Quest quest, Task task, UUID playerUUID) {
+ Player player = Bukkit.getPlayer(playerUUID);
+ Essentials ess = (Essentials) Bukkit.getPluginManager().getPlugin("Essentials");
+ if (player != null && player.isOnline() && ess != null) {
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(playerUUID);
+ if (qPlayer == null) {
+ return;
+ }
+ QuestProgressFile questProgressFile = qPlayer.getQuestProgressFile();
+ QuestProgress questProgress = questProgressFile.getQuestProgress(quest);
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ int earningsNeeded = (int) task.getConfigValue("amount");
+ BigDecimal money = ess.getUser(player).getMoney();
+ taskProgress.setProgress(money);
+ if (money.compareTo(BigDecimal.valueOf(earningsNeeded)) > 0) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onMoneyEarn(UserBalanceUpdateEvent event) {
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(event.getPlayer().getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ int earningsNeeded = (int) task.getConfigValue("amount");
+
+ taskProgress.setProgress(event.getNewBalance());
+
+ if (event.getNewBalance().compareTo(BigDecimal.valueOf(earningsNeeded)) > 0) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/EssentialsMoneyEarnTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/EssentialsMoneyEarnTaskType.java
new file mode 100644
index 00000000..5f4c2a0e
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/EssentialsMoneyEarnTaskType.java
@@ -0,0 +1,73 @@
+package com.leonardobishop.quests.bukkit.tasktype.type.dependent;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import net.ess3.api.events.UserBalanceUpdateEvent;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class EssentialsMoneyEarnTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public EssentialsMoneyEarnTaskType(BukkitQuestsPlugin plugin) {
+ super("essentials_moneyearn", TaskUtils.TASK_ATTRIBUTION_STRING, "Earn a set amount of money.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, false, "amount");
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onMoneyEarn(UserBalanceUpdateEvent event) {
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(event.getPlayer().getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ int earningsNeeded = (int) task.getConfigValue("amount");
+
+ BigDecimal current = (BigDecimal) taskProgress.getProgress();
+ if (current == null) {
+ current = new BigDecimal(0);
+ }
+ BigDecimal newProgress = current.add(event.getNewBalance().subtract(event.getOldBalance()));
+ taskProgress.setProgress(newProgress);
+
+ if (newProgress.compareTo(BigDecimal.valueOf(earningsNeeded)) > 0) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/IridiumSkyblockValueType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/IridiumSkyblockValueType.java
new file mode 100644
index 00000000..35934250
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/IridiumSkyblockValueType.java
@@ -0,0 +1,87 @@
+//package com.leonardobishop.quests.bukkit.tasktype.type.dependent;
+//
+//import com.iridium.iridiumskyblock.Island;
+//import com.iridium.iridiumskyblock.api.IslandWorthCalculatedEvent;
+//import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+//import com.leonardobishop.quests.util.QuestsConfigLoader;
+//import com.leonardobishop.quests.api.QuestsAPI;
+//import com.leonardobishop.quests.player.QPlayer;
+//import com.leonardobishop.quests.player.questprogressfile.QuestProgress;
+//import com.leonardobishop.quests.player.questprogressfile.TaskProgress;
+//import com.leonardobishop.quests.quest.Quest;
+//import com.leonardobishop.quests.quest.Task;
+//import com.leonardobishop.quests.tasktype.ConfigValue;
+//import com.leonardobishop.quests.tasktype.TaskType;
+//import com.leonardobishop.quests.tasktype.TaskUtils;
+//import org.bukkit.event.EventHandler;
+//import org.bukkit.event.EventPriority;
+//
+//import java.util.ArrayList;
+//import java.util.HashMap;
+//import java.util.List;
+//import java.util.UUID;
+//
+//TODO update to latest ver
+//public final class IridiumSkyblockValueType extends BukkitTaskType {
+//
+// private List<ConfigValue> creatorConfigValues = new ArrayList<>();
+//
+// public IridiumSkyblockValueType() {
+// super("iridiumskyblock_value", TaskUtils.TASK_ATTRIBUTION_STRING, "Reach a certain island value for Iridium Skyblock.");
+// this.creatorConfigValues.add(new ConfigValue("value", true, "Minimum island value needed."));
+// }
+//
+// @Override
+// public List<QuestsConfigLoader.ConfigProblem> detectProblemsInConfig(String root, HashMap<String, Object> config) {
+// ArrayList<QuestsConfigLoader.ConfigProblem> problems = new ArrayList<>();
+// if (TaskUtils.configValidateExists(root + ".value", config.get("value"), problems, "value", super.getType()))
+// TaskUtils.configValidateInt(root + ".value", config.get("value"), problems, false, false, "value");
+// return problems;
+// }
+//
+// @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+// public void onIslandLevel(IslandWorthCalculatedEvent event) {
+// Island island = event.getIsland();
+// for (String player : island.members) {
+// UUID uuid;
+// try {
+// uuid = UUID.fromString(player);
+// } catch (Exception e) {
+// continue;
+// }
+// QPlayer qPlayer = QuestsAPI.getPlayerManager().getPlayer(uuid);
+// if (qPlayer == null) {
+// continue;
+// }
+//
+// for (Quest quest : IridiumSkyblockValueType.super.getRegisteredQuests()) {
+// if (qPlayer.hasStartedQuest(quest)) {
+// QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+//
+// for (Task task : quest.getTasksOfType(IridiumSkyblockValueType.super.getType())) {
+// TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+//
+// if (taskProgress.isCompleted()) {
+// continue;
+// }
+//
+// int islandValueNeeded = (int) task.getConfigValue("value");
+//
+// taskProgress.setProgress(event.getIslandWorth());
+//
+// if (((double) taskProgress.getProgress()) >= islandValueNeeded) {
+// taskProgress.setCompleted(true);
+// }
+// }
+// }
+// }
+// }
+//
+// }
+//
+// @Override
+// public List<ConfigValue> getCreatorConfigValues() {
+// return creatorConfigValues;
+// }
+//
+//}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/MythicMobsKillingType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/MythicMobsKillingType.java
new file mode 100644
index 00000000..1e4ede3e
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/MythicMobsKillingType.java
@@ -0,0 +1,107 @@
+package com.leonardobishop.quests.bukkit.tasktype.type.dependent;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import io.lumine.xikage.mythicmobs.api.bukkit.events.MythicMobDeathEvent;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class MythicMobsKillingType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public MythicMobsKillingType(BukkitQuestsPlugin plugin) {
+ super("mythicmobs_killing", TaskUtils.TASK_ATTRIBUTION_STRING, "Kill a set amount of a MythicMobs entity.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ TaskUtils.configValidateExists(root + ".name", config.get("name"), problems, "name", super.getType());
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, true, "amount");
+ TaskUtils.configValidateInt(root + ".level", config.get("level"), problems, true, true, "level");
+ TaskUtils.configValidateInt(root + ".min-level", config.get("min-level"), problems, true, true, "min-level");
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onMobKill(MythicMobDeathEvent event) {
+ Entity killer = event.getKiller();
+ Entity mob = event.getEntity();
+
+ if (mob == null || mob instanceof Player) {
+ return;
+ }
+
+ if (killer == null) {
+ return;
+ }
+
+ String mobName = event.getMobType().getInternalName();
+ double level = event.getMobLevel();
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(killer.getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(killer.getWorld().getName(), task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ String configName = (String) task.getConfigValue("name");
+ int minMobLevel = (int) task.getConfigValue("min-level", -1);
+ int requiredLevel = (int) task.getConfigValue("level", -1);
+
+ if (!mobName.equals(configName) || level < minMobLevel) {
+ return;
+ }
+
+ if (requiredLevel != -1 && level != requiredLevel) {
+ return;
+ }
+
+ int mobKillsNeeded = (int) task.getConfigValue("amount");
+
+ int progressKills;
+ if (taskProgress.getProgress() == null) {
+ progressKills = 0;
+ } else {
+ progressKills = (int) taskProgress.getProgress();
+ }
+
+ taskProgress.setProgress(progressKills + 1);
+
+ if (((int) taskProgress.getProgress()) >= mobKillsNeeded) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/PlaceholderAPIEvaluateTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/PlaceholderAPIEvaluateTaskType.java
new file mode 100644
index 00000000..fed42bf4
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/PlaceholderAPIEvaluateTaskType.java
@@ -0,0 +1,151 @@
+package com.leonardobishop.quests.bukkit.tasktype.type.dependent;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import me.clip.placeholderapi.PlaceholderAPI;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitRunnable;
+import org.bukkit.scheduler.BukkitTask;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class PlaceholderAPIEvaluateTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+ private BukkitTask poll;
+
+ public PlaceholderAPIEvaluateTaskType(BukkitQuestsPlugin plugin) {
+ super("placeholderapi_evaluate", TaskUtils.TASK_ATTRIBUTION_STRING, "Evaluate the result of a placeholder");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ TaskUtils.configValidateExists(root + ".placeholder", config.get("placeholder"), problems, "placeholder", super.getType());
+ boolean evalExists = TaskUtils.configValidateExists(root + ".evaluates", config.get("evaluates"), problems, "evaluates", super.getType());
+
+ if (config.containsKey("operator")) {
+ String operatorStr = (String) config.get("operator");
+ Operator operator = null;
+ try {
+ operator = Operator.valueOf(operatorStr);
+ } catch (IllegalArgumentException ex) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.WARNING,
+ "Operator '" + operatorStr + "' does not exist.", root + ".operator"));
+ }
+ if (operator != null && evalExists) {
+ String evalStr = String.valueOf(config.get("evaluates"));
+ try {
+ Double.parseDouble(evalStr);
+ } catch (IllegalArgumentException ex) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.WARNING,
+ "Numeric operator specified, but placeholder evaluation '" + evalStr + "' is not numeric.", root + ".evaluates"));
+ }
+ }
+ }
+ return problems;
+ }
+
+ @Override
+ public void onReady() {
+ this.poll = new BukkitRunnable() {
+ @Override
+ public void run() {
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) {
+ continue;
+ }
+
+ for (Quest quest : PlaceholderAPIEvaluateTaskType.super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+ for (Task task : quest.getTasksOfType(PlaceholderAPIEvaluateTaskType.super.getType())) {
+ if (!TaskUtils.validateWorld(player, task)) continue;
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+ String placeholder = (String) task.getConfigValue("placeholder");
+ String evaluates = String.valueOf(task.getConfigValue("evaluates"));
+ String configOperator = (String) task.getConfigValue("operator");
+ Operator operator = null;
+ if (configOperator != null) {
+ try {
+ operator = Operator.valueOf(configOperator);
+ } catch (IllegalArgumentException ignored) { }
+ }
+ if (placeholder != null && evaluates != null) {
+ double numericEvaluates = 0;
+ if (operator != null) {
+ try {
+ numericEvaluates = Double.parseDouble(evaluates);
+ } catch (NumberFormatException ex) {
+ continue;
+ }
+ }
+
+ String evaluated = PlaceholderAPI.setPlaceholders(player, placeholder);
+ if (operator == null && evaluated.equals(evaluates)) {
+ taskProgress.setCompleted(true);
+ } else if (operator != null) {
+ double numericEvaluated;
+ try {
+ numericEvaluated = Double.parseDouble(evaluated);
+ } catch (NumberFormatException ex) {
+ continue;
+ }
+ switch (operator) {
+ case GREATER_THAN:
+ if (numericEvaluated > numericEvaluates)
+ taskProgress.setCompleted(true);
+ continue;
+ case LESS_THAN:
+ if (numericEvaluated < numericEvaluates)
+ taskProgress.setCompleted(true);
+ continue;
+ case GREATER_THAN_OR_EQUAL_TO:
+ if (numericEvaluated >= numericEvaluates)
+ taskProgress.setCompleted(true);
+ continue;
+ case LESS_THAN_OR_EQUAL_TO:
+ if (numericEvaluated <= numericEvaluates)
+ taskProgress.setCompleted(true);
+ continue;
+ }
+ }
+ }
+
+ }
+ }
+ }
+ }
+ }
+ }.runTaskTimer(plugin, 30L, 30L);
+ }
+
+ @Override
+ public void onDisable() {
+ if (this.poll != null) {
+ this.poll.cancel();
+ }
+ }
+
+ enum Operator {
+ GREATER_THAN,
+ LESS_THAN,
+ GREATER_THAN_OR_EQUAL_TO,
+ LESS_THAN_OR_EQUAL_TO;
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/ShopGUIPlusBuyCertainTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/ShopGUIPlusBuyCertainTaskType.java
new file mode 100644
index 00000000..c7944232
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/ShopGUIPlusBuyCertainTaskType.java
@@ -0,0 +1,85 @@
+package com.leonardobishop.quests.bukkit.tasktype.type.dependent;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import net.brcdev.shopgui.event.ShopPreTransactionEvent;
+import net.brcdev.shopgui.shop.ShopManager;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class ShopGUIPlusBuyCertainTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public ShopGUIPlusBuyCertainTaskType(BukkitQuestsPlugin plugin) {
+ super("shopguiplus_buycertain", TaskUtils.TASK_ATTRIBUTION_STRING, "Purchase a given item from a ShopGUI+ shop");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, false, "amount");
+ TaskUtils.configValidateExists(root + ".id", config.get("id"), problems, "id", super.getType());
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onMobKill(ShopPreTransactionEvent event) {
+ if (event.getShopAction() != ShopManager.ShopAction.BUY) return;
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(event.getPlayer().getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(event.getPlayer().getWorld().getName(), task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ String configName = (String) task.getConfigValue("id");
+
+ if (!event.getShopItem().getId().equals(configName)) {
+ return;
+ }
+
+ int amountNeeded = (int) task.getConfigValue("amount");
+
+ int progressAmount;
+ if (taskProgress.getProgress() == null) {
+ progressAmount = 0;
+ } else {
+ progressAmount = (int) taskProgress.getProgress();
+ }
+
+ taskProgress.setProgress(progressAmount + event.getAmount());
+
+ if (((int) taskProgress.getProgress()) >= amountNeeded) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/ShopGUIPlusSellCertainTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/ShopGUIPlusSellCertainTaskType.java
new file mode 100644
index 00000000..9b2ba441
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/ShopGUIPlusSellCertainTaskType.java
@@ -0,0 +1,85 @@
+package com.leonardobishop.quests.bukkit.tasktype.type.dependent;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import net.brcdev.shopgui.event.ShopPreTransactionEvent;
+import net.brcdev.shopgui.shop.ShopManager;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public class ShopGUIPlusSellCertainTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public ShopGUIPlusSellCertainTaskType(BukkitQuestsPlugin plugin) {
+ super("shopguiplus_sellcertain", TaskUtils.TASK_ATTRIBUTION_STRING, "Sell a given item from to a ShopGUI+ shop");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".amount", config.get("amount"), problems, "amount", super.getType()))
+ TaskUtils.configValidateInt(root + ".amount", config.get("amount"), problems, false, false, "amount");
+ TaskUtils.configValidateExists(root + ".id", config.get("id"), problems, "id", super.getType());
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onMobKill(ShopPreTransactionEvent event) {
+ if (event.getShopAction() != ShopManager.ShopAction.SELL || event.getShopAction() != ShopManager.ShopAction.SELL_ALL) return;
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(event.getPlayer().getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ if (!TaskUtils.validateWorld(event.getPlayer().getWorld().getName(), task)) continue;
+
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ String configName = (String) task.getConfigValue("id");
+
+ if (!event.getShopItem().getId().equals(configName)) {
+ return;
+ }
+
+ int amountNeeded = (int) task.getConfigValue("amount");
+
+ int progressAmount;
+ if (taskProgress.getProgress() == null) {
+ progressAmount = 0;
+ } else {
+ progressAmount = (int) taskProgress.getProgress();
+ }
+
+ taskProgress.setProgress(progressAmount + event.getAmount());
+
+ if (((int) taskProgress.getProgress()) >= amountNeeded) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/uSkyBlockLevelTaskType.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/uSkyBlockLevelTaskType.java
new file mode 100644
index 00000000..ac093522
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/tasktype/type/dependent/uSkyBlockLevelTaskType.java
@@ -0,0 +1,67 @@
+package com.leonardobishop.quests.bukkit.tasktype.type.dependent;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.tasktype.BukkitTaskType;
+import com.leonardobishop.quests.bukkit.util.TaskUtils;
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.TaskProgress;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import us.talabrek.ultimateskyblock.api.event.uSkyBlockScoreChangedEvent;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+public final class uSkyBlockLevelTaskType extends BukkitTaskType {
+
+ private final BukkitQuestsPlugin plugin;
+
+ public uSkyBlockLevelTaskType(BukkitQuestsPlugin plugin) {
+ super("uskyblock_level", TaskUtils.TASK_ATTRIBUTION_STRING, "Reach a certain island level for uSkyBlock.");
+ this.plugin = plugin;
+ }
+
+ @Override
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ ArrayList<ConfigProblem> problems = new ArrayList<>();
+ if (TaskUtils.configValidateExists(root + ".level", config.get("level"), problems, "level", super.getType()))
+ TaskUtils.configValidateInt(root + ".level", config.get("level"), problems, false, false, "level");
+ return problems;
+ }
+
+ @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
+ public void onIslandLevel(uSkyBlockScoreChangedEvent event) {
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(event.getPlayer().getUniqueId());
+ if (qPlayer == null) {
+ return;
+ }
+
+ for (Quest quest : super.getRegisteredQuests()) {
+ if (qPlayer.hasStartedQuest(quest)) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+
+ for (Task task : quest.getTasksOfType(super.getType())) {
+ TaskProgress taskProgress = questProgress.getTaskProgress(task.getId());
+
+ if (taskProgress.isCompleted()) {
+ continue;
+ }
+
+ double islandLevelNeeded = (double) (int) task.getConfigValue("level");
+
+ taskProgress.setProgress(event.getScore().getScore());
+
+ if (((double) taskProgress.getProgress()) >= islandLevelNeeded) {
+ taskProgress.setCompleted(true);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/Chat.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/Chat.java
new file mode 100644
index 00000000..36fe0d2d
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/Chat.java
@@ -0,0 +1,40 @@
+package com.leonardobishop.quests.bukkit.util;
+
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import org.bukkit.ChatColor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Chat {
+
+ public static String color(String s) {
+ return ChatColor.translateAlternateColorCodes('&', s);
+ }
+
+ public static List<String> color(List<String> s) {
+ if (s == null || s.size() == 0) return s;
+
+ List<String> colored = new ArrayList<>();
+ for (String line : s) {
+ colored.add(ChatColor.translateAlternateColorCodes('&', line));
+ }
+ return colored;
+ }
+
+ public static String strip(String s) {
+ return ChatColor.stripColor(s);
+ }
+
+ public static ChatColor matchConfigProblemToColor(ConfigProblem.ConfigProblemType configProblem) {
+ switch (configProblem) {
+ case ERROR:
+ return ChatColor.RED;
+ case WARNING:
+ return ChatColor.YELLOW;
+ default:
+ return ChatColor.WHITE;
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/MenuUtils.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/MenuUtils.java
new file mode 100644
index 00000000..2cce530f
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/MenuUtils.java
@@ -0,0 +1,74 @@
+package com.leonardobishop.quests.bukkit.util;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import com.leonardobishop.quests.bukkit.menu.CancelQMenu;
+import com.leonardobishop.quests.bukkit.menu.MenuController;
+import com.leonardobishop.quests.bukkit.menu.QMenu;
+import com.leonardobishop.quests.common.quest.Quest;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+public class MenuUtils {
+
+ public static ItemStack applyPlaceholders(BukkitQuestsPlugin plugin, UUID owner, ItemStack is) {
+ return applyPlaceholders(plugin, owner, is, Collections.emptyMap());
+ }
+
+ public static ItemStack applyPlaceholders(BukkitQuestsPlugin plugin, UUID owner, ItemStack is, Map<String, String> placeholders) {
+ ItemStack newItemStack = is.clone();
+ List<String> lore = newItemStack.getItemMeta().getLore();
+ List<String> newLore = new ArrayList<>();
+ ItemMeta ism = newItemStack.getItemMeta();
+ Player player = Bukkit.getPlayer(owner);
+ if (lore != null) {
+ for (String s : lore) {
+ for (Map.Entry<String, String> entry : placeholders.entrySet()) {
+ s = s.replace(entry.getKey(), entry.getValue());
+ if (plugin.getPlaceholderAPIHook() != null && plugin.getQuestsConfig().getBoolean("options.gui-use-placeholderapi")) {
+ s = plugin.getPlaceholderAPIHook().replacePlaceholders(player, s);
+ }
+ }
+ newLore.add(s);
+ }
+ }
+ for (Map.Entry<String, String> entry : placeholders.entrySet()) {
+ ism.setDisplayName(ism.getDisplayName().replace(entry.getKey(), entry.getValue()));
+ if (plugin.getPlaceholderAPIHook() != null && plugin.getQuestsConfig().getBoolean("options.gui-use-placeholderapi")) {
+ ism.setDisplayName(plugin.getPlaceholderAPIHook().replacePlaceholders(player, ism.getDisplayName()));
+ }
+ }
+ ism.setLore(newLore);
+ newItemStack.setItemMeta(ism);
+ return newItemStack;
+ }
+
+ public static void handleMiddleClick(BukkitQuestsPlugin plugin, QMenu menu, Quest quest, Player player, MenuController controller) {
+ if (menu.getOwner().hasStartedQuest(quest)) {
+ String tracked = menu.getOwner().getPlayerPreferences().getTrackedQuestId();
+
+ if (quest.getId().equals(tracked)) {
+ menu.getOwner().trackQuest(null);
+ } else {
+ menu.getOwner().trackQuest(quest);
+ }
+ player.closeInventory();
+ }
+ }
+
+ public static void handleRightClick(BukkitQuestsPlugin plugin, QMenu menu, Quest quest, Player player, MenuController controller) {
+ if (menu.getOwner().hasStartedQuest(quest)) {
+ if (plugin.getQuestsConfig().getBoolean("options.allow-quest-cancel")) return;
+ CancelQMenu cancelQMenu = new CancelQMenu(plugin, menu, menu.getOwner(), quest);
+ controller.openMenu(player, cancelQMenu, 1);
+ }
+ }
+
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/Messages.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/Messages.java
new file mode 100644
index 00000000..be99920e
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/Messages.java
@@ -0,0 +1,83 @@
+package com.leonardobishop.quests.bukkit.util;
+
+import com.leonardobishop.quests.bukkit.BukkitQuestsPlugin;
+import org.bukkit.ChatColor;
+
+//TODO refactor this
+public enum Messages {
+
+ TIME_FORMAT("messages.time-format"),
+ QUEST_START("messages.quest-start"),
+ QUEST_COMPLETE("messages.quest-complete"),
+ QUEST_CANCEL("messages.quest-cancel"),
+ QUEST_TRACK("messages.quest-track"),
+ QUEST_TRACK_STOP("messages.quest-track-stop"),
+ QUEST_RANDOM_NONE("messages.quest-random-none"),
+ QUEST_START_LIMIT("messages.quest-start-limit"),
+ QUEST_START_DISABLED("messages.quest-start-disabled"),
+ QUEST_START_LOCKED("messages.quest-start-locked"),
+ QUEST_START_COOLDOWN("messages.quest-start-cooldown"),
+ QUEST_START_STARTED("messages.quest-start-started"),
+ QUEST_START_PERMISSION("messages.quest-start-permission"),
+ QUEST_CATEGORY_QUEST_PERMISSION("messages.quest-category-quest-permission"),
+ QUEST_CATEGORY_PERMISSION("messages.quest-category-permission"),
+ QUEST_CANCEL_NOTSTARTED("messages.quest-cancel-notstarted"),
+ QUEST_UPDATER("messages.quest-updater"),
+ COMMAND_DATA_NOT_LOADED("messages.command-data-not-loaded"),
+ COMMAND_SUB_DOESNTEXIST("messages.command-sub-doesntexist"),
+ COMMAND_QUEST_START_DOESNTEXIST("messages.command-quest-start-doesntexist"),
+ COMMAND_QUEST_GENERAL_DOESNTEXIST("messages.command-quest-general-doesntexist"),
+ COMMAND_QUEST_OPENCATEGORY_ADMIN_SUCCESS("messages.command-quest-opencategory-admin-success"),
+ COMMAND_QUEST_OPENQUESTS_ADMIN_SUCCESS("messages.command-quest-openquests-admin-success"),
+ COMMAND_QUEST_ADMIN_PLAYERNOTFOUND("messages.command-quest-admin-playernotfound"),
+ COMMAND_CATEGORY_OPEN_DOESNTEXIST("messages.command-category-open-doesntexist"),
+ COMMAND_CATEGORY_OPEN_DISABLED("messages.command-category-open-disabled"),
+ COMMAND_QUEST_START_ADMIN_SUCCESS("messages.command-quest-start-admin-success"),
+ COMMAND_TASKVIEW_ADMIN_FAIL("messages.command-taskview-admin-fail"),
+ COMMAND_QUEST_START_ADMIN_FAIL("messages.command-quest-start-admin-fail"),
+ TITLE_QUEST_START_TITLE("titles.quest-start.title"),
+ TITLE_QUEST_START_SUBTITLE("titles.quest-start.subtitle"),
+ TITLE_QUEST_COMPLETE_TITLE("titles.quest-complete.title"),
+ TITLE_QUEST_COMPLETE_SUBTITLE("titles.quest-complete.subtitle"),
+ BETA_REMINDER("messages.beta-reminder"),
+ COMMAND_QUEST_ADMIN_LOADDATA("messages.command-quest-admin-loaddata"),
+ COMMAND_QUEST_ADMIN_NODATA("messages.command-quest-admin-nodata"),
+ COMMAND_QUEST_ADMIN_CLEAN_SUCCESS("messages.command-quest-admin-clean-success"),
+ COMMAND_QUEST_ADMIN_CLEAN_FAIL("messages.command-quest-admin-clean-fail"),
+ COMMAND_QUEST_ADMIN_FULLRESET("messages.command-quest-admin-fullreset"),
+ COMMAND_QUEST_ADMIN_START_FAILLOCKED("messages.command-quest-admin-start-faillocked"),
+ COMMAND_QUEST_ADMIN_START_FAILCOOLDOWN("messages.command-quest-admin-start-failcooldown"),
+ COMMAND_QUEST_ADMIN_START_FAILCOMPLETE("messages.command-quest-admin-start-failcomplete"),
+ COMMAND_QUEST_ADMIN_START_FAILLIMIT("messages.command-quest-admin-start-faillimit"),
+ COMMAND_QUEST_ADMIN_START_FAILSTARTED("messages.command-quest-admin-start-failstarted"),
+ COMMAND_QUEST_ADMIN_START_FAILPERMISSION("messages.command-quest-admin-start-failpermission"),
+ COMMAND_QUEST_ADMIN_START_FAILCATEGORY("messages.command-quest-admin-start-failpermission"),
+ COMMAND_QUEST_ADMIN_START_FAILCATEGORYPERMISSION("messages.command-quest-admin-start-failcategorypermission"),
+ COMMAND_QUEST_ADMIN_START_FAILOTHER("messages.command-quest-admin-start-failother"),
+ COMMAND_QUEST_ADMIN_START_SUCCESS("messages.command-quest-admin-start-success"),
+ COMMAND_QUEST_ADMIN_CATEGORY_PERMISSION("messages.command-quest-admin-category-permission"),
+ COMMAND_QUEST_ADMIN_COMPLETE_SUCCESS("messages.command-quest-admin-complete-success"),
+ COMMAND_QUEST_ADMIN_RESET_SUCCESS("messages.command-quest-admin-reset-success");
+
+ private static BukkitQuestsPlugin plugin;
+
+ private final String path;
+
+ Messages(String path) {
+ this.path = path;
+ }
+
+ public static void setPlugin(BukkitQuestsPlugin plugin) {
+ Messages.plugin = plugin;
+ }
+
+ public String getMessage() {
+ if (plugin.getConfig().contains(path)) {
+ String message = plugin.getQuestsConfig().getString(path);
+ if (message != null) {
+ return ChatColor.translateAlternateColorCodes('&', message);
+ }
+ }
+ return path;
+ }
+}
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/TaskUtils.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/TaskUtils.java
new file mode 100644
index 00000000..ce930639
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/TaskUtils.java
@@ -0,0 +1,90 @@
+package com.leonardobishop.quests.bukkit.util;
+
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.config.ConfigProblemDescriptions;
+import com.leonardobishop.quests.common.quest.Task;
+import org.bukkit.entity.Player;
+
+import java.util.List;
+
+public class TaskUtils {
+
+ public static String TASK_ATTRIBUTION_STRING = "<built-in>";
+
+ public static boolean validateWorld(Player player, Task task) {
+ return validateWorld(player.getLocation().getWorld().getName(), task.getConfigValue("worlds"));
+ }
+
+ public static boolean validateWorld(String worldName, Task task) {
+ return validateWorld(worldName, task.getConfigValue("worlds"));
+ }
+
+ public static boolean validateWorld(String worldName, Object configurationData) {
+ if (configurationData == null) {
+ return true;
+ }
+
+ if (configurationData instanceof List) {
+ List allowedWorlds = (List) configurationData;
+ if (!allowedWorlds.isEmpty() && allowedWorlds.get(0) instanceof String) {
+ List<String> allowedWorldNames = (List<String>) allowedWorlds;
+ return allowedWorldNames.contains(worldName);
+ }
+ return true;
+ }
+
+ if (configurationData instanceof String) {
+ String allowedWorld = (String) configurationData;
+ return worldName.equals(allowedWorld);
+ }
+
+ return true;
+ }
+
+ public static void configValidateInt(String path, Object object, List<ConfigProblem> problems, boolean allowNull, boolean greaterThanZero, String... args) {
+ if (object == null) {
+ if (!allowNull) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.ERROR,
+ String.format("Expected an integer for '%s', but got null instead", (Object[]) args), path));
+ }
+ return;
+ }
+
+ try {
+ Integer i = (Integer) object;
+ if (greaterThanZero && i <= 0) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.ERROR,
+ String.format("Value for field '%s' must be greater than 0", (Object[]) args), path));
+ }
+ } catch (ClassCastException ex) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.ERROR,
+ String.format("Expected an integer for '%s', but got '" + object + "' instead", (Object[]) args), path));
+ }
+ }
+
+ public static void configValidateBoolean(String path, Object object, List<ConfigProblem> problems, boolean allowNull, String... args) {
+ if (object == null) {
+ if (!allowNull) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.ERROR,
+ String.format("Expected a boolean for '%s', but got null instead", (Object[]) args), path));
+ }
+ return;
+ }
+
+ try {
+ Boolean b = (Boolean) object;
+ } catch (ClassCastException ex) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.ERROR,
+ String.format("Expected a boolean for '%s', but got '" + object + "' instead", (Object[]) args), path));
+ }
+ }
+
+ public static boolean configValidateExists(String path, Object object, List<ConfigProblem> problems, String... args) {
+ if (object == null) {
+ problems.add(new ConfigProblem(ConfigProblem.ConfigProblemType.ERROR,
+ String.format(ConfigProblemDescriptions.TASK_MISSING_FIELD.getDescription(args), (Object[]) args), path));
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/bukkit/src/main/resources/plugin.yml b/bukkit/src/main/resources/plugin.yml
new file mode 100644
index 00000000..5cb81473
--- /dev/null
+++ b/bukkit/src/main/resources/plugin.yml
@@ -0,0 +1,30 @@
+name: Quests
+
+# This will be replaced with the property "version" in build.gradle
+version: ${version}
+
+main: com.leonardobishop.quests.bukkit.BukkitQuestsPlugin
+website: https://github.com/LMBishop/Quests
+author: "LMBishop & contributors"
+softdepend: [ASkyBlock, BentoBox, IridiumSkyblock, uSkyBlock, Citizens, MythicMobs, PlaceholderAPI, Essentials, ShopGUIPlus, CoreProtect]
+prefix: Quests
+api-version: "1.13" # allows new API features but Quests will still work pre-1.13
+
+commands:
+ quests:
+ description: Interact with your quests
+ usage: /quests
+ permission: quests.command
+ aliases: [q, quest]
+
+#todo redo perms
+permissions:
+ quests.command:
+ description: Permission for main command
+ default: true
+ quests.command.random:
+ description: Permission to execute /q random
+ default: true
+ quests.admin:
+ description: Permission for the admin commands
+ default: op \ No newline at end of file
diff --git a/bukkit/src/main/resources/resources/bukkit/config.yml b/bukkit/src/main/resources/resources/bukkit/config.yml
new file mode 100644
index 00000000..f7084505
--- /dev/null
+++ b/bukkit/src/main/resources/resources/bukkit/config.yml
@@ -0,0 +1,355 @@
+# | =================================================== |
+# | Thank you for downloading and trying out |
+# | Quests |
+# | https://www.spigotmc.org/resources/23696/ |
+# | https://github.com/LMBishop/Quests/ |
+# | |
+# | =x= |
+# | |
+# | Trying to add new quests? |
+# | |
+# | Go to the 'quests' |
+# | subdirectory to edit the |
+# | quests |
+# | |
+# | The example quests |
+# | are commented to help you |
+# | learn how to use the plugin |
+# | |
+# | =x= |
+# | |
+# | Have Discord & need help? |
+# | Head to the GitHub page and click on the |
+# | Discord link, or create an issue |
+# | =================================================== |
+
+
+# Categories are a way of grouping up quests.
+# When a player uses /quests, a menu of categories will be presented to them.
+# When a player clicks ona category, a list of quests under that category will show.
+# If categories are disabled, all quests will be shown under one big GUI.
+# Players can access specific categories by command using /quests c [category].
+# If a quest does not have a category, it will not be shown.
+categories:
+ examples:
+ display:
+ name: "&cExample Quests"
+ lore:
+ - "&7This category contains example quests"
+ - "&7which are commented in the config."
+ - "&7The comments should guide you with"
+ - "&7how the config works."
+ - ""
+ - "&cIt is highly recommended you read this"
+ - "&csection and all the comments so you can"
+ - "&cmake the most of this plugin."
+ type: "WATER_BUCKET"
+ permissionexample:
+ display:
+ name: "&cPermission Example"
+ lore:
+ - "&7This category is an example of one which"
+ - "&7requires a permission to open."
+ - ""
+ - "&cIt is highly recommended you read this"
+ - "&csection and all the comments so you can"
+ - "&cmake the most of this plugin."
+ type: "WATER_BUCKET"
+ # This category needs the permission "quests.category.permissionexample", because the category ID is 'permissionexample'.
+ # The permission for other categories is: "quests.category.<id>".
+ permission-required: true
+
+# The items listed below are placeholder items for quests which the player cannot start.
+# You should change these for 1.8
+gui:
+ back-button:
+ name: "&cReturn"
+ lore:
+ - "&7Return to the categories menu."
+ type: "ARROW"
+ page-prev:
+ name: "&7Previous Page"
+ lore:
+ - "&7Switch the page to page &c{prevpage}."
+ type: "FEATHER"
+ page-next:
+ name: "&7Next Page"
+ lore:
+ - "&7Switch the page to page &c{nextpage}."
+ type: "FEATHER"
+ page-desc:
+ name: "&7Page &c{page}"
+ lore:
+ - "&7You are currently viewing page &c{page}."
+ type: "PAPER"
+ quest-locked-display:
+ name: "&c&lQuest Locked"
+ lore:
+ - "&7You have not completed the requirements"
+ - "&7for this quest (&c{quest}&7)."
+ - ""
+ - "&7Requires: &c{requirements}"
+ - "&7to be completed to unlock."
+ type: "RED_STAINED_GLASS_PANE"
+ quest-permission-display:
+ name: "&6&lNo Permission"
+ lore:
+ - "&7You do not have permission for this"
+ - "&7quest (&6{quest}&7)."
+ type: "BROWN_STAINED_GLASS_PANE"
+ quest-cooldown-display:
+ name: "&e&lQuest On Cooldown"
+ lore:
+ - "&7You have recently completed this quest"
+ - "&7(&e{quest}&7) and you must"
+ - "&7wait another &e{time} &7to unlock again."
+ type: "ORANGE_STAINED_GLASS_PANE"
+ quest-completed-display:
+ name: "&a&lQuest Complete"
+ lore:
+ - "&7You have completed this quest"
+ - "&7(&a{quest}&7) and cannot."
+ - "&7repeat it."
+ type: "GREEN_STAINED_GLASS_PANE"
+ no-started-quests:
+ name: "&c&lNo Started Quests"
+ lore:
+ - "&7Go start some!"
+ type: "FEATHER"
+ quest-cancel-yes:
+ name: "&a&lConfirm Cancel"
+ lore:
+ - "&7Confirm you wish to cancel"
+ - "&7this quest and lose all"
+ - "&7progress."
+ type: "GREEN_STAINED_GLASS_PANE"
+ quest-cancel-no:
+ name: "&c&lAbort Cancel"
+ lore:
+ - "&7Return to the quest menu."
+ type: "RED_STAINED_GLASS_PANE"
+ quest-cancel-background:
+ type: "GRAY_STAINED_GLASS_PANE"
+
+# Here you can add custom items to the quest menu
+# Categories & quests will fill empty slots
+#custom-elements:
+# "categories": # apply to the categories menu (the main menu by default)
+# 0: # <--- slot 1, note the slots start from 0! so 0 = slot 1 in this case
+# display:
+# name: "&cExample Custom Item (slot 1)"
+# lore:
+# - "&7This is a custom item which can be added"
+# - "&7to your menus. This is purely cosmetic."
+# - ""
+# - "&7Two empty slots should follow."
+# type: "DIAMOND_BLOCK"
+# 1: # <--- start from slot 2
+# spacer: true # empty slot in GUI
+# repeat: 2 # repeats for 2 slots
+# 3: # <--- start from slot 4
+# display:
+# name: "&cExample Custom Item (slots 4 - 7)"
+# lore:
+# - "&7This is a custom item which can be added"
+# - "&7to your menus, but in slot 4 and repeated"
+# - "&73 times."
+# - "&7"
+# - "&7This will come after 2 empty slots."
+# - "&7"
+# - "&7This is purely cosmetic."
+# type: "NETHERRACK"
+# repeat: 3 # repeats for 3 more slots
+# "c:examples": # apply to the category "examples"
+# 0:
+# display:
+# name: "&cExample Custom Item (slot 1)"
+# lore:
+# - "&7This is a custom item which can be added"
+# - "&7to your menus. This is purely cosmetic."
+# type: "EMERALD_BLOCK"
+# "quests": # apply to the general quests menu IF categories are disabled
+# 0:
+# display:
+# name: "&cExample Custom Item (slot 1)"
+# lore:
+# - "&7This is a custom item which can be added"
+# - "&7to your menus. This is purely cosmetic."
+# type: "EMERALD_BLOCK"
+
+options:
+ # If categories are disabled, quests will be put into one big gui.
+ categories-enabled: true
+ # If true, the gui size will automatically change based on the amount of quests inside it.
+ trim-gui-size: true
+ # Enable/disable titles
+ titles-enabled: true
+ # Players cannot start any more quests than this at a single time
+ quest-started-limit: 2
+ # Hide locked quests, quests on cooldown and completed (but not repeatable) quests
+ gui-hide-locked: false
+ # Allow players to cancel a quest (you may want to remove the cancel instructions in the global item lore)
+ allow-quest-cancel: true
+ # Allow players to track a quest (you may want to remove the tracking instructions in the global item lore)
+ allow-quest-track: true
+ # Titles for the GUIs
+ guinames:
+ quests-category: "Quests Categories"
+ quests-menu: "Quests"
+ quests-started-menu: "Started Quests"
+ daily-quests: "Daily Quests"
+ quest-cancel: "Cancel Quest"
+ # Show when quests register in console - will only show if verbose-logging-level=2. Disable if you want less console spam at startup.
+ show-quest-registrations: true
+ # Hide quests which a player cannot start due to permissions.
+ gui-hide-quests-nopermission: false
+ # Hide categories which a player cannot open due to permissions.
+ gui-hide-categories-nopermission: false
+ # Replace placeholders from PlaceholderAPI in Quests GUI items
+ gui-use-placeholderapi: false
+ # Make it so players do not have to start quest themselves
+ quest-autostart: false
+ # Automatically track quests on start, and stop tracking on completion
+ quest-autotrack: true
+ # How much quests should log, 0 = errors only, 1 = warnings, 2 = info, 3 = debug
+ verbose-logging-level: 2
+ # Verify quests exist when a player's data is loaded - inconsistencies may arise when
+ # players progress on specific quests and those quests are later removed. The problem is that their progress
+ # is still kept in the quest progress file, which may lead to issues such as players reaching a quest started
+ # limit when the quests they had active no longer exist - having this option enabled prevents
+ # non-existent quests from being loaded
+ verify-quest-exists-on-load: true
+ performance-tweaking: # The following are measured in server ticks, multiply SECONDS by 20 to get the number of ticks.
+ quest-queue-executor-interval: 1 # how frequently Quests should execute the next check in the completion queue (def=1 - 0.05s) - increase this value if you are struggling with performance
+ quest-autosave-interval: 12000 # how frequently online players data will be autosaved (def=12000 - 10 minutes)
+ tab-completion:
+ enabled: true
+ error-checking:
+ # Allow quests to be loaded if they contain errors
+ # This may lead to errors in the console!
+ override-errors: false
+ # How much time (in seconds) that plugin will cache placeholders
+ placeholder-cache-time: 10
+ # Whether or not the global task configuration will override per-task configuration settings
+ global-task-configuration-override: false
+ # Whether or not the global display configuration will override per-quest display settins
+ global-quest-display-configuration-override: false
+ # Storage options - please see the following: https://github.com/LMBishop/Quests/wiki/Storage-Providers
+ storage:
+ # Either 'yaml' (flatfile) or 'mysql' (network)
+ # Please read the following before using MySQL https://github.com/LMBishop/Quests/wiki/Storage-Providers#network
+ provider: "yaml"
+ # The following is only applicable for database storage providers (e.g. mysql)
+ database-settings:
+ network:
+ # The name of the database. This database should already exist!
+ database: "minecraft"
+ username: "root"
+ password: ""
+ # Address should be in the format ip:port (just like the game itself)
+ address: "localhost:3306"
+ # This plugin uses 'HikariCP' for connection management, the pooling configuration can be changed here
+ connection-pool-settings:
+ # The maximum number of connections to keep open with the database (def=8)
+ maximum-pool-size: 8
+ # The minimum number of connections to keep open with the database (def=8)
+ minimum-idle: 8
+ # The maximum time (in milliseconds) to keep a single connection open (def=1800000 - 30 min)
+ maximum-lifetime: 1800000
+ # The time (in milliseconds) the plugin will wait for a response by the database (def=500)
+ connection-timeout: 5000
+ # The prefix each table will use
+ table-prefix: "quests_"
+
+
+quest-mode:
+ mode: "NORMAL" # More modes are a work in progress
+
+# The global task configuration will apply to the config of each task of a specified type in each quest.
+# In the example below, every task with a type of "inventory" will have the config option "update-progress"
+# set to true. This is useful if you do not want to go through each quest if you want to update the configuration
+# for every task of a specific type.
+#
+# You must uncomment all below lines for the example to have any effect
+# vvvvvvvvvvvvvvvvvvvvvvvvv
+#global-task-configuration:
+# types:
+# inventory:
+# update-progress: true
+
+global-quest-display:
+ lore:
+# append-normal:
+# - "..."
+ append-not-started:
+ - ""
+ - "&eLeft Click &7to start this quest."
+ append-started:
+ - ""
+ - "&aYou have started this quest."
+ - "&eMiddle Click &7to track this quest."
+ - "&eRight Click &7to cancel this quest."
+ append-tracked:
+ - ""
+ - "&aYou are &etracking &athis quest."
+ - "&eMiddle Click &7to stop tracking this quest."
+ - "&eRight Click &7to cancel this quest."
+
+
+# Configure titles
+titles:
+ quest-start:
+ title: "&cQuest Started"
+ subtitle: "&7{quest}"
+ quest-complete:
+ title: "&cQuest Complete"
+ subtitle: "&7{quest}"
+
+# Configure messages
+messages:
+ time-format: "{hours}h {minutes}m"
+ quest-start: "&7Quest &c{quest} &7started!"
+ quest-complete: "&7Quest &c{quest} &7completed!"
+ quest-cancel: "&7Quest &c{quest} &7cancelled!"
+ quest-track: "&7Tracking quest &c{quest}&7."
+ quest-track-stop: "&7No longer tracking quest &c{quest}&7."
+ quest-random-none: "&cYou have no quests which you can start."
+ quest-start-limit: "&7Players are limited to &c{limit} &7started quests at a time."
+ quest-start-disabled: "&7You cannot repeat this quest."
+ quest-start-locked: "&7You have not unlocked this quest yet."
+ quest-start-cooldown: "&7You have recently completed this quest. You have to wait &c{time} &7until you are able to restart it."
+ quest-start-started: "&7You have already started this quest."
+ quest-start-permission: "&7You do not have permission to start this quest."
+ quest-category-permission: "&7You do not have permission to view this category."
+ quest-category-quest-permission: "&7You do not have permission to start this quest since it is in a category you do not have permission to view."
+ quest-cancel-notstarted: "&7You have not started this quest."
+ quest-updater: "&cQuests > &7A new version &c{newver} &7was found on Spigot (your version: &c{oldver}&7). Please update me! <3 - Link: {link}"
+ command-data-not-loaded: "&4Your quests progress file has not been loaded; you cannot use quests. If this issue persists, contact an admin."
+ command-sub-doesntexist: "&7The specified subcommand '&c{sub}' &7does not exist."
+ command-quest-start-doesntexist: "&7The specified quest '&c{quest}&7' does not exist."
+ command-quest-general-doesntexist: "&7The specified quest '&c{quest}&7' does not exist."
+ command-category-open-disabled: "&7Categories are disabled."
+ command-category-open-doesntexist: "&7The specified category '&c{category}&7' does not exist."
+ command-quest-admin-playernotfound: "&7Player '&c{player}&7' could not be found."
+ command-quest-openquests-admin-success: "&7Opened Quest GUI for player &c{player}&7."
+ command-quest-opencategory-admin-success: "&7Opened category &c{category} &7for player &c{player}&7."
+ command-taskview-admin-fail: "&7Task type '&c{task}&7' does not exist."
+ beta-reminder: "&cQuests > &7Reminder: you are currently using a &cbeta &7version of Quests. Please send bug reports to https://github.com/fatpigsarefat/Quests/issues and check for updates regularly using &c/quests admin update&7!"
+ command-quest-admin-loaddata: "&7Quest data for '&c{player}&7' is being loaded."
+ command-quest-admin-nodata: "&7No data could be found for player &c{player}&7."
+ command-quest-admin-fullreset: "&7Data for player &c{player}&7 has been fully reset."
+ command-quest-admin-clean-success: "&7All quest progress files have been cleaned."
+ command-quest-admin-clean-fail: "&cFailed to clean quest progress files. Please report the error in the console."
+ command-quest-admin-start-faillocked: "&7Quest '&c{quest}&7' could not be started for player &c{player}&7. They have not yet unlocked it."
+ command-quest-admin-start-failcooldown: "&7Quest '&c{quest}&7' could not be started for player &c{player}&7. It is still on cooldown for them."
+ command-quest-admin-start-failcomplete: "&7Quest '&c{quest}&7' could not be started for player &c{player}&7. They have already completed it."
+ command-quest-admin-start-faillimit: "&7Quest '&c{quest}&7' could not be started for player &c{player}&7. They have reached their quest start limit."
+ command-quest-admin-start-failstarted: "&7Quest '&c{quest}&7' could not be started for player &c{player}&7. It is already started."
+ command-quest-admin-start-failpermission: "&7Quest '&c{quest}&7' could not be started for player &c{player}&7. They do not have permission."
+ command-quest-admin-start-failcategorypermission: "&7Quest '&c{quest}&7' could not be started for player &c{player}&7. They do not have permission for the category which the quest is in."
+ command-quest-admin-start-failother: "&7Quest '&c{quest}&7' could not be started for player &c{player}&7."
+ command-quest-admin-start-success: "&7Quest &c{quest} &7started for player &c{player}&7."
+ command-quest-admin-category-permission: "&7Category &c{category} &7 could not be opened for player &c{player}&7. They do not have permission to view it."
+ command-quest-admin-complete-success: "&7Quest &c{quest} &7completed for player &c{player}&7."
+ command-quest-admin-reset-success: "&7Successfully reset quest '&c{quest}&7' for player &c{player}&7."
+
diff --git a/bukkit/src/main/resources/resources/bukkit/quests/README.txt b/bukkit/src/main/resources/resources/bukkit/quests/README.txt
new file mode 100644
index 00000000..779dd6bb
--- /dev/null
+++ b/bukkit/src/main/resources/resources/bukkit/quests/README.txt
@@ -0,0 +1,40 @@
+# !! READ ME - IT WILL NOT TAKE LONG !!
+#
+# Each file ín the 'quests' folder defines a single quest.
+# The name of the file is the quest ID. These must be alphanumeric and unique.
+# Quest files must be in the .yml format.
+#
+# A quest is a series of tasks which players must complete for a reward and may require a previous quest to start.
+# A task is an objective such as breaking blocks or obtaining items.
+# A reward is a command executed by the SERVER. Use {player} to get the players name.
+#
+# Most task types will allow you to restrict them to a certain world. Check out the wiki for which ones.
+#
+# A quest can have a 'startstring' (this is optional). They will be sent to the player when they start the quest.
+# A quest can have a 'rewardstring' (this is optional). They will be sent to the player when they complete the quest.
+# An example of the startstring/rewardstring in use can be seen in the quest example4.
+#
+# Each quest will have ONE "display" item, this is the item shown to the player in the GUI.
+# The display item will have a "name", a "type" and TWO lores.
+# The name is the name of the item, the type is the material and the lore is the text underneath the item (when mouse-over-ing).
+# The first lore you must give is called 'lore-normal'. This is the lore seen if the player has not started the quest.
+# The second lore you must give is 'lore-started'. This will be APPENDED to the first lore IF the player has started the quest - useful for putting progression.
+# Within the lores you can get the players progress for each task. Use {TASKID:progress} (replace TASKID with the ID of the task).
+# You can also get if a task is complete. Use {TASKID:complete} (replace TASKID with the ID of the task).
+#
+# Quests can be put inside a category. When a player runs /quests they will first see a menu of categories.
+# They can click one and another menu of quests under that category will show up.
+#
+# ===============================================================
+#
+# You can see other task types here:
+# https://github.com/LMBishop/Quests/wiki/Task-Types
+#
+# ===============================================================
+#
+# Need help?
+# Find the Discord link in the README.md, or open an issue on GitHub
+# https://github.com/LMBishop/Quests/issues
+# https://github.com/LMBishop/Quests/blob/master/README.md
+#
+# ===============================================================
diff --git a/bukkit/src/main/resources/resources/bukkit/quests/example1.yml b/bukkit/src/main/resources/resources/bukkit/quests/example1.yml
new file mode 100644
index 00000000..ca57f714
--- /dev/null
+++ b/bukkit/src/main/resources/resources/bukkit/quests/example1.yml
@@ -0,0 +1,61 @@
+# The name of this file is the quest ID. It must be alphanumeric and unique.
+
+# Everything inside of this section defines tasks the player must complete to progress.
+tasks:
+ # This is the task ID ("mining"). This can share the same name as the quest ID but MUST be unique with all other task IDs in the same quest.
+ mining:
+ # This defines what type of task this is. In this instance, it is "blockbreak" (breaking blocks)
+ # NOTE: guides to set up each type of task is on the wiki (see README)!
+ type: "blockbreak"
+ # This defines the amount of blocks which need to be broken
+ amount: 30
+ # You can have multiple tasks for each quest (example further down).
+
+# Everything inside of this section defines the display item.
+display:
+ # This is the name of the item. This allows color codes.
+ name: "&cExample I (Single Task)"
+ # This is the lore of the item if the player has not started the quest. This allows color codes and task/player placeholders.
+ lore-normal:
+ - "&cThis category is designed to show you the different"
+ - "&cattributes a quest can have."
+ - ""
+ - "&7This quest requires you to:"
+ - "&7 - Break &f30 blocks&7."
+ - ""
+ - "&7Rewards:"
+ - "&7 - &f10 &7diamonds."
+ # This lore will be appended to the bottom of the above lore when the player starts their quest.
+ # To get the players progress through a task, use {TASKID:progress} and replace TASKID with the ID of the task.
+ lore-started:
+ - ""
+ - "&7Your current progression:"
+ - "&7 - &f{mining:progress}&7/30 blocks broken."
+ # This is the material of the item. It is recommended to stick to bukkit names.
+ type: "WOODEN_PICKAXE"
+
+# List all commands to be executed by the server when the player completes the quest. Use {player} to get the players name.
+rewards:
+ - "give {player} diamond 10"
+
+# These placeholders are accessible using PlaceholderAPI, for example %quests_tracked_p:description%
+# They are useful for putting information about the players tracked quest on a scoreboard
+# You may want to keep the names of them the same for ALL quests for this use-case
+placeholders:
+ description: "&7Break &f30 blocks &7of any type."
+ progress: " &8- &f{mining:progress}&7/30 broken"
+
+# Everything inside this section define quest-specific options
+options:
+ # This is the category for the quest, it will appear under the "examples" category. Categories can be disabled.
+ category: "examples"
+ # Set if the quest can be repeated after being completed for the first time.
+ repeatable: false
+ # Define the cooldown on quests. The above (repeatable) must be true for this to take effect.
+ cooldown:
+ # If true, players will have to wait between repeating quests.
+ enabled: true
+ # Time (in minutes)
+ time: 1440
+ # This is the relative position in the GUI
+ sort-order: 1 \ No newline at end of file
diff --git a/bukkit/src/main/resources/resources/bukkit/quests/example2.yml b/bukkit/src/main/resources/resources/bukkit/quests/example2.yml
new file mode 100644
index 00000000..7ad82bea
--- /dev/null
+++ b/bukkit/src/main/resources/resources/bukkit/quests/example2.yml
@@ -0,0 +1,47 @@
+# This is a quest which requires the previous quest to be complete to start.
+
+tasks:
+ # Unlike the previous quest, this quest has multiple tasks.
+ mining:
+ type: "blockbreak"
+ amount: 100
+ building:
+ type: "blockplace"
+ amount: 100
+display:
+ name: "&cExample II (Multiple Tasks)"
+ lore-normal:
+ - "&cThis category is designed to show you the different"
+ - "&cattributes a quest can have. This quest requires"
+ - "&cmultiple things to be done, unlike the previous one."
+ - ""
+ - "&7This quest requires you to:"
+ - "&7 - Break &f100 &7blocks."
+ - "&7 - Place &f100 &7blocks."
+ - ""
+ - "&7Rewards:"
+ - "&7 - &f15 diamonds&7."
+ - "&7 - &f$50&7 added to your in-game balance."
+ lore-started:
+ - ""
+ - "&7Your current progression:"
+ - "&7 - &f{mining:progress}&7/100 blocks broken."
+ - "&7 - &f{building:progress}&7/100 blocks placed."
+ type: "GRASS_BLOCK"
+rewards:
+ - "give {player} diamond 15"
+ - "eco give {player} 50"
+placeholders:
+ description: "&7Break and place &f100 blocks &7of any type."
+ progress: " &8- &f{mining:progress}&7/100 broken, &f{building:progress}&7/100 placed"
+options:
+ category: "examples"
+ # Unlike the previous quest, this quest has "example1" as a required quest. You cannot start this quest without "example1" quest complete.
+ requires:
+ - "example1"
+ repeatable: false
+ cooldown:
+ enabled: true
+ time: 1440
+ # The sort order has been changed so this quest will appear after in the GUI
+ sort-order: 2 \ No newline at end of file
diff --git a/bukkit/src/main/resources/resources/bukkit/quests/example3.yml b/bukkit/src/main/resources/resources/bukkit/quests/example3.yml
new file mode 100644
index 00000000..dbd893e4
--- /dev/null
+++ b/bukkit/src/main/resources/resources/bukkit/quests/example3.yml
@@ -0,0 +1,49 @@
+# This is a quest which requires the previous quest to be complete to start.
+# Unlike the previous quest, this one can be re-done but it has a 10 minute cooldown.
+
+tasks:
+ # Unlike the previous two quests, this quest specifies a specific block to be broken.
+ mining:
+ type: "blockbreakcertain"
+ amount: 81
+ block: GOLD_ORE
+ building:
+ type: "blockplacecertain"
+ amount: 9
+ block: GOLD_BLOCK
+display:
+ name: "&cExample III (Repeatable, 10 minute cooldown)"
+ lore-normal:
+ - "&cThis category is designed to show you the different"
+ - "&cattributes a quest can have. This quest can be replayed"
+ - "&cafter a cooldown, unlike the previous one."
+ - ""
+ - "&7This quest requires you to:"
+ - "&7 - Break &f81 gold ore&7."
+ - "&7 - Place &f9 gold blocks&7."
+ - ""
+ - "&7Rewards:"
+ - "&7 - &f30 diamonds&7."
+ - "&7 - &f$10&7 added to your in-game balance."
+ lore-started:
+ - ""
+ - "&7Your current progression:"
+ - "&7 - &f{mining:progress}&7/81 gold ore broken."
+ - "&7 - &f{building:progress}&7/9 gold blocks placed."
+ type: "GOLD_ORE"
+rewards:
+ - "give {player} diamond 30"
+ - "eco give {player} 10"
+placeholders:
+ description: "&7Break &f81 gold ore &7and place &f9 gold blocks."
+ progress: " &8- &f{mining:progress}&7/81 gold ore, &f{building:progress}&7/9 gold blocks"
+options:
+ category: "examples"
+ requires:
+ - "example2"
+ # This quest is repeatable, it has cooldowns enabled (meaning the player must wait before repeating it) and the time set to 10 (minutes).
+ repeatable: true
+ cooldown:
+ enabled: true
+ time: 10
+ sort-order: 3 \ No newline at end of file
diff --git a/bukkit/src/main/resources/resources/bukkit/quests/example4.yml b/bukkit/src/main/resources/resources/bukkit/quests/example4.yml
new file mode 100644
index 00000000..4e9c27a8
--- /dev/null
+++ b/bukkit/src/main/resources/resources/bukkit/quests/example4.yml
@@ -0,0 +1,50 @@
+# This is a quest which requires the previous quest to be complete to start.
+# Unlike the previous quests, this quest has a reward string and a start string.
+
+tasks:
+ mobkilling:
+ type: "mobkilling"
+ amount: 3
+display:
+ name: "&cExample IV (Reward String)"
+ lore-normal:
+ - "&cThis category is designed to show you the different"
+ - "&cattributes a quest can have. This quest has a 'reward string'"
+ - "&c(a series of messages sent when a quest is complete),"
+ - "&cunlike the previous one."
+ - ""
+ - "&7This quest requires you to:"
+ - "&7 - Kill &f3 &7mobs."
+ - ""
+ - "&7Rewards:"
+ - "&7 - &f$50 &7added to your in-game balance."
+ - "&7 - &f1 diamond&7."
+ lore-started:
+ - ""
+ - "&7Your current progression:"
+ - "&7 - &f{mobkilling:progress}&7/3 mobs killed."
+ type: "STRING"
+# Here you can list messages which will be sent to the player (if they are online) upon the quest starting.
+startstring:
+ - "&7Upon completion of this quest, you will be rewarded with"
+ - " &8* &c$50"
+ - " &8* &c1 diamonds"
+rewards:
+ - "eco give {player} 50"
+ - "give {player} diamond 1"
+placeholders:
+ description: "&7Kill &f3 &7mobs."
+ progress: " &8- &f{mobkilling:progress}&7/3 mobs"
+# Here you can list messages which will be sent to the player (if they are online) upon completion.
+rewardstring:
+ - " &8* &c$1000 &7was added to your in-game balance."
+ - " &8* &c1 diamond &7was added to your inventory."
+options:
+ category: "examples"
+ requires:
+ - "example3"
+ repeatable: true
+ cooldown:
+ enabled: true
+ time: 10
+ sort-order: 4 \ No newline at end of file
diff --git a/bukkit/src/main/resources/resources/bukkit/quests/example5.yml b/bukkit/src/main/resources/resources/bukkit/quests/example5.yml
new file mode 100644
index 00000000..cb92f0f1
--- /dev/null
+++ b/bukkit/src/main/resources/resources/bukkit/quests/example5.yml
@@ -0,0 +1,38 @@
+tasks:
+ building:
+ type: "blockplace"
+ amount: 10
+display:
+ name: "&cExample V (Permission)"
+ lore-normal:
+ - "&cThis category is designed to show you the different"
+ - "&cattributes a quest can have. This quest requires"
+ - "&ccertain permissions."
+ - ""
+ - "&7This quest requires you to:"
+ - "&7 - Place &f10 &7blocks."
+ - ""
+ - "&7Rewards:"
+ - "&7 - &f$10 &7added to your in-game balance."
+ lore-started:
+ - ""
+ - "&7Your current progression:"
+ - "&7 - &f{building:progress}&7/10 blocks placed."
+ type: "GRASS_BLOCK"
+rewards:
+ - "eco give {player} 10"
+placeholders:
+ description: "&7Place &f10 &7blocks of any type."
+ progress: " &8- &f{building:progress}&7/10 blocks"
+options:
+ category: "examples"
+ requires:
+ - "example4"
+ # Unlike the previous quests, this one requires you to have the permission "quests.quest.example5" to start.
+ # The permission for other quests is: "quests.quest.<id>".
+ permission-required: true
+ repeatable: false
+ cooldown:
+ enabled: true
+ time: 1440
+ sort-order: 5 \ No newline at end of file
diff --git a/bukkit/src/main/resources/resources/bukkit/quests/example6.yml b/bukkit/src/main/resources/resources/bukkit/quests/example6.yml
new file mode 100644
index 00000000..0913a7cb
--- /dev/null
+++ b/bukkit/src/main/resources/resources/bukkit/quests/example6.yml
@@ -0,0 +1,35 @@
+tasks:
+ building:
+ type: "blockplace"
+ amount: 10
+display:
+ name: "&cExample VI (Different category, permissions)"
+ lore-normal:
+ - "&cThis category is designed to show you the different"
+ - "&cattributes a quest can have. This quest requires"
+ - "&ccertain permissions."
+ - ""
+ - "&7This quest requires you to:"
+ - "&7 - Place &f10 &7blocks."
+ - ""
+ - "&7Rewards:"
+ - "&7 - &f$10 &7added to your in-game balance."
+ lore-started:
+ - ""
+ - "&7Your current progression:"
+ - "&7 - &f{building:progress}&7/10 blocks placed."
+ type: "GRASS_BLOCK"
+rewards:
+ - "eco give {player} 10"
+placeholders:
+ description: "&7Place &f10 &7blocks of any type."
+ progress: " &8- &f{building:progress}&7/10 blocks"
+options:
+ category: "permissionexample"
+ # This quest has no specific permission, however its category does. The permission for the category is "quests.category.permissionexample"
+ repeatable: false
+ cooldown:
+ enabled: true
+ time: 1440
+ # The quest is in a different category so the sort order is 1.
+ sort-order: 1 \ No newline at end of file
diff --git a/bukkit/src/main/resources/resources/bukkit/quests/example7.yml b/bukkit/src/main/resources/resources/bukkit/quests/example7.yml
new file mode 100644
index 00000000..32bb7e04
--- /dev/null
+++ b/bukkit/src/main/resources/resources/bukkit/quests/example7.yml
@@ -0,0 +1,37 @@
+tasks:
+ building:
+ type: "blockplace"
+ amount: 10
+ worlds:
+ - "world"
+display:
+ name: "&cExample VII (Different category, world restricted)"
+ lore-normal:
+ - "&cThis category is designed to show you the different"
+ - "&cattributes a quest can have. This quest requires"
+ - "&chas a task which requires you to be in a world called 'world'."
+ - ""
+ - "&7This quest requires you to:"
+ - "&7 - Place &f10 &7blocks."
+ - ""
+ - "&7Rewards:"
+ - "&7 - &f$10 &7added to your in-game balance."
+ lore-started:
+ - ""
+ - "&7Your current progression:"
+ - "&7 - &f{building:progress}&7/10 blocks placed."
+ type: "GRASS_BLOCK"
+rewards:
+ - "eco give {player} 10"
+placeholders:
+ description: "&7Place &f10 &7blocks of any type in world &fworld."
+ progress: " &8- &f{building:progress}&7/10 blocks"
+options:
+ category: "permissionexample"
+ # This quest has no specific permission, however its category does. The permission for the category is "quests.category.permissionexample"
+ repeatable: false
+ cooldown:
+ enabled: true
+ time: 1440
+ # The quest is in a different category so the sort order is 1.
+ sort-order: 1 \ No newline at end of file