From 3554de1c0d546ad0326b5e4895ac2e11aff696f5 Mon Sep 17 00:00:00 2001 From: PikaMug <2267126+PikaMug@users.noreply.github.com> Date: Tue, 25 Jan 2022 19:39:27 -0500 Subject: [PATCH] Ask for confirmation on quit, fixes #1866 --- api/pom.xml | 33 -- .../misc/MiscPostQuestAbandonEvent.java | 56 ++++ .../me/blackvein/quests/player/IQuester.java | 16 +- .../java/me/blackvein/quests/Quester.java | 288 +++++++++++------- .../quests/convo/misc/QuestAbandonPrompt.java | 137 +++++++++ .../quests/listeners/CmdExecutor.java | 18 +- core/src/main/resources/strings.yml | 1 + 7 files changed, 386 insertions(+), 163 deletions(-) create mode 100644 api/src/main/java/me/blackvein/quests/events/misc/MiscPostQuestAbandonEvent.java create mode 100644 core/src/main/java/me/blackvein/quests/convo/misc/QuestAbandonPrompt.java diff --git a/api/pom.xml b/api/pom.xml index 41f63da4f..dd5c976f1 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -129,39 +129,6 @@ 1.1.1 provided - - com.github.PikaMug - LocaleLib - 2.4 - - - mysql - mysql-connector-java - 8.0.25 - - - com.google.protobuf - protobuf-java - - - - - com.zaxxer - HikariCP - - 4.0.3 - - - slf4j-api - org.slf4j - - - - - org.slf4j - slf4j-simple - 1.7.32 - diff --git a/api/src/main/java/me/blackvein/quests/events/misc/MiscPostQuestAbandonEvent.java b/api/src/main/java/me/blackvein/quests/events/misc/MiscPostQuestAbandonEvent.java new file mode 100644 index 000000000..1baf6051d --- /dev/null +++ b/api/src/main/java/me/blackvein/quests/events/misc/MiscPostQuestAbandonEvent.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2014 PikaMug and contributors. All rights reserved. + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package me.blackvein.quests.events.misc; + +import org.bukkit.conversations.ConversationContext; +import org.bukkit.conversations.Prompt; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.NotNull; + +public class MiscPostQuestAbandonEvent extends MiscEditorEvent { + private static final HandlerList HANDLERS = new HandlerList(); + private final Prompt prompt; + + public MiscPostQuestAbandonEvent(final ConversationContext context, final Prompt prompt) { + super(context, prompt); + this.context = context; + this.prompt = prompt; + } + + /** + * Returns the context involved in this event + * + * @return ConversationContext which is involved in this event + */ + public ConversationContext getConversationContext() { + return context; + } + + /** + * Returns the prompt involved in this event + * + * @return Prompt which is involved in this event + */ + public Prompt getPrompt() { + return prompt; + } + + @Override + public @NotNull HandlerList getHandlers() { + return HANDLERS; + } + + public static HandlerList getHandlerList() { + return HANDLERS; + } +} diff --git a/api/src/main/java/me/blackvein/quests/player/IQuester.java b/api/src/main/java/me/blackvein/quests/player/IQuester.java index 3b6be0355..1f61aea54 100644 --- a/api/src/main/java/me/blackvein/quests/player/IQuester.java +++ b/api/src/main/java/me/blackvein/quests/player/IQuester.java @@ -36,6 +36,10 @@ public interface IQuester extends Comparable { void setQuestIdToTake(final String questIdToTake); + String getQuestIdToQuit(); + + void setQuestIdToQuit(final String questIdToQuit); + String getLastKnownName(); void setLastKnownName(final String lastKnownName); @@ -102,8 +106,16 @@ public interface IQuester extends Comparable { void updateJournal(); + boolean offerQuest(final IQuest quest, final boolean giveReason); + + boolean canAcceptOffer(final IQuest quest, final boolean giveReason); + void takeQuest(final IQuest quest, final boolean ignoreRequirements); + boolean abandonQuest(final IQuest quest, final String message); + + boolean abandonQuest(final IQuest quest, final String[] messages); + void quitQuest(final IQuest quest, final String message); void quitQuest(final IQuest quest, final String[] messages); @@ -221,10 +233,6 @@ public interface IQuester extends Comparable { List getMultiplayerQuesters(final IQuest quest); - boolean offerQuest(final IQuest quest, final boolean giveReason); - - boolean canAcceptOffer(final IQuest quest, final boolean giveReason); - boolean meetsCondition(final IQuest quest, final boolean giveReason); boolean isSelectingBlock(); diff --git a/core/src/main/java/me/blackvein/quests/Quester.java b/core/src/main/java/me/blackvein/quests/Quester.java index 960d40668..ec5778a1b 100644 --- a/core/src/main/java/me/blackvein/quests/Quester.java +++ b/core/src/main/java/me/blackvein/quests/Quester.java @@ -17,7 +17,10 @@ import com.alessiodp.parties.api.interfaces.PartyPlayer; import com.gmail.nossr50.datatypes.skills.SkillType; import com.gmail.nossr50.util.player.UserManager; import me.blackvein.quests.conditions.ICondition; +import me.blackvein.quests.config.ISettings; +import me.blackvein.quests.convo.misc.QuestAbandonPrompt; import me.blackvein.quests.dependencies.IDependencies; +import me.blackvein.quests.events.quest.QuestQuitEvent; import me.blackvein.quests.module.ICustomObjective; import me.blackvein.quests.enums.ObjectiveType; import me.blackvein.quests.events.quest.QuestTakeEvent; @@ -55,6 +58,7 @@ import org.bukkit.OfflinePlayer; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.conversations.ConversationFactory; import org.bukkit.enchantments.Enchantment; import org.bukkit.entity.EntityType; import org.bukkit.entity.Player; @@ -91,6 +95,7 @@ public class Quester implements IQuester { private final Quests plugin; private UUID id; protected String questIdToTake; + protected String questIdToQuit; private String lastKnownName; protected int questPoints = 0; private String compassTargetQuestId; @@ -269,6 +274,16 @@ public class Quester implements IQuester { this.questIdToTake = questIdToTake; } + @Override + public String getQuestIdToQuit() { + return questIdToQuit; + } + + @Override + public void setQuestIdToQuit(final String questIdToQuit) { + this.questIdToQuit = questIdToQuit; + } + @Override public String getLastKnownName() { return lastKnownName; @@ -462,6 +477,120 @@ public class Quester implements IQuester { getPlayer().getInventory().setItem(index, journal.toItemStack()); } } + + /** + * Check if quest is available and, if so, ask Quester if they would like to start it

