summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLMBishop <13875753+LMBishop@users.noreply.github.com>2022-05-01 18:48:21 +0100
committerLMBishop <13875753+LMBishop@users.noreply.github.com>2022-05-01 18:48:21 +0100
commit8068d1efc179e60e5b01a1733c392f5b3de21549 (patch)
tree21803b247438d81e08305e34b1ec177d43cdc1bf
parent649bb972c9bcd587a5d1b2d495922db5901d05db (diff)
Add time-limit option and quest expiry functionality (closes #379)
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerExpireQuestEvent.java51
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/config/BukkitQuestsLoader.java6
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/questcompleter/BukkitQuestCompleter.java57
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/questcontroller/NormalQuestController.java42
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/storage/MySqlStorageProvider.java4
-rw-r--r--bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/Messages.java1
-rw-r--r--bukkit/src/main/resources/resources/bukkit/config.yml1
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/player/QPlayer.java12
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/player/questprogressfile/QuestProgressFile.java19
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/quest/Quest.java37
-rw-r--r--common/src/main/java/com/leonardobishop/quests/common/questcontroller/QuestController.java2
11 files changed, 213 insertions, 19 deletions
diff --git a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerExpireQuestEvent.java b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerExpireQuestEvent.java
new file mode 100644
index 00000000..3e7c87a3
--- /dev/null
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/api/event/PlayerExpireQuestEvent.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 PlayerExpireQuestEvent extends PlayerQuestEvent {
+ private final static HandlerList handlers = new HandlerList();
+ private final QuestProgress questProgress;
+ private String questExpireMessage;
+
+ public PlayerExpireQuestEvent(@NotNull Player who, @NotNull QPlayer questPlayer, @NotNull QuestProgress questProgress, String questExpireMessage) {
+ super(who, questPlayer);
+ this.questProgress = questProgress;
+ this.questExpireMessage = questExpireMessage;
+ }
+
+ /**
+ * @return The quest progress
+ */
+ public QuestProgress getQuestProgress() {
+ return this.questProgress;
+ }
+
+ /**
+ * @return The message sent to the player
+ */
+ public String getQuestExpireMessage() {
+ return this.questExpireMessage;
+ }
+
+ /**
+ * @param questExpireMessage The quest expire message
+ * @return The quest expire message set
+ */
+ public String setQuestExpireMessage(String questExpireMessage) {
+ return (this.questExpireMessage = questExpireMessage);
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+}
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
index 282b0d61..9291caee 100644
--- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/config/BukkitQuestsLoader.java
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/config/BukkitQuestsLoader.java
@@ -223,11 +223,13 @@ public class BukkitQuestsLoader implements QuestsLoader {
List<String> startCommands = config.getStringList("startcommands");
boolean repeatable = config.getBoolean("options.repeatable", false);
boolean cooldown = config.getBoolean("options.cooldown.enabled", false);
+ boolean timeLimit = config.getBoolean("options.time-limit.enabled", false);
boolean permissionRequired = config.getBoolean("options.permission-required", false);
boolean autostart = config.getBoolean("options.autostart", false);
boolean cancellable = config.getBoolean("options.cancellable", true);
boolean countsTowardsLimit = config.getBoolean("options.counts-towards-limit", true);
int cooldownTime = config.getInt("options.cooldown.time", 10);
+ int timeLimtTime = config.getInt("options.time-limit.time", 10);
int sortOrder = config.getInt("options.sort-order", 1);
String category = config.getString("options.category");
Map<String, String> placeholders = new HashMap<>();
@@ -250,12 +252,14 @@ public class BukkitQuestsLoader implements QuestsLoader {
.withStartCommands(startCommands)
.withPlaceholders(placeholders)
.withCooldown(cooldownTime)
+ .withTimeLimit(timeLimtTime)
.withSortOrder(sortOrder)
.withCooldownEnabled(cooldown)
+ .withTimeLimitEnabled(timeLimit)
.withPermissionRequired(permissionRequired)
.withRepeatEnabled(repeatable)
.withCancellable(cancellable)
- .withCancellable(countsTowardsLimit)
+ .withCountsTowardsLimit(countsTowardsLimit)
.withAutoStartEnabled(autostart)
.inCategory(category)
.build();
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
index a981dc15..95c20f68 100644
--- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/questcompleter/BukkitQuestCompleter.java
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/questcompleter/BukkitQuestCompleter.java
@@ -16,13 +16,17 @@ import org.jetbrains.annotations.NotNull;
import java.util.LinkedList;
import java.util.Objects;
import java.util.Queue;
+import java.util.UUID;
+import java.util.stream.Collectors;
//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 Queue<UUID> expiredCheckQueue = new LinkedList<>();
private final BukkitQuestsPlugin plugin;
+ private int expiredQuestsCheckCountdown;
public BukkitQuestCompleter(BukkitQuestsPlugin plugin) {
this.plugin = plugin;
@@ -30,10 +34,47 @@ public class BukkitQuestCompleter implements QuestCompleter, Runnable {
@Override
public void run() {
+ this.processExpiredCheckQueue();
this.processCompletionQueue();
this.processFullCheckQueue();
}
+ private void checkExpiredQuests(QPlayer qPlayer) {
+ QuestProgressFile questProgressFile = qPlayer.getQuestProgressFile();
+ for (QuestProgress questProgress : questProgressFile.getAllQuestProgress()) {
+ if (!questProgress.isStarted()) {
+ continue;
+ }
+
+ Quest quest = plugin.getQuestManager().getQuestById(questProgress.getQuestId());
+ if (quest == null) {
+ continue;
+ }
+
+ if (questProgressFile.getTimeRemainingFor(quest) == 0) {
+ qPlayer.expireQuest(quest);
+ }
+ }
+ }
+
+ private void processExpiredCheckQueue() {
+ UUID who = expiredCheckQueue.poll();
+ if (who == null) {
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ expiredCheckQueue.add(player.getUniqueId());
+ }
+ return;
+ }
+
+ Player player = Bukkit.getPlayer(who);
+ if (player == null || !player.isOnline()) return;
+
+ QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
+ if (qPlayer == null) return;
+
+ checkExpiredQuests(qPlayer);
+ }
+
private void processCompletionQueue() {
QuestProgress questProgress = completionQueue.poll();
if (questProgress == null) return;
@@ -44,6 +85,9 @@ public class BukkitQuestCompleter implements QuestCompleter, Runnable {
if (qPlayer == null) return;
plugin.getQuestsLogger().debug("Processing player (singular: " + questProgress.getQuestId() + ") " + qPlayer.getPlayerUUID());
+
+ checkExpiredQuests(qPlayer);
+
Quest quest = plugin.getQuestManager().getQuestById(questProgress.getQuestId());
if (!qPlayer.hasStartedQuest(quest)) return;
@@ -63,20 +107,15 @@ public class BukkitQuestCompleter implements QuestCompleter, Runnable {
QPlayer qPlayer = plugin.getPlayerManager().getPlayer(player.getUniqueId());
if (qPlayer == null) return;
plugin.getQuestsLogger().debug("Processing player (full check) " + qPlayer.getPlayerUUID());
+
+ checkExpiredQuests(qPlayer);
+
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) {
+ if (checkComplete(quest, questProgress)) {
qPlayer.completeQuest(quest);
}
}
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
index 3a8dbb5c..c5f4ef7a 100644
--- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/questcontroller/NormalQuestController.java
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/questcontroller/NormalQuestController.java
@@ -258,6 +258,15 @@ public class NormalQuestController implements QuestController {
}
}
+ private void resetQuest(QuestProgress questProgress) {
+ questProgress.setStarted(false);
+ questProgress.setStartedDate(System.currentTimeMillis());
+ for (TaskProgress taskProgress : questProgress.getTaskProgress()) {
+ taskProgress.setCompleted(false);
+ taskProgress.setProgress(null);
+ }
+ }
+
@Override
public boolean cancelQuestForPlayer(QPlayer qPlayer, Quest quest) {
QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
@@ -272,12 +281,7 @@ public class NormalQuestController implements QuestController {
Messages.QUEST_CANCEL_NOTCANCELLABLE.send(player);
return false;
}
- questProgress.setStarted(false);
- questProgress.setStartedDate(System.currentTimeMillis());
- for (TaskProgress taskProgress : questProgress.getTaskProgress()) {
- taskProgress.setCompleted(false);
- taskProgress.setProgress(null);
- }
+ resetQuest(questProgress);
if (player != null) {
QItemStack qItemStack = plugin.getQItemStackRegistry().getQuestItemStack(quest);
String displayName = Chat.strip(qItemStack.getName());
@@ -298,6 +302,32 @@ public class NormalQuestController implements QuestController {
}
@Override
+ public boolean expireQuestForPlayer(QPlayer qPlayer, Quest quest) {
+ QuestProgress questProgress = qPlayer.getQuestProgressFile().getQuestProgress(quest);
+ Player player = Bukkit.getPlayer(qPlayer.getPlayerUUID());
+ if (!questProgress.isStarted()) {
+ return false;
+ }
+ resetQuest(questProgress);
+ if (player != null) {
+ QItemStack qItemStack = plugin.getQItemStackRegistry().getQuestItemStack(quest);
+ String displayName = Chat.strip(qItemStack.getName());
+ String questExpireMessage = Messages.QUEST_EXPIRE.getMessage().replace("{quest}", displayName);
+ // PlayerCancelQuestEvent -- start
+ PlayerExpireQuestEvent questCancelEvent = new PlayerExpireQuestEvent(player, qPlayer, questProgress, questExpireMessage);
+ Bukkit.getPluginManager().callEvent(questCancelEvent);
+ // PlayerCancelQuestEvent -- end
+ Messages.send(questCancelEvent.getQuestExpireMessage(), player);
+ }
+ if (config.getBoolean("options.allow-quest-track")
+ && config.getBoolean("options.quest-autotrack")
+ && quest.getId().equals(qPlayer.getPlayerPreferences().getTrackedQuestId())) {
+ trackNextQuest(qPlayer, null);
+ }
+ return true;
+ }
+
+ @Override
public void trackQuestForPlayer(QPlayer qPlayer, Quest quest) {
Player player = Bukkit.getPlayer(qPlayer.getPlayerUUID());
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
index 45244374..640bbd98 100644
--- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/storage/MySqlStorageProvider.java
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/storage/MySqlStorageProvider.java
@@ -44,7 +44,7 @@ public class MySqlStorageProvider implements StorageProvider {
" `value` VARCHAR(255) NOT NULL," +
" PRIMARY KEY (`key`));";
private static final String SELECT_PLAYER_QUEST_PROGRESS =
- "SELECT quest_id, started, completed, completed_before, completion_date FROM `{prefix}quest_progress` WHERE uuid=?;";
+ "SELECT quest_id, started, started_date, 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_UUID_LIST =
@@ -54,7 +54,7 @@ public class MySqlStorageProvider implements StorageProvider {
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, started_date, completed, completed_before, completion_date) VALUES (?,?,?,?,?,?) ON DUPLICATE KEY UPDATE started=?, started_date=?, completed=?, completed_before=?, completion_date=?";
+ "INSERT INTO `{prefix}quest_progress` (uuid, quest_id, started, started_date, completed, completed_before, completion_date) VALUES (?,?,?,?,?,?,?) ON DUPLICATE KEY UPDATE started=?, started_date=?, 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=?";
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
index c63a2f05..87dd1087 100644
--- a/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/Messages.java
+++ b/bukkit/src/main/java/com/leonardobishop/quests/bukkit/util/Messages.java
@@ -19,6 +19,7 @@ public enum Messages {
QUEST_START("messages.quest-start", "&7Quest &c{quest} &7started!"),
QUEST_COMPLETE("messages.quest-complete", "&7Quest &c{quest} &completed!"),
QUEST_CANCEL("messages.quest-cancel", "&7Quest &c{quest} &7cancelled!"),
+ QUEST_EXPIRE("messages.quest-expire", "&7Quest &c{quest} &7has expired."),
QUEST_TRACK("messages.quest-track", "&7Tracking quest &c{quest}&7."),
QUEST_TRACK_STOP("messages.quest-track-stop", "&7No longer tracking quest &c{quest}&7."),
QUEST_RANDOM_NONE("messages.quest-random-none", "&cYou have no quests which you can start."),
diff --git a/bukkit/src/main/resources/resources/bukkit/config.yml b/bukkit/src/main/resources/resources/bukkit/config.yml
index 0e7192c6..472a0da1 100644
--- a/bukkit/src/main/resources/resources/bukkit/config.yml
+++ b/bukkit/src/main/resources/resources/bukkit/config.yml
@@ -311,6 +311,7 @@ messages:
quest-start: "&7Quest &c{quest} &7started!"
quest-complete: "&7Quest &c{quest} &7completed!"
quest-cancel: "&7Quest &c{quest} &7cancelled!"
+ quest-expire: "&7Quest &c{quest} &7has expired."
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."
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
index 3e775dca..c7c71fbb 100644
--- a/common/src/main/java/com/leonardobishop/quests/common/player/QPlayer.java
+++ b/common/src/main/java/com/leonardobishop/quests/common/player/QPlayer.java
@@ -113,6 +113,18 @@ public class QPlayer {
}
/**
+ * Attempt to expire 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 expired, false otherwise
+ */
+ public boolean expireQuest(@NotNull Quest quest) {
+ Objects.requireNonNull(quest, "quest cannot be null");
+
+ return questController.expireQuestForPlayer(this, quest);
+ }
+
+ /**
* Check if the player can start a quest.
*
* Warning: will fail if the player is not online.
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
index 2953a340..13756619 100644
--- 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
@@ -146,6 +146,25 @@ public class QuestProgressFile {
}
/**
+ * Gets the time remaining before a quest will have expired.
+ *
+ * @param quest the quest to test for
+ * @return 0 if no time remaining, -1 if the time limit is disabled or the quest is not started,
+ * otherwise the time left in milliseconds
+ */
+ public long getTimeRemainingFor(Quest quest) {
+ QuestProgress questProgress = getQuestProgress(quest);
+ if (quest.isTimeLimitEnabled() && questProgress.isStarted()) {
+ return Math.max(
+ questProgress.getStartedDate()
+ + TimeUnit.MILLISECONDS.convert(quest.getTimeLimit(), TimeUnit.MINUTES)
+ - System.currentTimeMillis()
+ , 0);
+ }
+ return -1;
+ }
+
+ /**
* Tests whether or not the player meets the requirements to start a specific quest.
*
* @param quest the quest to test for
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
index 999507b2..f12c9a5f 100644
--- a/common/src/main/java/com/leonardobishop/quests/common/quest/Quest.java
+++ b/common/src/main/java/com/leonardobishop/quests/common/quest/Quest.java
@@ -18,6 +18,8 @@ public class Quest implements Comparable<Quest> {
private boolean repeatEnabled;
private boolean cooldownEnabled;
private int cooldown;
+ private boolean timeLimitEnabled;
+ private int timeLimit;
private int sortOrder;
private boolean permissionRequired;
private boolean autoStartEnabled;
@@ -176,13 +178,32 @@ public class Quest implements Comparable<Quest> {
* Get the cooldown for this quest between completing and restarting the quest.
* Whether or not this cooldown is in use depends on {@link Quest#isCooldownEnabled()}.
*
- * @return the cooldown, in seconds
+ * @return the cooldown, in minutes
*/
public int getCooldown() {
return cooldown;
}
/**
+ * Get whether this quest has a time limit.
+ *
+ * @return boolean
+ */
+ public boolean isTimeLimitEnabled() {
+ return timeLimitEnabled;
+ }
+
+ /**
+ * Get the time limit for this quest.
+ * Whether or not this time limit is in use depends on {@link Quest#isTimeLimitEnabled()}.
+ *
+ * @return the time limit, in minutes
+ */
+ public int getTimeLimit() {
+ return timeLimit;
+ }
+
+ /**
* Get the category id this quest is in.
*
* @return the category id, or null
@@ -260,6 +281,8 @@ public class Quest implements Comparable<Quest> {
private boolean repeatEnabled = false;
private boolean cooldownEnabled = false;
private int cooldown = 0;
+ private boolean timeLimitEnabled = false;
+ private int timeLimit = 0;
private int sortOrder = 1;
private boolean permissionRequired = false;
private boolean autoStartEnabled = false;
@@ -307,6 +330,11 @@ public class Quest implements Comparable<Quest> {
return this;
}
+ public Builder withTimeLimit(int timeLimit) {
+ this.timeLimit = timeLimit;
+ return this;
+ }
+
public Builder withPlaceholders(Map<String, String> placeholders) {
this.placeholders = placeholders;
return this;
@@ -322,6 +350,11 @@ public class Quest implements Comparable<Quest> {
return this;
}
+ public Builder withTimeLimitEnabled(boolean timeLimitEnabled) {
+ this.timeLimitEnabled = timeLimitEnabled;
+ return this;
+ }
+
public Builder withPermissionRequired(boolean permissionRequired) {
this.permissionRequired = permissionRequired;
return this;
@@ -358,6 +391,8 @@ public class Quest implements Comparable<Quest> {
quest.repeatEnabled = this.repeatEnabled;
quest.cooldownEnabled = this.cooldownEnabled;
quest.cooldown = this.cooldown;
+ quest.timeLimitEnabled = this.timeLimitEnabled;
+ quest.timeLimit = this.timeLimit;
quest.sortOrder = this.sortOrder;
quest.permissionRequired = this.permissionRequired;
quest.autoStartEnabled = this.autoStartEnabled;
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
index 14aa03fd..d1e4812c 100644
--- a/common/src/main/java/com/leonardobishop/quests/common/questcontroller/QuestController.java
+++ b/common/src/main/java/com/leonardobishop/quests/common/questcontroller/QuestController.java
@@ -22,6 +22,8 @@ public interface QuestController {
boolean cancelQuestForPlayer(QPlayer qPlayer, Quest quest);
+ boolean expireQuestForPlayer(QPlayer qPlayer, Quest quest);
+
void trackQuestForPlayer(QPlayer qPlayer, Quest quest);
}