Ask for confirmation on quit, fixes #1866

This commit is contained in:
PikaMug 2022-01-25 19:39:27 -05:00
parent 2e622d5673
commit 3554de1c0d
7 changed files with 386 additions and 163 deletions

View File

@ -129,39 +129,6 @@
<version>1.1.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.PikaMug</groupId>
<artifactId>LocaleLib</artifactId>
<version>2.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.25</version>
<exclusions>
<exclusion>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<!-- Do not update until Java 11 is min -->
<version>4.0.3</version>
<exclusions>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.32</version>
</dependency>
</dependencies>
<build>

View File

@ -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;
}
}

View File

@ -36,6 +36,10 @@ public interface IQuester extends Comparable<IQuester> {
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<IQuester> {
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<IQuester> {
List<IQuester> 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();

View File

@ -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<p>
*
* @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>", quest.getName()) + "\n" + ChatColor.RESET + quest.getDescription();
for (final String msg : s.split("<br>")) {
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<p>
*
* @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("<number>",
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("<quest>", 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("<npc>", 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("<quest>", 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("<quest>", ChatColor.AQUA
+ quest.getName()+ ChatColor.YELLOW).replace("<time>", ChatColor.DARK_PURPLE
+ MiscUtil.getTime(getRemainingCooldown(quest)) + ChatColor.YELLOW);
getPlayer().sendMessage(ChatColor.YELLOW + msg);
}
return false;
} else if (quest.getRegionStart() != null) {
if (!quest.isInRegionStart(this)) {
if (giveReason) {
final String msg = Lang.get(getPlayer(), "questInvalidLocation").replace("<quest>", ChatColor.AQUA
+ quest.getName() + ChatColor.YELLOW);
getPlayer().sendMessage(ChatColor.YELLOW + msg);
}
return false;
}
}
return true;
}
/**
* Start a quest for this Quester
@ -707,6 +836,51 @@ public class Quester implements IQuester {
plugin.getServer().getPluginManager().callEvent(postEvent);
}
}
/**
* End a quest for this Quester, but ask permission first if possible<p>
*
* @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<p>
*
* @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<p>
*
* @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>", quest.getName()) + "\n" + ChatColor.RESET + quest.getDescription();
for (final String msg : s.split("<br>")) {
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<p>
*
* @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("<number>",
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("<quest>", 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("<npc>", 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("<quest>", 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("<quest>", ChatColor.AQUA
+ quest.getName()+ ChatColor.YELLOW).replace("<time>", ChatColor.DARK_PURPLE
+ MiscUtil.getTime(getRemainingCooldown(quest)) + ChatColor.YELLOW);
getPlayer().sendMessage(ChatColor.YELLOW + msg);
}
return false;
} else if (quest.getRegionStart() != null) {
if (!quest.isInRegionStart(this)) {
if (giveReason) {
final String msg = Lang.get(getPlayer(), "questInvalidLocation").replace("<quest>", ChatColor.AQUA
+ quest.getName() + ChatColor.YELLOW);
getPlayer().sendMessage(ChatColor.YELLOW + msg);
}
return false;
}
}
return true;
}
public boolean meetsCondition(final IQuest quest, final boolean giveReason) {
final IStage stage = getCurrentStage(quest);
if (stage != null && stage.getCondition() != null && !stage.getCondition().check(this, quest)) {

View File

@ -0,0 +1,137 @@
package me.blackvein.quests.convo.misc;
import me.blackvein.quests.Quests;
import me.blackvein.quests.events.misc.MiscPostQuestAbandonEvent;
import me.blackvein.quests.player.IQuester;
import me.blackvein.quests.quests.IQuest;
import me.blackvein.quests.util.Lang;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.TextComponent;
import org.bukkit.ChatColor;
import org.bukkit.conversations.ConversationContext;
import org.bukkit.conversations.Prompt;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
public class QuestAbandonPrompt extends MiscStringPrompt {
private ConversationContext cc;
public QuestAbandonPrompt() {
super(null);
}
public QuestAbandonPrompt(final ConversationContext context) {
super(context);
}
private final int size = 2;
public int getSize() {
return size;
}
public String getTitle(final ConversationContext context) {
return null;
}
public ChatColor getNumberColor(final ConversationContext context, final int number) {
switch (number) {
case 1:
return ChatColor.GREEN;
case 2:
return ChatColor.RED;
default:
return null;
}
}
public String getSelectionText(final ConversationContext context, final int number) {
switch (number) {
case 1:
return ChatColor.GREEN + Lang.get("yesWord");
case 2:
return ChatColor.RED + Lang.get("noWord");
default:
return null;
}
}
public String getQueryText(final ConversationContext context) {
return Lang.get("abandonQuest");
}
@Override
public @NotNull String getPromptText(final @NotNull ConversationContext context) {
this.cc = context;
final Quests plugin = (Quests)context.getPlugin();
if (plugin == null) {
return ChatColor.YELLOW + Lang.get("unknownError");
}
final MiscPostQuestAbandonEvent event = new MiscPostQuestAbandonEvent(context, this);
plugin.getServer().getPluginManager().callEvent(event);
if (!plugin.getSettings().canClickablePrompts()) {
return ChatColor.YELLOW + getQueryText(context) + " " + ChatColor.GREEN
+ getSelectionText(context, 1) + ChatColor.RESET + " / " + getSelectionText(context, 2);
}
final TextComponent component = new TextComponent("");
component.addExtra(ChatColor.YELLOW + getQueryText(context) + " " + ChatColor.GREEN);
final TextComponent yes = new TextComponent(getSelectionText(context, 1));
yes.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, Lang.get("yesWord")));
component.addExtra(yes);
component.addExtra(ChatColor.RESET + " / ");
final TextComponent no = new TextComponent(getSelectionText(context, 2));
no.setClickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, Lang.get("noWord")));
component.addExtra(no);
((Player)context.getForWhom()).spigot().sendMessage(component);
return "";
}
@Override
public Prompt acceptInput(final @NotNull ConversationContext context, final String input) {
final Quests plugin = (Quests)context.getPlugin();
if (plugin == null || input == null) {
return Prompt.END_OF_CONVERSATION;
}
final Player player = (Player) context.getForWhom();
if (input.equalsIgnoreCase("1") || input.equalsIgnoreCase("y")
|| input.equalsIgnoreCase(Lang.get(player, "yesWord"))) {
final IQuester quester = plugin.getQuester(player.getUniqueId());
if (quester == null) {
plugin.getLogger().info("Ended conversation because quester for " + getName() + "was null");
return Prompt.END_OF_CONVERSATION;
}
final String questIdToQuit = quester.getQuestIdToQuit();
try {
IQuest quest = plugin.getQuestById(questIdToQuit);
if (quest == null) {
plugin.getLogger().info(player.getName() + " attempted to quit quest ID \"" + questIdToQuit
+ "\" but something went wrong");
player.sendMessage(ChatColor.RED
+ "Something went wrong! Please report issue to an administrator.");
} else {
final String msg = ChatColor.YELLOW + Lang.get("questQuit").replace("<quest>",
ChatColor.DARK_PURPLE + quest.getName() + ChatColor.YELLOW);
quester.quitQuest(plugin.getQuestById(questIdToQuit), msg);
}
} catch (final Exception e) {
e.printStackTrace();
}
return Prompt.END_OF_CONVERSATION;
} else if (input.equalsIgnoreCase("2") || input.equalsIgnoreCase("n")
|| input.equalsIgnoreCase(Lang.get("noWord"))) {
Lang.send(player, ChatColor.YELLOW + Lang.get("cancelled"));
return Prompt.END_OF_CONVERSATION;
} else {
final String msg = Lang.get(player, "questInvalidChoice")
.replace("<yes>", Lang.get(player, "yesWord"))
.replace("<no>", Lang.get(player, "noWord"));
Lang.send(player, ChatColor.RED + msg);
return new QuestAbandonPrompt(context);
}
}
}

View File

@ -12,18 +12,17 @@
package me.blackvein.quests.listeners;
import me.blackvein.quests.quests.IQuest;
import me.blackvein.quests.player.IQuester;
import me.blackvein.quests.Quester;
import me.blackvein.quests.Quests;
import me.blackvein.quests.quests.IStage;
import me.blackvein.quests.quests.Requirements;
import me.blackvein.quests.events.command.QuestsCommandPreQuestsEditorEvent;
import me.blackvein.quests.events.command.QuestsCommandPreQuestsJournalEvent;
import me.blackvein.quests.events.command.QuestsCommandPreQuestsListEvent;
import me.blackvein.quests.events.quest.QuestQuitEvent;
import me.blackvein.quests.interfaces.ReloadCallback;
import me.blackvein.quests.item.QuestJournal;
import me.blackvein.quests.Quester;
import me.blackvein.quests.player.IQuester;
import me.blackvein.quests.quests.IQuest;
import me.blackvein.quests.quests.IStage;
import me.blackvein.quests.quests.Requirements;
import me.blackvein.quests.storage.Storage;
import me.blackvein.quests.util.ItemUtil;
import me.blackvein.quests.util.Lang;
@ -702,14 +701,9 @@ public class CmdExecutor implements CommandExecutor {
final IQuest quest = plugin.getQuest(concatArgArray(args, 1, args.length - 1, ' '));
if (quest != null) {
if (quest.getOptions().canAllowQuitting()) {
final QuestQuitEvent event = new QuestQuitEvent(quest, quester);
plugin.getServer().getPluginManager().callEvent(event);
if (event.isCancelled()) {
return;
}
final String msg = ChatColor.YELLOW + Lang.get("questQuit").replace("<quest>",
ChatColor.DARK_PURPLE + quest.getName() + ChatColor.YELLOW);
quester.quitQuest(quest, msg);
quester.abandonQuest(quest, msg);
} else {
Lang.send(player, ChatColor.YELLOW + Lang.get(player, "questQuitDisabled"));
}

View File

@ -642,6 +642,7 @@ strSpace: "separating each by a space"
strSemicolon: "separating each by a semicolon"
charSemi: ";"
acceptQuest: "Accept quest?"
abandonQuest: "Abandon quest?"
enterAnOption: "Enter an option"
questAccepted: "Quest accepted: <quest>"
currentQuest: "Current Quests:"