summaryrefslogtreecommitdiffstats
path: root/common/src/main/java
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 /common/src/main/java
parent5c3d30840bb62c047f077d9ec1cec6b8572cc17b (diff)
Convert to multi module project
- Common module to provide an abstract Quests plugin - Api is still todo
Diffstat (limited to 'common/src/main/java')
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/config/ConfigProblem.java63
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/config/ConfigProblemDescriptions.java35
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/config/QuestsConfig.java25
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/config/QuestsLoader.java11
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/enums/QuestStartResult.java13
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/logger/QuestsLogger.java44
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/player/QPlayer.java127
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/player/QPlayerManager.java158
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/player/QPlayerPreferences.java18
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/QuestProgress.java143
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/QuestProgressFile.java253
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/TaskProgress.java73
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/plugin/Quests.java38
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/quest/Category.java33
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/quest/Quest.java120
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/quest/QuestCompleter.java11
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/quest/QuestManager.java49
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/quest/Task.java41
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/questcontroller/QuestController.java23
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/scheduler/ServerScheduler.java8
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/storage/StorageProvider.java17
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/tasktype/TaskType.java108
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/tasktype/TaskTypeManager.java67
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/updater/Updater.java78
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/util/Format.java21
25 files changed, 1577 insertions, 0 deletions
diff --git a/common/src/main/java/com/leonardobishop/quests/common/config/ConfigProblem.java b/common/src/main/java/com/leonardobishop/quests/common/config/ConfigProblem.java
new file mode 100644
index 00000000..d9f30c09
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/config/ConfigProblem.java
@@ -0,0 +1,63 @@
+package com.leonardobishop.quests.common.config;
+
+public final class ConfigProblem {
+
+ private final ConfigProblemType type;
+ private final String description;
+ private final String location;
+
+ public ConfigProblem(ConfigProblemType type, String description, String location) {
+ this.type = type;
+ this.description = description == null ? "?" : description;
+ ;
+ this.location = location == null ? "?" : location;
+ }
+
+ public ConfigProblem(ConfigProblemType type, String description) {
+ this.type = type;
+ this.description = description == null ? "?" : description;
+ ;
+ this.location = "?";
+ }
+
+ public ConfigProblemType getType() {
+ return type;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public String getLocation() {
+ return location;
+ }
+
+ public enum ConfigProblemType {
+
+ ERROR("Error", "E", 1),
+ WARNING("Warning", "W", 2);
+
+ private final String title;
+ private final String shortened;
+ private final int priority;
+
+ ConfigProblemType(String title, String shortened, int priority) {
+ this.title = title;
+ this.shortened = shortened;
+ this.priority = priority;
+ }
+
+ public String getTitle() {
+ return title;
+ }
+
+ public String getShortened() {
+ return shortened;
+ }
+
+ public int getPriority() {
+ return priority;
+ }
+
+ }
+} \ No newline at end of file
diff --git a/common/src/main/java/com/leonardobishop/quests/common/config/ConfigProblemDescriptions.java b/common/src/main/java/com/leonardobishop/quests/common/config/ConfigProblemDescriptions.java
new file mode 100644
index 00000000..868bdfbb
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/config/ConfigProblemDescriptions.java
@@ -0,0 +1,35 @@
+package com.leonardobishop.quests.common.config;
+
+public enum ConfigProblemDescriptions {
+
+ MALFORMED_YAML("Malformed YAML file, cannot read config"),
+ INVALID_QUEST_ID("ID '%s' is invalid, must be alphanumeric, unique and with no spaces"),
+ NO_TASKS("Quest contains no valid tasks"),
+ NO_TASK_TYPE("Task type not specified"),
+ UNKNOWN_TASK_TYPE("Task type '%s' does not exist"),
+ NO_DISPLAY_NAME("No name specified"),
+ NO_DISPLAY_MATERIAL("No material specified"),
+ UNKNOWN_MATERIAL("Material '%s' does not exist"),
+ UNKNOWN_ENTITY_TYPE("Entity type '%s' does not exist"),
+ TASK_MALFORMED_NOT_SECTION("Task '%s' is not a configuration section (has no fields)"),
+ TASK_MISSING_FIELD("Required field '%s' is missing for task type '%s'"),
+ UNKNOWN_TASK_REFERENCE("Attempt to reference unknown task '%s'"),
+ UNKNOWN_CATEGORY("Category '%s' does not exist"),
+ UNKNOWN_REQUIREMENT("Quest requirement '%s' does not exist");
+
+ private final String description;
+
+ ConfigProblemDescriptions(String description) {
+ this.description = description;
+ }
+
+ @Override
+ public String toString() {
+ return getDescription();
+ }
+
+ public String getDescription(String... format) {
+ return String.format(description, (Object[]) format);
+ }
+
+} \ No newline at end of file
diff --git a/common/src/main/java/com/leonardobishop/quests/common/config/QuestsConfig.java b/common/src/main/java/com/leonardobishop/quests/common/config/QuestsConfig.java
new file mode 100644
index 00000000..be93ed83
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/config/QuestsConfig.java
@@ -0,0 +1,25 @@
+package com.leonardobishop.quests.common.config;
+
+import java.util.List;
+
+public interface QuestsConfig {
+
+ boolean loadConfig();
+
+ String getString(String path);
+
+ String getString(String path, String def);
+
+ boolean getBoolean(String path);
+
+ boolean getBoolean(String path, boolean def);
+
+ int getInt(String path);
+
+ int getInt(String path, int def);
+
+ List<String> getStringList(String path);
+
+ List<String> getStringList(String path, List<String> def);
+
+}
diff --git a/common/src/main/java/com/leonardobishop/quests/common/config/QuestsLoader.java b/common/src/main/java/com/leonardobishop/quests/common/config/QuestsLoader.java
new file mode 100644
index 00000000..09db3c52
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/config/QuestsLoader.java
@@ -0,0 +1,11 @@
+package com.leonardobishop.quests.common.config;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+public interface QuestsLoader {
+
+ Map<String, List<ConfigProblem>> loadQuests(File root);
+
+}
diff --git a/common/src/main/java/com/leonardobishop/quests/common/enums/QuestStartResult.java b/common/src/main/java/com/leonardobishop/quests/common/enums/QuestStartResult.java
new file mode 100644
index 00000000..28d8a43c
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/enums/QuestStartResult.java
@@ -0,0 +1,13 @@
+package com.leonardobishop.quests.common.enums;
+
+public enum QuestStartResult {
+ QUEST_SUCCESS, //0
+ QUEST_LIMIT_REACHED, //1
+ QUEST_ALREADY_COMPLETED, //2
+ QUEST_COOLDOWN, //3
+ QUEST_LOCKED, //4
+ QUEST_ALREADY_STARTED, //5
+ QUEST_NO_PERMISSION, //6
+ NO_PERMISSION_FOR_CATEGORY, //7
+ OTHER //8
+}
diff --git a/common/src/main/java/com/leonardobishop/quests/common/logger/QuestsLogger.java b/common/src/main/java/com/leonardobishop/quests/common/logger/QuestsLogger.java
new file mode 100644
index 00000000..b4a192cf
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/logger/QuestsLogger.java
@@ -0,0 +1,44 @@
+package com.leonardobishop.quests.common.logger;
+
+public interface QuestsLogger {
+
+ LoggingLevel getServerLoggingLevel();
+
+ void setServerLoggingLevel(LoggingLevel serverLoggingLevel);
+
+ void log(String str, LoggingLevel level);
+
+ void debug(String str);
+
+ void info(String str);
+
+ void warning(String str);
+
+ void severe(String str);
+
+ enum LoggingLevel {
+ ERROR(0),
+ WARNING(1),
+ INFO(2),
+ DEBUG(3);
+
+ private int numericVerbosity;
+
+ LoggingLevel(int number) {
+ numericVerbosity = number;
+ }
+
+ public int getNumericVerbosity() {
+ return numericVerbosity;
+ }
+
+ public static LoggingLevel fromNumber(int number) {
+ for (LoggingLevel level : LoggingLevel.values()) {
+ if (level.getNumericVerbosity() == number) {
+ return level;
+ }
+ }
+ return LoggingLevel.INFO;
+ }
+ }
+}
diff --git a/common/src/main/java/com/leonardobishop/quests/common/player/QPlayer.java b/common/src/main/java/com/leonardobishop/quests/common/player/QPlayer.java
new file mode 100644
index 00000000..0792a0fc
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/player/QPlayer.java
@@ -0,0 +1,127 @@
+package com.leonardobishop.quests.common.player;
+
+import com.leonardobishop.quests.common.questcontroller.QuestController;
+import com.leonardobishop.quests.common.enums.QuestStartResult;
+import com.leonardobishop.quests.common.plugin.Quests;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgressFile;
+import com.leonardobishop.quests.common.quest.Quest;
+
+import java.util.UUID;
+
+/**
+ * Represents a player.
+ */
+public class QPlayer {
+
+ private final Quests plugin;
+ private final UUID uuid;
+ private final QPlayerPreferences playerPreferences;
+ private final QuestProgressFile questProgressFile;
+ private QuestController questController;
+
+ public QPlayer(Quests plugin, UUID uuid, QPlayerPreferences playerPreferences, QuestProgressFile questProgressFile, QuestController questController) {
+ this.plugin = plugin;
+ this.uuid = uuid;
+ this.playerPreferences = playerPreferences;
+ this.questProgressFile = questProgressFile;
+ this.questController = questController;
+ }
+
+ public UUID getPlayerUUID() {
+ return this.uuid;
+ }
+
+ /**
+ * Attempt to complete a quest for the player. This will also play all effects (such as titles, messages etc.)
+ * and also dispatches all rewards for the player.
+ *
+ * @param quest the quest to complete
+ * @return true (always)
+ */
+ public boolean completeQuest(Quest quest) {
+ return questController.completeQuestForPlayer(this, quest);
+ }
+
+ /**
+ * Attempt to track a quest for the player. This will also play all effects (such as titles, messages etc.)
+ **
+ * @param quest the quest to track
+ */
+ public void trackQuest(Quest quest) {
+ questController.trackQuestForPlayer(this, quest);
+ }
+
+ /**
+ * Gets whether or not the player has started a specific quest.
+ *
+ * @param quest the quest to test for
+ * @return true if the quest is started or quest autostart is enabled and the quest is ready to start, false otherwise
+ */
+ public boolean hasStartedQuest(Quest quest) {
+ return questController.hasPlayerStartedQuest(this, quest);
+ }
+
+ /**
+ * Attempt to start a quest for the player. This will also play all effects (such as titles, messages etc.)
+ *
+ * Warning: will fail if the player is not online.
+ *
+ * @param quest the quest to start
+ * @return the quest start result -- {@code QuestStartResult.QUEST_SUCCESS} indicates success
+ */
+ // TODO PlaceholderAPI support
+ public QuestStartResult startQuest(Quest quest) {
+ return questController.startQuestForPlayer(this, quest);
+ }
+
+ /**
+ * Attempt to cancel a quest for the player. This will also play all effects (such as titles, messages etc.)
+ *
+ * @param quest the quest to start
+ * @return true if the quest was cancelled, false otherwise
+ */
+ public boolean cancelQuest(Quest quest) {
+ return questController.cancelQuestForPlayer(this, quest);
+ }
+
+ /**
+ * Check if the player can start a quest.
+ *
+ * Warning: will fail if the player is not online.
+ *
+ * @param quest the quest to check
+ * @return the quest start result
+ */
+ public QuestStartResult canStartQuest(Quest quest) {
+ return questController.canPlayerStartQuest(this, quest);
+ }
+
+
+ public QuestProgressFile getQuestProgressFile() {
+ return questProgressFile;
+ }
+
+ public QPlayerPreferences getPlayerPreferences() {
+ return playerPreferences;
+ }
+
+ public QuestController getQuestController() {
+ return questController;
+ }
+
+ public void setQuestController(QuestController questController) {
+ this.questController = questController;
+ }
+
+ @Override //Used by java GC
+ public boolean equals(Object o) {
+ if (!(o instanceof QPlayer)) return false;
+ QPlayer qPlayer = (QPlayer) o;
+ return this.uuid == qPlayer.getPlayerUUID();
+ }
+
+ @Override //Used by java GC
+ public int hashCode() {
+ return uuid.hashCode() * 73; //uuid hash * prime number
+ }
+}
diff --git a/common/src/main/java/com/leonardobishop/quests/common/player/QPlayerManager.java b/common/src/main/java/com/leonardobishop/quests/common/player/QPlayerManager.java
new file mode 100644
index 00000000..28a46ae1
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/player/QPlayerManager.java
@@ -0,0 +1,158 @@
+package com.leonardobishop.quests.common.player;
+
+import com.leonardobishop.quests.common.logger.QuestsLogger;
+import com.leonardobishop.quests.common.storage.StorageProvider;
+import com.leonardobishop.quests.common.plugin.Quests;
+import com.leonardobishop.quests.common.questcontroller.QuestController;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgressFile;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+public class QPlayerManager {
+
+ private final Map<UUID, QPlayer> qPlayers = new ConcurrentHashMap<>();
+ private final Quests plugin;
+ private final StorageProvider storageProvider;
+ private QuestController activeQuestController;
+
+ public QPlayerManager(Quests plugin, StorageProvider storageProvider, QuestController questController) {
+ this.plugin = plugin;
+ this.storageProvider = storageProvider;
+ this.activeQuestController = questController;
+ }
+
+ /**
+ * Gets the QPlayer from a given UUID.
+ *
+ * @param uuid the uuid
+ * @return {@link QPlayer} if they are loaded
+ */
+ public QPlayer getPlayer(UUID uuid) {
+ QPlayer qPlayer = qPlayers.get(uuid);
+ if (qPlayer == null) {
+ plugin.getQuestsLogger().debug("QPlayer of " + uuid + " is null, but was requested:");
+ if (plugin.getQuestsLogger().getServerLoggingLevel() == QuestsLogger.LoggingLevel.DEBUG) {
+ Thread.dumpStack();
+ }
+ }
+ return qPlayer;
+ }
+
+ /**
+ * Unloads and schedules a save for the player. See {@link QPlayerManager#savePlayer(UUID)}
+ *
+ * @param uuid the uuid of the player
+ */
+ public void removePlayer(UUID uuid) {
+ plugin.getQuestsLogger().debug("Unloading and saving player " + uuid + ".");
+ qPlayers.computeIfPresent(uuid, (mapUUID, qPlayer) -> {
+ savePlayer(uuid);
+ return null;
+ });
+ }
+
+ /**
+ * Schedules a save for the player with the {@link QuestProgressFile} associated by the {@link QPlayerManager}.
+ * The modified status of the progress file will be reset.
+ *
+ * @param uuid the uuid of the player
+ */
+ public void savePlayer(UUID uuid) {
+ QPlayer qPlayer = getPlayer(uuid);
+ if (qPlayer == null) return;
+ savePlayer(uuid, qPlayer.getQuestProgressFile());
+ }
+
+ /**
+ * Schedules a save for the player with a specified {@link QuestProgressFile}. The modified status of the
+ * specified progress file will be reset.
+ *
+ * @param uuid the uuid of the player
+ * @param originalProgressFile the quest progress file to associate with and save
+ */
+ public void savePlayer(UUID uuid, QuestProgressFile originalProgressFile) {
+ QuestProgressFile clonedProgressFile = new QuestProgressFile(originalProgressFile);
+ originalProgressFile.resetModified();
+ plugin.getScheduler().doAsync(() -> save(uuid, clonedProgressFile));
+ }
+
+ /**
+ * Immediately saves the player with the {@link QuestProgressFile} associated by the {@link QPlayerManager},
+ * on the same thread. The modified status of the specified progress file is not changed.
+ *
+ * @param uuid the uuid of the player
+ */
+ public void savePlayerSync(UUID uuid) {
+ QPlayer qPlayer = getPlayer(uuid);
+ if (qPlayer == null) return;
+ savePlayerSync(uuid, qPlayer.getQuestProgressFile());
+ }
+
+ /**
+ * Immediately saves the player with a specified {@link QuestProgressFile}, on the same thread. The modified status
+ * of the specified progress file is not changed.
+ *
+ * @param uuid the uuid of the player
+ * @param questProgressFile the quest progress file to associate with and save
+ */
+ public void savePlayerSync(UUID uuid, QuestProgressFile questProgressFile) {
+ save(uuid, questProgressFile);
+ }
+
+ private void save(UUID uuid, QuestProgressFile questProgressFile) {
+ plugin.getQuestsLogger().debug("Saving player " + uuid + ".");
+ storageProvider.saveProgressFile(uuid, questProgressFile);
+ }
+
+ /**
+ * Unloads the player without saving to disk.
+ *
+ * @param uuid the uuid of the player
+ */
+ public void dropPlayer(UUID uuid) {
+ plugin.getQuestsLogger().debug("Dropping player " + uuid + ".");
+ qPlayers.remove(uuid);
+ }
+
+ public Collection<QPlayer> getQPlayers() {
+ return qPlayers.values();
+ }
+
+ /**
+ * Load the player if they exist, otherwise create a new {@link QuestProgressFile}.
+ * This will have no effect if player is already loaded. Can be invoked asynchronously.
+ *
+ * @param uuid the uuid of the player
+ */
+ public void loadPlayer(UUID uuid) {
+ plugin.getQuestsLogger().debug("Loading player " + uuid + ".");
+ qPlayers.computeIfAbsent(uuid, s -> {
+ QuestProgressFile questProgressFile = storageProvider.loadProgressFile(uuid);
+ if (questProgressFile == null) return null;
+ return new QPlayer(plugin, uuid, new QPlayerPreferences(null), questProgressFile, activeQuestController);
+ });
+ }
+
+ /**
+ * Gets the current storage provider which loads and saves players.
+ *
+ * @return {@link StorageProvider}
+ */
+ public StorageProvider getStorageProvider() {
+ return storageProvider;
+ }
+
+ public QuestController getActiveQuestController() {
+ return activeQuestController;
+ }
+
+ public void setActiveQuestController(QuestController activeQuestController) {
+ this.activeQuestController = activeQuestController;
+ for (QPlayer qPlayer : qPlayers.values()) {
+ qPlayer.setQuestController(activeQuestController);
+ }
+ }
+}
diff --git a/common/src/main/java/com/leonardobishop/quests/common/player/QPlayerPreferences.java b/common/src/main/java/com/leonardobishop/quests/common/player/QPlayerPreferences.java
new file mode 100644
index 00000000..5278ae09
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/player/QPlayerPreferences.java
@@ -0,0 +1,18 @@
+package com.leonardobishop.quests.common.player;
+
+public class QPlayerPreferences {
+
+ private String trackedQuestId;
+
+ public QPlayerPreferences(String trackedQuestId) {
+ this.trackedQuestId = trackedQuestId;
+ }
+
+ public String getTrackedQuestId() {
+ return trackedQuestId;
+ }
+
+ public void setTrackedQuestId(String trackedQuestId) {
+ this.trackedQuestId = trackedQuestId;
+ }
+}
diff --git a/common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/QuestProgress.java b/common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/QuestProgress.java
new file mode 100644
index 00000000..d0e0627d
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/QuestProgress.java
@@ -0,0 +1,143 @@
+package com.leonardobishop.quests.common.player.questprogressfile;
+
+import com.leonardobishop.quests.common.plugin.Quests;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+public class QuestProgress {
+
+ private final Quests plugin;
+
+ private final Map<String, TaskProgress> taskProgress = new HashMap<>();
+ private final String questid;
+ private final UUID player;
+
+ private boolean started;
+ private boolean completed;
+ private boolean completedBefore;
+ private long completionDate;
+ private boolean modified;
+
+ public QuestProgress(Quests plugin, String questid, boolean completed, boolean completedBefore, long completionDate, UUID player, boolean started) {
+ this.plugin = plugin;
+ this.questid = questid;
+ this.completed = completed;
+ this.completedBefore = completedBefore;
+ this.completionDate = completionDate;
+ this.player = player;
+ this.started = started;
+ }
+
+ public QuestProgress(Quests plugin, String questid, boolean completed, boolean completedBefore, long completionDate, UUID player, boolean started, boolean modified) {
+ this(plugin, questid, completed, completedBefore, completionDate, player, started);
+ this.modified = modified;
+ }
+
+ public QuestProgress(QuestProgress questProgress) {
+ this.plugin = questProgress.plugin;
+ for (Map.Entry<String, TaskProgress> progressEntry : questProgress.taskProgress.entrySet()) {
+ taskProgress.put(progressEntry.getKey(), new TaskProgress(progressEntry.getValue()));
+ }
+ this.questid = questProgress.questid;
+ this.player = questProgress.player;
+ this.started = questProgress.started;
+ this.completed = questProgress.completed;
+ this.completedBefore = questProgress.completedBefore;
+ this.completionDate = questProgress.completionDate;
+ this.modified = questProgress.modified;
+ }
+
+ public String getQuestId() {
+ return questid;
+ }
+
+ public boolean isCompleted() {
+ return completed;
+ }
+
+ public void setCompleted(boolean completed) {
+ this.completed = completed;
+ this.modified = true;
+ }
+
+ public boolean isStarted() {
+ return started;
+ }
+
+ public void setStarted(boolean started) {
+ this.started = started;
+ this.modified = true;
+ }
+
+ public long getCompletionDate() {
+ return completionDate;
+ }
+
+ public void setCompletionDate(long completionDate) {
+ this.completionDate = completionDate;
+ this.modified = true;
+ }
+
+ public UUID getPlayer() {
+ return player;
+ }
+
+ public boolean isCompletedBefore() {
+ return completedBefore;
+ }
+
+ public void setCompletedBefore(boolean completedBefore) {
+ this.completedBefore = completedBefore;
+ this.modified = true;
+ }
+
+ public void addTaskProgress(TaskProgress taskProgress) {
+ this.taskProgress.put(taskProgress.getTaskId(), taskProgress);
+ }
+
+ public Collection<TaskProgress> getTaskProgress() {
+ return taskProgress.values();
+ }
+
+ public Map<String, TaskProgress> getTaskProgressMap() {
+ return taskProgress;
+ }
+
+ public TaskProgress getTaskProgress(String taskId) {
+ TaskProgress tP = taskProgress.getOrDefault(taskId, null);
+ if (tP == null) {
+ repairTaskProgress(taskId);
+ tP = taskProgress.getOrDefault(taskId, null);
+ }
+ return tP;
+ }
+
+ public void repairTaskProgress(String taskid) {
+ TaskProgress taskProgress = new TaskProgress(this, taskid, null, player, false, false);
+ this.addTaskProgress(taskProgress);
+ }
+
+ public boolean isModified() {
+ if (modified) return true;
+ else {
+ for (TaskProgress progress : this.taskProgress.values()) {
+ if (progress.isModified()) return true;
+ }
+ return false;
+ }
+ }
+
+ public void queueForCompletionTest() {
+ plugin.getQuestCompleter().queueSingular(this);
+ }
+
+ public void resetModified() {
+ this.modified = false;
+ for (TaskProgress progress : this.taskProgress.values()) {
+ progress.resetModified();
+ }
+ }
+}
diff --git a/common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/QuestProgressFile.java b/common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/QuestProgressFile.java
new file mode 100644
index 00000000..a0c51d9c
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/QuestProgressFile.java
@@ -0,0 +1,253 @@
+package com.leonardobishop.quests.common.player.questprogressfile;
+
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.plugin.Quests;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Represents underlying quest progress for a player.
+ */
+public class QuestProgressFile {
+
+ private final Map<String, QuestProgress> questProgress = new HashMap<>();
+ private final UUID playerUUID;
+ private final Quests plugin;
+
+ public QuestProgressFile(UUID playerUUID, Quests plugin) {
+ this.playerUUID = playerUUID;
+ this.plugin = plugin;
+ }
+
+ public QuestProgressFile(QuestProgressFile questProgressFile) {
+ for (Map.Entry<String, QuestProgress> progressEntry : questProgressFile.questProgress.entrySet()) {
+ questProgress.put(progressEntry.getKey(), new QuestProgress(progressEntry.getValue()));
+ }
+ this.playerUUID = questProgressFile.playerUUID;
+ this.plugin = questProgressFile.plugin;
+ }
+
+ public void addQuestProgress(QuestProgress questProgress) {
+ //TODO don't do here
+// if (Options.VERIFY_QUEST_EXISTS_ON_LOAD.getBooleanValue(true) && plugin.getQuestManager().getQuestById(questProgress.getQuestId()) == null) {
+// return;
+// }
+ this.questProgress.put(questProgress.getQuestId(), questProgress);
+ }
+
+ /**
+ * Gets all started quests.
+ * Note: if quest autostart is enabled then this may produce unexpected results as quests are
+ * not "started" by the player if autostart is true. Consider {@link QPlayer#hasStartedQuest(Quest)} instead.
+ *
+ * @return list of started quests
+ */
+ public List<Quest> getStartedQuests() {
+ List<Quest> startedQuests = new ArrayList<>();
+ for (QuestProgress questProgress : questProgress.values()) {
+ if (questProgress.isStarted()) {
+ startedQuests.add(plugin.getQuestManager().getQuestById(questProgress.getQuestId()));
+ }
+ }
+ return startedQuests;
+ }
+
+ /**
+ * Returns all {@link Quest} a player has encountered
+ * (not to be confused with a collection of quest progress)
+ *
+ * @return {@code List<Quest>} all quests
+ */
+ public List<Quest> getAllQuestsFromProgress(QuestsProgressFilter filter) {
+ List<Quest> questsProgress = new ArrayList<>();
+ for (QuestProgress qProgress : questProgress.values()) {
+ boolean condition = false;
+ if (filter == QuestsProgressFilter.STARTED) {
+ condition = qProgress.isStarted();
+ } else if (filter == QuestsProgressFilter.COMPLETED_BEFORE) {
+ condition = qProgress.isCompletedBefore();
+ } else if (filter == QuestsProgressFilter.COMPLETED) {
+ condition = qProgress.isCompleted();
+ } else if (filter == QuestsProgressFilter.ALL) {
+ condition = true;
+ }
+ if (condition) {
+ Quest quest = plugin.getQuestManager().getQuestById(qProgress.getQuestId());
+ if (quest != null) {
+ questsProgress.add(quest);
+ }
+ }
+ }
+ return questsProgress;
+ }
+
+ public enum QuestsProgressFilter {
+ ALL("all"),
+ COMPLETED("completed"),
+ COMPLETED_BEFORE("completedBefore"),
+ STARTED("started");
+
+ private String legacy;
+
+ QuestsProgressFilter(String legacy) {
+ this.legacy = legacy;
+ }
+
+ public static QuestsProgressFilter fromLegacy(String filter) {
+ for (QuestsProgressFilter filterEnum : QuestsProgressFilter.values()) {
+ if (filterEnum.getLegacy().equals(filter)) return filterEnum;
+ }
+ return QuestsProgressFilter.ALL;
+ }
+
+ public String getLegacy() {
+ return legacy;
+ }
+ }
+
+ /**
+ * Gets all the quest progress that it has ever encountered.
+ *
+ * @return {@code Collection<QuestProgress>} all quest progresses
+ */
+ public Collection<QuestProgress> getAllQuestProgress() {
+ return questProgress.values();
+ }
+
+ /**
+ * Checks whether or not the player has {@link QuestProgress} for a specified quest
+ *
+ * @param quest the quest to check for
+ * @return true if they have quest progress
+ */
+ public boolean hasQuestProgress(Quest quest) {
+ return questProgress.containsKey(quest.getId());
+ }
+
+ /**
+ * Gets the remaining cooldown before being able to start a specific quest.
+ *
+ * @param quest the quest to test for
+ * @return 0 if no cooldown remaining or the cooldown is disabled, otherwise the cooldown in milliseconds
+ */
+ public long getCooldownFor(Quest quest) {
+ QuestProgress questProgress = getQuestProgress(quest);
+ if (quest.isCooldownEnabled() && questProgress.isCompleted()) {
+ if (questProgress.getCompletionDate() > 0) {
+ long date = questProgress.getCompletionDate();
+ return (date + TimeUnit.MILLISECONDS.convert(quest.getCooldown(), TimeUnit.MINUTES)) - System.currentTimeMillis();
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Tests whether or not the player meets the requirements to start a specific quest.
+ *
+ * @param quest the quest to test for
+ * @return true if they can start the quest
+ */
+ //TODO possibly move this
+ public boolean hasMetRequirements(Quest quest) {
+ for (String id : quest.getRequirements()) {
+ Quest q = plugin.getQuestManager().getQuestById(id);
+ if (q == null) {
+ continue;
+ }
+ if (hasQuestProgress(q) && !getQuestProgress(q).isCompletedBefore()) {
+ return false;
+ } else if (!hasQuestProgress(q)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Get the {@link UUID} of the player this QuestProgressFile represents
+ *
+ * @return UUID
+ */
+ public UUID getPlayerUUID() {
+ return playerUUID;
+ }
+
+ /**
+ * Get the {@link QuestProgress} for a specified {@link Quest}, and generates a new one if it does not exist
+ *
+ * @param quest the quest to get progress for
+ * @return {@link QuestProgress} or null if the quest does not exist
+ */
+ public QuestProgress getQuestProgress(Quest quest) {
+ if (questProgress.containsKey(quest.getId())) {
+ return questProgress.get(quest.getId());
+ }
+ generateBlankQuestProgress(quest);
+ return getQuestProgress(quest);
+ }
+
+ /**
+ * Generate a new blank {@link QuestProgress} for a specified {@code quest}.
+ * Has no effect if there is already an existing {@link QuestProgress} for {@code quest}.
+ *
+ * @param quest the quest to generate progress for
+ */
+ public void generateBlankQuestProgress(Quest quest) {
+ QuestProgress questProgress = new QuestProgress(plugin, quest.getId(), false, false, 0, playerUUID, false, false);
+ for (Task task : quest.getTasks()) {
+ TaskProgress taskProgress = new TaskProgress(questProgress, task.getId(), null, playerUUID, false, false);
+ questProgress.addTaskProgress(taskProgress);
+ }
+
+ addQuestProgress(questProgress);
+ }
+
+ public void clear() {
+ questProgress.clear();
+ }
+
+ /**
+ * Removes any references to quests or tasks which are no longer defined in the config.
+ */
+ @Deprecated
+ public void clean() {
+ plugin.getQuestsLogger().debug("Cleaning file " + playerUUID + ".");
+ if (!plugin.getTaskTypeManager().areRegistrationsAccepted()) {
+ ArrayList<String> invalidQuests = new ArrayList<>();
+ for (String questId : this.questProgress.keySet()) {
+ Quest q;
+ if ((q = plugin.getQuestManager().getQuestById(questId)) == null) {
+ invalidQuests.add(questId);
+ } else {
+ ArrayList<String> invalidTasks = new ArrayList<>();
+ for (String taskId : this.questProgress.get(questId).getTaskProgressMap().keySet()) {
+ if (q.getTaskById(taskId) == null) {
+ invalidTasks.add(taskId);
+ }
+ }
+ for (String taskId : invalidTasks) {
+ this.questProgress.get(questId).getTaskProgressMap().remove(taskId);
+ }
+ }
+ }
+ for (String questId : invalidQuests) {
+ this.questProgress.remove(questId);
+ }
+ }
+ }
+
+ public void resetModified() {
+ for (QuestProgress questProgress : questProgress.values()) {
+ questProgress.resetModified();
+ }
+ }
+
+}
diff --git a/common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/TaskProgress.java b/common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/TaskProgress.java
new file mode 100644
index 00000000..cb7f3fec
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/TaskProgress.java
@@ -0,0 +1,73 @@
+package com.leonardobishop.quests.common.player.questprogressfile;
+
+import java.util.UUID;
+
+public class TaskProgress {
+
+ private final String taskid;
+ private final UUID player;
+
+ private QuestProgress linkedQuestProgress;
+ private boolean modified;
+ private Object progress;
+ private boolean completed;
+
+ public TaskProgress(QuestProgress linkedQuestProgress, String taskid, Object progress, UUID player, boolean completed) {
+ this.linkedQuestProgress = linkedQuestProgress;
+ this.taskid = taskid;
+ this.progress = progress;
+ this.player = player;
+ this.completed = completed;
+ }
+
+ public TaskProgress(QuestProgress linkedQuestProgress, String taskid, Object progress, UUID player, boolean completed, boolean modified) {
+ this(linkedQuestProgress, taskid, progress, player, completed);
+ this.modified = modified;
+ }
+
+ public TaskProgress(TaskProgress taskProgress) {
+ this.taskid = taskProgress.taskid;
+ this.player = taskProgress.player;
+ this.modified = taskProgress.modified;
+ this.progress = taskProgress.progress;
+ this.completed = taskProgress.completed;
+ }
+
+ public String getTaskId() {
+ return taskid;
+ }
+
+ public Object getProgress() {
+ return progress;
+ }
+
+ public void setProgress(Object progress) {
+ this.progress = progress;
+ this.modified = true;
+ }
+
+ public UUID getPlayer() {
+ return player;
+ }
+
+ public boolean isCompleted() {
+ return completed;
+ }
+
+ public void setCompleted(boolean complete) {
+ this.completed = complete;
+ this.modified = true;
+
+ if (complete) {
+ linkedQuestProgress.queueForCompletionTest();
+ }
+ }
+
+ public boolean isModified() {
+ return modified;
+ }
+
+ public void resetModified() {
+ this.modified = false;
+ }
+}
diff --git a/common/src/main/java/com/leonardobishop/quests/common/plugin/Quests.java b/common/src/main/java/com/leonardobishop/quests/common/plugin/Quests.java
new file mode 100644
index 00000000..f6eb67c5
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/plugin/Quests.java
@@ -0,0 +1,38 @@
+package com.leonardobishop.quests.common.plugin;
+
+import com.leonardobishop.quests.common.questcontroller.QuestController;
+import com.leonardobishop.quests.common.logger.QuestsLogger;
+import com.leonardobishop.quests.common.player.QPlayerManager;
+import com.leonardobishop.quests.common.quest.QuestCompleter;
+import com.leonardobishop.quests.common.quest.QuestManager;
+import com.leonardobishop.quests.common.scheduler.ServerScheduler;
+import com.leonardobishop.quests.common.storage.StorageProvider;
+import com.leonardobishop.quests.common.tasktype.TaskTypeManager;
+import com.leonardobishop.quests.common.updater.Updater;
+import com.leonardobishop.quests.common.config.QuestsConfig;
+
+public interface Quests {
+
+ QuestsLogger getQuestsLogger();
+
+ QuestManager getQuestManager();
+
+ QPlayerManager getPlayerManager();
+
+ QuestController getQuestController();
+
+ TaskTypeManager getTaskTypeManager();
+
+ QuestCompleter getQuestCompleter();
+
+ QuestsConfig getQuestsConfig();
+
+ Updater getUpdater();
+
+ ServerScheduler getScheduler();
+
+ StorageProvider getStorageProvider();
+
+ void reloadQuests();
+
+}
diff --git a/common/src/main/java/com/leonardobishop/quests/common/quest/Category.java b/common/src/main/java/com/leonardobishop/quests/common/quest/Category.java
new file mode 100644
index 00000000..7bd8f10d
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/quest/Category.java
@@ -0,0 +1,33 @@
+package com.leonardobishop.quests.common.quest;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Category {
+
+ private final String id;
+ private final boolean permissionRequired;
+ private final List<String> registeredQuestIds = new ArrayList<>();
+
+ public Category(String id, boolean permissionRequired) {
+ this.id = id;
+ this.permissionRequired = permissionRequired;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public boolean isPermissionRequired() {
+ return permissionRequired;
+ }
+
+ public void registerQuestId(String questid) {
+ registeredQuestIds.add(questid);
+ }
+
+ public List<String> getRegisteredQuestIds() {
+ return registeredQuestIds;
+ }
+
+}
diff --git a/common/src/main/java/com/leonardobishop/quests/common/quest/Quest.java b/common/src/main/java/com/leonardobishop/quests/common/quest/Quest.java
new file mode 100644
index 00000000..510e88d9
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/quest/Quest.java
@@ -0,0 +1,120 @@
+package com.leonardobishop.quests.common.quest;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class Quest implements Comparable<Quest> {
+
+ private Map<String, Task> tasks = new HashMap<>();
+ private final String id;
+ private final List<String> rewards;
+ private final List<String> requirements;
+ private final List<String> rewardString;
+ private final List<String> startString;
+ private final boolean repeatable;
+ private final boolean cooldownEnabled;
+ private final int cooldown;
+ private final int sortOrder;
+ private final boolean permissionRequired;
+ private final Map<String, String> placeholders;
+ private String categoryid;
+
+
+ public Quest(String id, List<String> rewards, List<String> requirements, boolean repeatable, boolean cooldownEnabled, int cooldown, boolean permissionRequired, List<String> rewardString, List<String> startString, Map<String, String> placeholders, String categoryid, int sortOrder) {
+ this(id, rewards, requirements, repeatable, cooldownEnabled, cooldown, permissionRequired, rewardString, startString, placeholders, sortOrder);
+ this.categoryid = categoryid;
+ }
+
+ public Quest(String id, List<String> rewards, List<String> requirements, boolean repeatable, boolean cooldownEnabled, int cooldown, boolean permissionRequired, List<String> rewardString, List<String> startString, Map<String, String> placeholders, int sortOrder) {
+ this.id = id;
+ this.rewards = rewards;
+ this.requirements = requirements;
+ this.repeatable = repeatable;
+ this.cooldownEnabled = cooldownEnabled;
+ this.cooldown = cooldown;
+ this.permissionRequired = permissionRequired;
+ this.rewardString = rewardString;
+ this.startString = startString;
+ this.placeholders = placeholders;
+ this.sortOrder = sortOrder;
+ }
+
+ public void registerTask(Task task) {
+ tasks.put(task.getId(), task);
+ }
+
+ public Collection<Task> getTasks() {
+ return tasks.values();
+ }
+
+ public Task getTaskById(String id) {
+ return tasks.get(id);
+ }
+
+ public List<Task> getTasksOfType(String type) {
+ List<Task> tasks = new ArrayList<>();
+ for (Task task : getTasks()) {
+ if (task.getType().equals(type)) {
+ tasks.add(task);
+ }
+ }
+ return tasks;
+ }
+
+
+ public boolean isPermissionRequired() {
+ return permissionRequired;
+ }
+
+ public List<String> getRewardString() {
+ return rewardString;
+ }
+
+ public List<String> getStartString() {
+ return startString;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public List<String> getRewards() {
+ return rewards;
+ }
+
+ public List<String> getRequirements() {
+ return requirements;
+ }
+
+ public boolean isRepeatable() {
+ return repeatable;
+ }
+
+ public boolean isCooldownEnabled() {
+ return cooldownEnabled;
+ }
+
+ public int getCooldown() {
+ return cooldown;
+ }
+
+ public String getCategoryId() {
+ return categoryid;
+ }
+
+ public Map<String, String> getPlaceholders() {
+ return placeholders;
+ }
+
+ public int getSortOrder() {
+ return sortOrder;
+ }
+
+ @Override
+ public int compareTo(Quest quest) {
+ return (sortOrder - quest.sortOrder);
+ }
+}
diff --git a/common/src/main/java/com/leonardobishop/quests/common/quest/QuestCompleter.java b/common/src/main/java/com/leonardobishop/quests/common/quest/QuestCompleter.java
new file mode 100644
index 00000000..db9cd36c
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/quest/QuestCompleter.java
@@ -0,0 +1,11 @@
+package com.leonardobishop.quests.common.quest;
+
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgress;
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgressFile;
+
+public interface QuestCompleter {
+
+ void queueSingular(QuestProgress questProgress);
+ void queueFullCheck(QuestProgressFile questProgressFile);
+
+}
diff --git a/common/src/main/java/com/leonardobishop/quests/common/quest/QuestManager.java b/common/src/main/java/com/leonardobishop/quests/common/quest/QuestManager.java
new file mode 100644
index 00000000..ebace123
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/quest/QuestManager.java
@@ -0,0 +1,49 @@
+package com.leonardobishop.quests.common.quest;
+
+import com.leonardobishop.quests.common.plugin.Quests;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class QuestManager {
+
+ private final Quests plugin;
+
+ public QuestManager(Quests plugin) {
+ this.plugin = plugin;
+ }
+
+ private Map<String, Quest> quests = new LinkedHashMap<>();
+ private List<Category> categories = new ArrayList<>();
+
+ public void registerQuest(Quest quest) {
+ quests.put(quest.getId(), quest);
+ }
+
+ public Quest getQuestById(String id) {
+ return quests.get(id);
+ }
+
+ public Map<String, Quest> getQuests() {
+ return quests;
+ }
+
+ public void registerCategory(Category category) { categories.add(category); }
+
+ public List<Category> getCategories() {
+ return categories;
+ }
+
+ public Category getCategoryById(String id) {
+ for (Category category : categories) {
+ if (category.getId().equals(id)) return category;
+ }
+ return null;
+ }
+
+ public Quests getPlugin() {
+ return this.plugin;
+ }
+}
diff --git a/common/src/main/java/com/leonardobishop/quests/common/quest/Task.java b/common/src/main/java/com/leonardobishop/quests/common/quest/Task.java
new file mode 100644
index 00000000..8189e06e
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/quest/Task.java
@@ -0,0 +1,41 @@
+package com.leonardobishop.quests.common.quest;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class Task {
+
+ private final Map<String, Object> configValues = new HashMap<>();
+ private final String id;
+ private final String type;
+
+ public Task(String id, String type) {
+ this.id = id;
+ this.type = type;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public Object getConfigValue(String key) {
+ return configValues.getOrDefault(key, null); //??? this will return null without the need of `OrDefault(key, null)`
+ }
+
+ public Object getConfigValue(String key, Object def) {
+ return configValues.getOrDefault(key, def);
+ }
+
+ public Map<String, Object> getConfigValues() {
+ return configValues;
+ }
+
+ public void addConfigValue(String key, Object value) {
+ configValues.put(key, value);
+ }
+
+}
diff --git a/common/src/main/java/com/leonardobishop/quests/common/questcontroller/QuestController.java b/common/src/main/java/com/leonardobishop/quests/common/questcontroller/QuestController.java
new file mode 100644
index 00000000..0f5b6e8f
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/questcontroller/QuestController.java
@@ -0,0 +1,23 @@
+package com.leonardobishop.quests.common.questcontroller;
+
+import com.leonardobishop.quests.common.enums.QuestStartResult;
+import com.leonardobishop.quests.common.player.QPlayer;
+import com.leonardobishop.quests.common.quest.Quest;
+
+public interface QuestController {
+
+ String getName();
+
+ QuestStartResult startQuestForPlayer(QPlayer qPlayer, Quest quest);
+
+ QuestStartResult canPlayerStartQuest(QPlayer qPlayer, Quest quest);
+
+ boolean completeQuestForPlayer(QPlayer qPlayer, Quest quest);
+
+ boolean hasPlayerStartedQuest(QPlayer qPlayer, Quest quest);
+
+ boolean cancelQuestForPlayer(QPlayer qPlayer, Quest quest);
+
+ void trackQuestForPlayer(QPlayer qPlayer, Quest quest);
+
+}
diff --git a/common/src/main/java/com/leonardobishop/quests/common/scheduler/ServerScheduler.java b/common/src/main/java/com/leonardobishop/quests/common/scheduler/ServerScheduler.java
new file mode 100644
index 00000000..0de30f51
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/scheduler/ServerScheduler.java
@@ -0,0 +1,8 @@
+package com.leonardobishop.quests.common.scheduler;
+
+public interface ServerScheduler {
+
+ void doSync(Runnable runnable);
+ void doAsync(Runnable runnable);
+
+}
diff --git a/common/src/main/java/com/leonardobishop/quests/common/storage/StorageProvider.java b/common/src/main/java/com/leonardobishop/quests/common/storage/StorageProvider.java
new file mode 100644
index 00000000..4ea885be
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/storage/StorageProvider.java
@@ -0,0 +1,17 @@
+package com.leonardobishop.quests.common.storage;
+
+import com.leonardobishop.quests.common.player.questprogressfile.QuestProgressFile;
+
+import java.util.UUID;
+
+public interface StorageProvider {
+
+ void init();
+
+ void shutdown();
+
+ QuestProgressFile loadProgressFile(UUID uuid);
+
+ void saveProgressFile(UUID uuid, QuestProgressFile questProgressFile);
+
+}
diff --git a/common/src/main/java/com/leonardobishop/quests/common/tasktype/TaskType.java b/common/src/main/java/com/leonardobishop/quests/common/tasktype/TaskType.java
new file mode 100644
index 00000000..79c7a0b0
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/tasktype/TaskType.java
@@ -0,0 +1,108 @@
+package com.leonardobishop.quests.common.tasktype;
+
+import com.leonardobishop.quests.common.config.ConfigProblem;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * A task type which can be used within Quests. A {@link Quest}
+ * will be registered to this if it contains at least 1 task
+ * which is of this type. This is so you do not have to
+ * iterate through every single quest.
+ */
+public abstract class TaskType {
+
+ private final List<Quest> quests = new ArrayList<>();
+ private final String type;
+ private String author;
+ private String description;
+
+ /**
+ * @param type the name of the task type, should not contain spaces
+ * @param author the name of the person (or people) who wrote it
+ * @param description a short, simple description of the task type
+ */
+ public TaskType(String type, String author, String description) {
+ this.type = type;
+ this.author = author;
+ this.description = description;
+ }
+
+ /**
+ * @param type the name of the task type, should not contain spaces
+ */
+ public TaskType(String type) {
+ this.type = type;
+ }
+
+ /**
+ * Registers a {@link Quest} to this task type. This is usually done when
+ * all the quests are initially loaded.
+ *
+ * @param quest the {@link Quest} to register.
+ */
+ public final void registerQuest(Quest quest) {
+ if (!quests.contains(quest)) {
+ quests.add(quest);
+ }
+ }
+
+ /**
+ * Clears the list which contains the registered quests.
+ */
+ public final void unregisterAll() {
+ quests.clear();
+ }
+
+ /**
+ * @return {@link List} of type {@link Quest} of all registered quests.
+ */
+ public final List<Quest> getRegisteredQuests() {
+ return quests;
+ }
+
+ public final String getType() {
+ return type;
+ }
+
+ public String getAuthor() {
+ return author;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * Called when Quests has finished registering all quests to the task type
+ * May be called several times if an operator uses /quests admin reload
+ */
+ public void onReady() {
+ // not implemented here
+ }
+
+ /**
+ * Called when a player starts a quest containing a task of this type
+ */
+ public void onStart(Quest quest, Task task, UUID playerUUID) {
+ // not implemented here
+ }
+
+ public void onDisable() {
+ // not implemented here
+ }
+
+ /**
+ * Called when Quests reloads the configuration - used to detect errors in the configuration of your task type
+ */
+ public List<ConfigProblem> validateConfig(String root, HashMap<String, Object> config) {
+ // not implemented here
+ return Collections.emptyList();
+ }
+}
diff --git a/common/src/main/java/com/leonardobishop/quests/common/tasktype/TaskTypeManager.java b/common/src/main/java/com/leonardobishop/quests/common/tasktype/TaskTypeManager.java
new file mode 100644
index 00000000..dfa5124e
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/tasktype/TaskTypeManager.java
@@ -0,0 +1,67 @@
+package com.leonardobishop.quests.common.tasktype;
+
+import com.leonardobishop.quests.common.plugin.Quests;
+import com.leonardobishop.quests.common.quest.Quest;
+import com.leonardobishop.quests.common.quest.Task;
+
+import java.util.ArrayList;
+
+public abstract class TaskTypeManager {
+
+ private final Quests plugin;
+ private boolean allowRegistrations;
+
+ public TaskTypeManager(Quests plugin) {
+ this.plugin = plugin;
+ allowRegistrations = true;
+ }
+
+ public void closeRegistrations() {
+ allowRegistrations = false;
+ }
+
+ public boolean areRegistrationsAccepted() {
+ return allowRegistrations;
+ }
+
+ private ArrayList<TaskType> taskTypes = new ArrayList<>();
+
+ public ArrayList<TaskType> getTaskTypes() {
+ return taskTypes;
+ }
+
+ public void resetTaskTypes() {
+ for (TaskType taskType : taskTypes) {
+ taskType.getRegisteredQuests().clear();
+ }
+ }
+
+ public void registerTaskType(TaskType taskType) {
+ if (!allowRegistrations) {
+ throw new IllegalStateException("No longer accepting new task types (must be done before quests are loaded)");
+ }
+ plugin.getQuestsLogger().info("Task type " + taskType.getType() + " has been registered.");
+ taskTypes.add(taskType);
+ }
+
+ public void registerQuestTasksWithTaskTypes(Quest quest) {
+ if (allowRegistrations) {
+ throw new IllegalStateException("Still accepting new task types (type registrations must be closed before registering quests)");
+ }
+ for (Task task : quest.getTasks()) {
+ TaskType t;
+ if ((t = getTaskType(task.getType())) != null) {
+ t.registerQuest(quest);
+ }
+ }
+ }
+
+ public TaskType getTaskType(String string) {
+ for (TaskType taskType : taskTypes) {
+ if (taskType.getType().equalsIgnoreCase(string)) {
+ return taskType;
+ }
+ }
+ return null;
+ }
+}
diff --git a/common/src/main/java/com/leonardobishop/quests/common/updater/Updater.java b/common/src/main/java/com/leonardobishop/quests/common/updater/Updater.java
new file mode 100644
index 00000000..70660735
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/updater/Updater.java
@@ -0,0 +1,78 @@
+package com.leonardobishop.quests.common.updater;
+
+import com.leonardobishop.quests.common.plugin.Quests;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.concurrent.TimeUnit;
+
+public class Updater {
+
+ private static final int PROJECT_ID = 23696;
+ private final String installedVersion;
+ private final boolean enabled;
+ private final Quests plugin;
+
+ private String returnedVersion;
+ private URL api;
+ private boolean updateReady;
+ private long lastCheck;
+
+ public Updater(Quests plugin, String installedVersion, boolean enabled) {
+ this.plugin = plugin;
+ this.installedVersion = installedVersion;
+ this.enabled = enabled;
+ try {
+ this.api = new URL(getApiUrl());
+ } catch (MalformedURLException ignored) { }
+ }
+
+ public String getUpdateLink() {
+ return "https://www.spigotmc.org/resources/" + PROJECT_ID;
+ }
+
+ public String getApiUrl() {
+ return "https://api.spigotmc.org/legacy/update.php?resource=" + PROJECT_ID;
+ }
+
+ public String getInstalledVersion() {
+ return installedVersion;
+ }
+
+ public String getReturnedVersion() {
+ return returnedVersion;
+ }
+
+ public void check() {
+ if (!enabled) {
+ return;
+ }
+ // stop users from spamming the command and making needless requests
+ if (lastCheck != 0 && TimeUnit.MINUTES.convert(System.currentTimeMillis() - lastCheck, TimeUnit.MILLISECONDS) < 10) {
+ return;
+ }
+ try {
+ lastCheck = System.currentTimeMillis();
+ URLConnection con = api.openConnection();
+ returnedVersion = new BufferedReader(new InputStreamReader(con.getInputStream())).readLine();
+ if (!returnedVersion.equals(installedVersion)) {
+ plugin.getQuestsLogger().info("A new version " + returnedVersion + " was found on Spigot (your version: " + installedVersion + "). Please update me! <3 - Link: " + getUpdateLink());
+ updateReady = true;
+ } else {
+ updateReady = false;
+ }
+ } catch (IOException e) {
+ plugin.getQuestsLogger().warning("Failed to check for updates. You can check manually at " + getUpdateLink());
+ // probably offline
+ }
+ }
+
+ public boolean isUpdateReady() {
+ return updateReady;
+ }
+
+} \ No newline at end of file
diff --git a/common/src/main/java/com/leonardobishop/quests/common/util/Format.java b/common/src/main/java/com/leonardobishop/quests/common/util/Format.java
new file mode 100644
index 00000000..410462ee
--- /dev/null
+++ b/common/src/main/java/com/leonardobishop/quests/common/util/Format.java
@@ -0,0 +1,21 @@
+package com.leonardobishop.quests.common.util;
+
+public class Format {
+
+ //TODO reimplement customisation
+ public static String formatTime(long sec) {
+ long hours = sec / 3600;
+ long minutes = (sec % 3600) / 60;
+ long seconds = ((sec % 3600) % 60) % 60;
+
+// return Messages.TIME_FORMAT.getMessage()
+// .replace("{hours}", String.format("%02d", hours))
+// .replace("{minutes}", String.format("%02d", minutes))
+// .replace("{seconds}", String.format("%02d", seconds));
+ return "{houes}h {minutes}m {seconds}s"
+ .replace("{hours}", String.format("%02d", hours))
+ .replace("{minutes}", String.format("%02d", minutes))
+ .replace("{seconds}", String.format("%02d", seconds));
+ }
+
+}