+ * + * @param quest The quest to check and then offer + * @param giveReason Whether to inform Quester of unavailability + * @return true if successful + */ + public boolean offerQuest(final IQuest quest, final boolean giveReason) { + if (quest == null) { + return false; + } + final QuestTakeEvent event = new QuestTakeEvent(quest, this); + plugin.getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return false; + } + if (canAcceptOffer(quest, giveReason)) { + if (getPlayer() != null) { + if (!getPlayer().isConversing()) { + setQuestIdToTake(quest.getId()); + final String s = ChatColor.GOLD + Lang.get("questObjectivesTitle") + .replace("", quest.getName()) + "\n" + ChatColor.RESET + quest.getDescription(); + for (final String msg : s.split("
")) { + sendMessage(msg); + } + if (!plugin.getSettings().canAskConfirmation()) { + takeQuest(quest, false); + } else { + plugin.getConversationFactory().buildConversation(getPlayer()).begin(); + } + return true; + } else { + sendMessage(ChatColor.YELLOW + Lang.get(getPlayer(), "alreadyConversing")); + } + } + } + return false; + } + + /** + * Check if quest is available to this Quester

+ * + * @param quest The quest to check + * @param giveReason Whether to inform Quester of unavailability + * @return true if available + */ + public boolean canAcceptOffer(final IQuest quest, final boolean giveReason) { + if (quest == null) { + return false; + } + if (getCurrentQuests().size() >= plugin.getSettings().getMaxQuests() && plugin.getSettings().getMaxQuests() + > 0) { + if (giveReason) { + final String msg = Lang.get(getPlayer(), "questMaxAllowed").replace("", + String.valueOf(plugin.getSettings().getMaxQuests())); + sendMessage(ChatColor.YELLOW + msg); + } + return false; + } else if (getCurrentQuests().containsKey(quest)) { + if (giveReason) { + final String msg = Lang.get(getPlayer(), "questAlreadyOn"); + sendMessage(ChatColor.YELLOW + msg); + } + return false; + } else if (getCompletedQuests().contains(quest) && quest.getPlanner().getCooldown() < 0) { + if (giveReason) { + final String msg = Lang.get(getPlayer(), "questAlreadyCompleted") + .replace("", ChatColor.DARK_PURPLE + quest.getName() + ChatColor.YELLOW); + sendMessage(ChatColor.YELLOW + msg); + } + return false; + } else if (plugin.getDependencies().getCitizens() != null + && !plugin.getSettings().canAllowCommandsForNpcQuests() + && quest.getNpcStart() != null && quest.getNpcStart().getEntity() != null + && quest.getNpcStart().getEntity().getLocation().getWorld() != null + && getPlayer().getLocation().getWorld() != null + && quest.getNpcStart().getEntity().getLocation().getWorld().getName().equals( + getPlayer().getLocation().getWorld().getName()) + && quest.getNpcStart().getEntity().getLocation().distance(getPlayer().getLocation()) > 6.0) { + if (giveReason) { + final String msg = Lang.get(getPlayer(), "mustSpeakTo").replace("", ChatColor.DARK_PURPLE + + quest.getNpcStart().getName() + ChatColor.YELLOW); + sendMessage(ChatColor.YELLOW + msg); + } + return false; + } else if (quest.getBlockStart() != null) { + if (giveReason) { + final String msg = Lang.get(getPlayer(), "noCommandStart").replace("", ChatColor.DARK_PURPLE + + quest.getName() + ChatColor.YELLOW); + sendMessage(ChatColor.YELLOW + msg); + } + return false; + } else if (getCompletedQuests().contains(quest) && getRemainingCooldown(quest) > 0 + && !quest.getPlanner().getOverride()) { + if (giveReason) { + final String msg = Lang.get(getPlayer(), "questTooEarly").replace("", ChatColor.AQUA + + quest.getName()+ ChatColor.YELLOW).replace("

+ * + * @param quest The quest to check and then offer + * @param message Messages to send Quester upon quit, can be left null or empty + * @return true if successful + */ + public boolean abandonQuest(final IQuest quest, final String message) { + return abandonQuest(quest, new String[] {message}); + } + + /** + * End a quest for this Quester, but ask permission first if possible

+ * + * @param quest The quest to check and then offer + * @param messages Messages to send Quester upon quit, can be left null or empty + * @return true if successful + */ + public boolean abandonQuest(final IQuest quest, final String[] messages) { + if (quest == null) { + return false; + } + final QuestQuitEvent event = new QuestQuitEvent(quest, this); + plugin.getServer().getPluginManager().callEvent(event); + if (event.isCancelled()) { + return false; + } + final ISettings settings = plugin.getSettings(); + if (getPlayer() != null) { + setQuestIdToQuit(quest.getId()); + if (settings.canAskConfirmation()) { + final ConversationFactory cf = new ConversationFactory(plugin).withModality(false) + .withPrefix(context -> ChatColor.GRAY.toString()) + .withFirstPrompt(new QuestAbandonPrompt()).withTimeout(settings.getAcceptTimeout()) + .thatExcludesNonPlayersWithMessage("Console may not perform this conversation!") + .addConversationAbandonedListener(plugin.getConvoListener()); + cf.buildConversation(getPlayer()).begin(); + } else { + quitQuest(quest, messages); + } + return true; + } + return false; + } /** * End a quest for this Quester @@ -4195,120 +4369,6 @@ public class Quester implements IQuester { return mq; } - /** - * Check if quest is available and, if so, ask Quester if they would like to start it

- * - * @param quest The quest to check and then offer - * @param giveReason Whether to inform Quester of unavailability - * @return true if successful - */ - public boolean offerQuest(final IQuest quest, final boolean giveReason) { - if (quest == null) { - return false; - } - final QuestTakeEvent event = new QuestTakeEvent(quest, this); - plugin.getServer().getPluginManager().callEvent(event); - if (event.isCancelled()) { - return false; - } - if (canAcceptOffer(quest, giveReason)) { - if (getPlayer() != null) { - if (!getPlayer().isConversing()) { - setQuestIdToTake(quest.getId()); - final String s = ChatColor.GOLD + Lang.get("questObjectivesTitle") - .replace("", quest.getName()) + "\n" + ChatColor.RESET + quest.getDescription(); - for (final String msg : s.split("
")) { - sendMessage(msg); - } - if (!plugin.getSettings().canAskConfirmation()) { - takeQuest(quest, false); - } else { - plugin.getConversationFactory().buildConversation(getPlayer()).begin(); - } - return true; - } else { - sendMessage(ChatColor.YELLOW + Lang.get(getPlayer(), "alreadyConversing")); - } - } - } - return false; - } - - /** - * Check if quest is available to this Quester

- * - * @param quest The quest to check - * @param giveReason Whether to inform Quester of unavailability - * @return true if available - */ - public boolean canAcceptOffer(final IQuest quest, final boolean giveReason) { - if (quest == null) { - return false; - } - if (getCurrentQuests().size() >= plugin.getSettings().getMaxQuests() && plugin.getSettings().getMaxQuests() - > 0) { - if (giveReason) { - final String msg = Lang.get(getPlayer(), "questMaxAllowed").replace("", - String.valueOf(plugin.getSettings().getMaxQuests())); - sendMessage(ChatColor.YELLOW + msg); - } - return false; - } else if (getCurrentQuests().containsKey(quest)) { - if (giveReason) { - final String msg = Lang.get(getPlayer(), "questAlreadyOn"); - sendMessage(ChatColor.YELLOW + msg); - } - return false; - } else if (getCompletedQuests().contains(quest) && quest.getPlanner().getCooldown() < 0) { - if (giveReason) { - final String msg = Lang.get(getPlayer(), "questAlreadyCompleted") - .replace("", ChatColor.DARK_PURPLE + quest.getName() + ChatColor.YELLOW); - sendMessage(ChatColor.YELLOW + msg); - } - return false; - } else if (plugin.getDependencies().getCitizens() != null - && !plugin.getSettings().canAllowCommandsForNpcQuests() - && quest.getNpcStart() != null && quest.getNpcStart().getEntity() != null - && quest.getNpcStart().getEntity().getLocation().getWorld() != null - && getPlayer().getLocation().getWorld() != null - && quest.getNpcStart().getEntity().getLocation().getWorld().getName().equals( - getPlayer().getLocation().getWorld().getName()) - && quest.getNpcStart().getEntity().getLocation().distance(getPlayer().getLocation()) > 6.0) { - if (giveReason) { - final String msg = Lang.get(getPlayer(), "mustSpeakTo").replace("", ChatColor.DARK_PURPLE - + quest.getNpcStart().getName() + ChatColor.YELLOW); - sendMessage(ChatColor.YELLOW + msg); - } - return false; - } else if (quest.getBlockStart() != null) { - if (giveReason) { - final String msg = Lang.get(getPlayer(), "noCommandStart").replace("", ChatColor.DARK_PURPLE - + quest.getName() + ChatColor.YELLOW); - sendMessage(ChatColor.YELLOW + msg); - } - return false; - } else if (getCompletedQuests().contains(quest) && getRemainingCooldown(quest) > 0 - && !quest.getPlanner().getOverride()) { - if (giveReason) { - final String msg = Lang.get(getPlayer(), "questTooEarly").replace("", ChatColor.AQUA - + quest.getName()+ ChatColor.YELLOW).replace("