diff options
| author | LMBishop <13875753+LMBishop@users.noreply.github.com> | 2022-05-01 18:48:21 +0100 |
|---|---|---|
| committer | LMBishop <13875753+LMBishop@users.noreply.github.com> | 2022-05-01 18:48:21 +0100 |
| commit | 8068d1efc179e60e5b01a1733c392f5b3de21549 (patch) | |
| tree | 21803b247438d81e08305e34b1ec177d43cdc1bf | |
| parent | 649bb972c9bcd587a5d1b2d495922db5901d05db (diff) | |
Add time-limit option and quest expiry functionality (closes #379)
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); } |
