Add experimental global quest Options

This commit is contained in:
PikaMug 2025-02-20 22:46:19 -05:00
parent d4f784de86
commit 223a0ce2fc
8 changed files with 304 additions and 25 deletions

View File

@ -50,4 +50,16 @@ public interface Options {
boolean canIgnoreBlockReplace(); boolean canIgnoreBlockReplace();
void setIgnoreBlockReplace(final boolean ignoreBlockReplace); void setIgnoreBlockReplace(final boolean ignoreBlockReplace);
boolean canGiveGloballyAtLogin();
void setGiveGloballyAtLogin(final boolean giveGloballyAtLogin);
boolean canAllowStackingGlobal();
void setAllowStackingGlobal(final boolean allowStackingGlobal);
boolean canInformOnStart();
void setInformOnStart(final boolean informOnStart);
} }

View File

@ -148,9 +148,12 @@ public class Key {
public static final String OPT_USE_PARTIES_PLUGIN = "usePartiesPluginOpt"; public static final String OPT_USE_PARTIES_PLUGIN = "usePartiesPluginOpt";
public static final String OPT_SHARE_PROGRESS_LEVEL = "shareProgressLevelOpt"; public static final String OPT_SHARE_PROGRESS_LEVEL = "shareProgressLevelOpt";
public static final String OPT_SHARE_SAME_QUEST_ONLY = "shareSameQuestOnlyOpt"; public static final String OPT_SHARE_SAME_QUEST_ONLY = "shareSameQuestOnlyOpt";
public static final String OPT_SHARE_DISTANCE = "shareDistance"; public static final String OPT_SHARE_DISTANCE = "shareDistanceOpt";
public static final String OPT_HANDLE_OFFLINE_PLAYERS = "handleOfflinePlayers"; public static final String OPT_HANDLE_OFFLINE_PLAYERS = "handleOfflinePlayersOpt";
public static final String OPT_IGNORE_BLOCK_REPLACE = "ignoreBlockReplace"; public static final String OPT_IGNORE_BLOCK_REPLACE = "ignoreBlockReplaceOpt";
public static final String OPT_GIVE_GLOBALLY_AT_LOGIN = "giveGloballyAtLoginOpt";
public static final String OPT_ALLOW_STACKING_GLOBAL = "allowStackingGlobalOpt";
public static final String OPT_INFORM_QUEST_START = "informQuestStartOpt";
// Actions // Actions
public static final String A_OLD_ACTION = "oldAction"; public static final String A_OLD_ACTION = "oldAction";
public static final String A_NAME = "actName"; public static final String A_NAME = "actName";

View File

@ -37,7 +37,7 @@ public class QuestOptionsPrompt extends QuestsEditorNumericPrompt {
this.plugin = (BukkitQuestsPlugin)context.getPlugin(); this.plugin = (BukkitQuestsPlugin)context.getPlugin();
} }
private final int size = 3; private final int size = 4;
@Override @Override
public int getSize() { public int getSize() {
@ -55,8 +55,9 @@ public class QuestOptionsPrompt extends QuestsEditorNumericPrompt {
switch (number) { switch (number) {
case 1: case 1:
case 2: case 2:
return ChatColor.BLUE;
case 3: case 3:
return ChatColor.BLUE;
case 4:
return ChatColor.GREEN; return ChatColor.GREEN;
default: default:
return null; return null;
@ -71,6 +72,8 @@ public class QuestOptionsPrompt extends QuestsEditorNumericPrompt {
case 2: case 2:
return ChatColor.GOLD + BukkitLang.get("optMultiplayer"); return ChatColor.GOLD + BukkitLang.get("optMultiplayer");
case 3: case 3:
return ChatColor.GOLD + "Server (Global)";
case 4:
return ChatColor.YELLOW + BukkitLang.get("done"); return ChatColor.YELLOW + BukkitLang.get("done");
default: default:
return null; return null;
@ -106,6 +109,8 @@ public class QuestOptionsPrompt extends QuestsEditorNumericPrompt {
case 2: case 2:
return new QuestOptionsMultiplayerPrompt(context); return new QuestOptionsMultiplayerPrompt(context);
case 3: case 3:
return new QuestOptionsGlobalPrompt(context);
case 4:
return plugin.getQuestFactory().returnToMenu(context); return plugin.getQuestFactory().returnToMenu(context);
default: default:
return new QuestOptionsPrompt(context); return new QuestOptionsPrompt(context);
@ -736,4 +741,143 @@ public class QuestOptionsPrompt extends QuestsEditorNumericPrompt {
} }
} }
} }
public class QuestOptionsGlobalPrompt extends QuestsEditorNumericPrompt {
public QuestOptionsGlobalPrompt(final ConversationContext context) {
super(context);
}
private final int size = 4;
@Override
public int getSize() {
return size;
}
@Override
public String getTitle(final ConversationContext context) {
return ChatColor.DARK_GREEN + "Server (Global)";
}
@Override
public ChatColor getNumberColor(final ConversationContext context, final int number) {
switch (number) {
case 1:
case 2:
case 3:
return ChatColor.BLUE;
case 4:
return ChatColor.GREEN;
default:
return null;
}
}
@Override
public String getSelectionText(final ConversationContext context, final int number) {
switch (number) {
case 1:
return ChatColor.YELLOW + "Give quest globally at login";
case 2:
return ChatColor.YELLOW + "Allow stacking with global quests";
case 3:
return ChatColor.YELLOW + "Inform players on quest start";
case 4:
return ChatColor.YELLOW + BukkitLang.get("done");
default:
return null;
}
}
@Override
public String getAdditionalText(final ConversationContext context, final int number) {
switch (number) {
case 1:
final Boolean globalOpt = (Boolean) context.getSessionData(Key.OPT_GIVE_GLOBALLY_AT_LOGIN);
if (globalOpt == null) {
final boolean defaultOpt = new BukkitOptions().canGiveGloballyAtLogin();
return ChatColor.GRAY + "(" + (defaultOpt ? ChatColor.GREEN
+ BukkitLang.get(String.valueOf(defaultOpt)) : ChatColor.RED
+ BukkitLang.get(String.valueOf(defaultOpt))) + ChatColor.GRAY + ")";
} else {
return ChatColor.GRAY + "(" + (globalOpt ? ChatColor.GREEN
+ BukkitLang.get(String.valueOf(globalOpt)) : ChatColor.RED
+ BukkitLang.get(String.valueOf(globalOpt))) + ChatColor.GRAY + ")";
}
case 2:
final Boolean stackOpt = (Boolean) context.getSessionData(Key.OPT_ALLOW_STACKING_GLOBAL);
if (stackOpt == null) {
final boolean defaultOpt = new BukkitOptions().canAllowStackingGlobal();
return ChatColor.GRAY + "(" + (defaultOpt ? ChatColor.GREEN
+ BukkitLang.get(String.valueOf(defaultOpt)) : ChatColor.RED
+ BukkitLang.get(String.valueOf(defaultOpt))) + ChatColor.GRAY + ")";
} else {
return ChatColor.GRAY + "(" + (stackOpt ? ChatColor.GREEN
+ BukkitLang.get(String.valueOf(stackOpt)) : ChatColor.RED
+ BukkitLang.get(String.valueOf(stackOpt))) + ChatColor.GRAY + ")";
}
case 3:
final Boolean informOpt = (Boolean) context.getSessionData(Key.OPT_INFORM_QUEST_START);
if (informOpt == null) {
final boolean defaultOpt = new BukkitOptions().canInformOnStart();
return ChatColor.GRAY + "(" + (defaultOpt ? ChatColor.GREEN
+ BukkitLang.get(String.valueOf(defaultOpt)) : ChatColor.RED
+ BukkitLang.get(String.valueOf(defaultOpt))) + ChatColor.GRAY + ")";
} else {
return ChatColor.GRAY + "(" + (informOpt ? ChatColor.GREEN
+ BukkitLang.get(String.valueOf(informOpt)) : ChatColor.RED
+ BukkitLang.get(String.valueOf(informOpt))) + ChatColor.GRAY + ")";
}
case 4:
return "";
default:
return null;
}
}
@Override
public @NotNull String getBasicPromptText(final @NotNull ConversationContext context) {
final QuestsEditorPostOpenNumericPromptEvent event
= new QuestsEditorPostOpenNumericPromptEvent(context, this);
plugin.getServer().getPluginManager().callEvent(event);
final StringBuilder text = new StringBuilder(ChatColor.DARK_GREEN + "- " + getTitle(context) + " -");
for (int i = 1; i <= size; i++) {
text.append("\n").append(getNumberColor(context, i)).append(ChatColor.BOLD).append(i)
.append(ChatColor.RESET).append(" - ").append(getSelectionText(context, i)).append(" ")
.append(getAdditionalText(context, i));
}
return text.toString();
}
@Override
public Prompt acceptValidatedInput(final @NotNull ConversationContext context, final Number input) {
switch (input.intValue()) {
case 1:
tempKey = Key.OPT_GIVE_GLOBALLY_AT_LOGIN;
tempPrompt = new QuestOptionsGlobalPrompt(context);
return new QuestOptionsTrueFalsePrompt(context);
case 2:
tempKey = Key.OPT_ALLOW_STACKING_GLOBAL;
tempPrompt = new QuestOptionsGlobalPrompt(context);
return new QuestOptionsTrueFalsePrompt(context);
case 3:
tempKey = Key.OPT_INFORM_QUEST_START;
tempPrompt = new QuestOptionsGlobalPrompt(context);
return new QuestOptionsTrueFalsePrompt(context);
case 4:
tempKey = null;
tempPrompt = null;
try {
return new QuestOptionsPrompt(context);
} catch (final Exception e) {
context.getForWhom().sendRawMessage(ChatColor.RED + BukkitLang.get("itemCreateCriticalError"));
return Prompt.END_OF_CONVERSATION;
}
default:
return null;
}
}
}
} }

View File

@ -907,6 +907,28 @@ public class BukkitPlayerListener implements Listener {
} }
} }
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> { plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () -> {
boolean alreadyHasAtLeastOneGlobalQuest = false;
for (final Quest cq : quester.getCurrentQuests().keySet()) {
if (cq.getOptions().canGiveGloballyAtLogin()) {
alreadyHasAtLeastOneGlobalQuest = true;
break;
}
}
for (final Quest quest : plugin.getLoadedQuests()) {
if (quest.getOptions().canGiveGloballyAtLogin()) {
if (!quest.getOptions().canAllowStackingGlobal() && alreadyHasAtLeastOneGlobalQuest) {
if (plugin.getConfigSettings().getConsoleLogging() > 3) {
plugin.getLogger().info(quester.getUUID() + " denied global quest ID "
+ quest.getId() + " because it was not stackable");
}
continue;
}
if (quester.canAcceptOffer(quest, quest.getOptions().canInformOnStart())) {
alreadyHasAtLeastOneGlobalQuest = true;
quester.takeQuest(quest, false);
}
}
}
if (quester.hasJournal()) { if (quester.hasJournal()) {
quester.updateJournal(); quester.updateJournal();
} }

View File

@ -714,13 +714,18 @@ public class BukkitQuester implements Quester {
return; return;
} }
} }
final boolean isGlobalQuest = quest.getOptions().canGiveGloballyAtLogin();
if (bukkitQuest.testRequirements(offlinePlayer) || ignoreRequirements) { if (bukkitQuest.testRequirements(offlinePlayer) || ignoreRequirements) {
addEmptiesFor(bukkitQuest, 0); addEmptiesFor(bukkitQuest, 0);
try { try {
currentQuests.put(bukkitQuest, 0); currentQuests.put(bukkitQuest, 0);
if (plugin.getConfigSettings().getConsoleLogging() > 1) { if (plugin.getConfigSettings().getConsoleLogging() > 1) {
if (isGlobalQuest) {
plugin.getLogger().info(getPlayer().getUniqueId() + " started global quest " + bukkitQuest.getName());
} else {
plugin.getLogger().info(getPlayer().getUniqueId() + " started quest " + bukkitQuest.getName()); plugin.getLogger().info(getPlayer().getUniqueId() + " started quest " + bukkitQuest.getName());
} }
}
} catch (final NullPointerException npe) { } catch (final NullPointerException npe) {
plugin.getLogger().severe("Unable to add quest" + bukkitQuest.getName() + " for player " plugin.getLogger().severe("Unable to add quest" + bukkitQuest.getName() + " for player "
+ offlinePlayer.getName() + ". Consider resetting player data or report on Github"); + offlinePlayer.getName() + ". Consider resetting player data or report on Github");
@ -752,6 +757,7 @@ public class BukkitQuester implements Quester {
} }
} }
} }
if (!isGlobalQuest || quest.getOptions().canInformOnStart()) {
String accepted = BukkitLang.get(p, "questAccepted"); String accepted = BukkitLang.get(p, "questAccepted");
accepted = accepted.replace("<quest>", bukkitQuest.getName()); accepted = accepted.replace("<quest>", bukkitQuest.getName());
sendMessage(ChatColor.GREEN + accepted); sendMessage(ChatColor.GREEN + accepted);
@ -765,6 +771,7 @@ public class BukkitQuester implements Quester {
} }
} }
} }
}
if (offlinePlayer.isOnline()) { if (offlinePlayer.isOnline()) {
final Player p = getPlayer(); final Player p = getPlayer();
final String title = BukkitLang.get(p, "objectives").replace("<quest>", bukkitQuest.getName()); final String title = BukkitLang.get(p, "objectives").replace("<quest>", bukkitQuest.getName());
@ -787,13 +794,19 @@ public class BukkitQuester implements Quester {
setCompassTarget(bukkitQuest); setCompassTarget(bukkitQuest);
bukkitQuest.updateCompass(this, stage); bukkitQuest.updateCompass(this, stage);
} else { } else {
if (!isGlobalQuest || quest.getOptions().canInformOnStart()) {
if (offlinePlayer.isOnline()) { if (offlinePlayer.isOnline()) {
if (isGlobalQuest) {
sendMessage(ChatColor.RED + BukkitConfigUtil.parseString(BukkitLang.get("questObjectivesTitle"))
.replace("<quest>", quest.getName()));
}
sendMessage(ChatColor.DARK_AQUA + BukkitLang.get("requirements")); sendMessage(ChatColor.DARK_AQUA + BukkitLang.get("requirements"));
for (final String s : getCurrentRequirements(bukkitQuest, false)) { for (final String s : getCurrentRequirements(bukkitQuest, false)) {
sendMessage("- " + ChatColor.GRAY + s); sendMessage("- " + ChatColor.GRAY + s);
} }
} }
} }
}
if (offlinePlayer.isOnline()) { if (offlinePlayer.isOnline()) {
final BukkitQuesterPostStartQuestEvent postEvent = new BukkitQuesterPostStartQuestEvent(this, bukkitQuest); final BukkitQuesterPostStartQuestEvent postEvent = new BukkitQuesterPostStartQuestEvent(this, bukkitQuest);
plugin.getServer().getPluginManager().callEvent(postEvent); plugin.getServer().getPluginManager().callEvent(postEvent);
@ -804,7 +817,7 @@ public class BukkitQuester implements Quester {
* End a quest for this Quester, but ask permission first if possible<p> * End a quest for this Quester, but ask permission first if possible<p>
* *
* @param quest The quest to check and then offer * @param quest The quest to check and then offer
* @param message Messages to send Quester upon quit, can be left null or empty * @param message Message to send Quester upon quit, can be left null or empty
* @return true if successful * @return true if successful
*/ */
public boolean abandonQuest(final Quest quest, final String message) { public boolean abandonQuest(final Quest quest, final String message) {
@ -4283,7 +4296,23 @@ public class BukkitQuester implements Quester {
final Set<String> appliedQuestIDs = new HashSet<>(); final Set<String> appliedQuestIDs = new HashSet<>();
if (quest != null) { if (quest != null) {
try { try {
if (quest.getOptions().getShareProgressLevel() == 1) { if (quest.getOptions().canGiveGloballyAtLogin()) {
final List<Quester> mq = getGlobalQuesters(quest);
if (mq == null) {
return appliedQuestIDs;
}
for (final Quester q : mq) {
if (q == null) {
continue;
}
q.getCurrentQuests().forEach((otherQuest, i) -> {
if (otherQuest.getStage(i).containsObjective(type)) {
fun.apply(q, otherQuest);
appliedQuestIDs.add(otherQuest.getId());
}
});
}
} else if (quest.getOptions().getShareProgressLevel() == 1) {
final List<Quester> mq = getMultiplayerQuesters(quest); final List<Quester> mq = getMultiplayerQuesters(quest);
if (mq == null) { if (mq == null) {
return appliedQuestIDs; return appliedQuestIDs;
@ -4453,6 +4482,34 @@ public class BukkitQuester implements Quester {
return mq; return mq;
} }
/**
* Get a list of fellow Questers participating in a global quest
*
* @param quest The server-wide, global quest
* @return Potentially empty list of Questers or null for invalid quest
*/
public List<Quester> getGlobalQuesters(final Quest quest) {
if (quest == null) {
return null;
}
final List<Quester> mq = new LinkedList<>();
if (quest.getOptions().canGiveGloballyAtLogin()) {
for (final Quester oq : plugin.getOnlineQuesters()) {
if (oq.getUUID().equals(getUUID())) {
continue;
}
if (oq.getCurrentQuests().containsKey(quest)) {
mq.add(oq);
}
}
if (mq.size() > 0 && plugin.getConfigSettings().getConsoleLogging() > 3) {
plugin.getLogger().info("Found " + mq.size() + " global members for quest ID "
+ quest.getId());
}
}
return mq;
}
/** /**
* Whether this Quester meets condition of given quest * Whether this Quester meets condition of given quest
* *

View File

@ -272,6 +272,9 @@ public class BukkitQuestFactory implements QuestFactory, ConversationAbandonedLi
context.setSessionData(Key.OPT_SHARE_DISTANCE, opt.getShareDistance()); context.setSessionData(Key.OPT_SHARE_DISTANCE, opt.getShareDistance());
context.setSessionData(Key.OPT_HANDLE_OFFLINE_PLAYERS, opt.canHandleOfflinePlayers()); context.setSessionData(Key.OPT_HANDLE_OFFLINE_PLAYERS, opt.canHandleOfflinePlayers());
context.setSessionData(Key.OPT_IGNORE_BLOCK_REPLACE, opt.canIgnoreBlockReplace()); context.setSessionData(Key.OPT_IGNORE_BLOCK_REPLACE, opt.canIgnoreBlockReplace());
context.setSessionData(Key.OPT_GIVE_GLOBALLY_AT_LOGIN, opt.canGiveGloballyAtLogin());
context.setSessionData(Key.OPT_ALLOW_STACKING_GLOBAL, opt.canAllowStackingGlobal());
context.setSessionData(Key.OPT_INFORM_QUEST_START, opt.canInformOnStart());
// Stages (Objectives) // Stages (Objectives)
int index = 1; int index = 1;
for (final Stage stage : bukkitQuest.getStages()) { for (final Stage stage : bukkitQuest.getStages()) {
@ -917,6 +920,12 @@ public class BukkitQuestFactory implements QuestFactory, ConversationAbandonedLi
? context.getSessionData(Key.OPT_HANDLE_OFFLINE_PLAYERS) : null); ? context.getSessionData(Key.OPT_HANDLE_OFFLINE_PLAYERS) : null);
opts.set("ignore-block-replace", context.getSessionData(Key.OPT_IGNORE_BLOCK_REPLACE) != null opts.set("ignore-block-replace", context.getSessionData(Key.OPT_IGNORE_BLOCK_REPLACE) != null
? context.getSessionData(Key.OPT_IGNORE_BLOCK_REPLACE) : null); ? context.getSessionData(Key.OPT_IGNORE_BLOCK_REPLACE) : null);
opts.set("give-at-login", context.getSessionData(Key.OPT_GIVE_GLOBALLY_AT_LOGIN) != null
? context.getSessionData(Key.OPT_GIVE_GLOBALLY_AT_LOGIN) : null);
opts.set("allow-stacking-global", context.getSessionData(Key.OPT_ALLOW_STACKING_GLOBAL) != null
? context.getSessionData(Key.OPT_ALLOW_STACKING_GLOBAL) : null);
opts.set("inform-on-start", context.getSessionData(Key.OPT_INFORM_QUEST_START) != null
? context.getSessionData(Key.OPT_INFORM_QUEST_START) : null);
if (opts.getKeys(false).isEmpty()) { if (opts.getKeys(false).isEmpty()) {
section.set("options", null); section.set("options", null);
} }

View File

@ -10,8 +10,6 @@
package me.pikamug.quests.quests.components; package me.pikamug.quests.quests.components;
import me.pikamug.quests.quests.components.Options;
public class BukkitOptions implements Options { public class BukkitOptions implements Options {
private boolean allowCommands = true; private boolean allowCommands = true;
private boolean allowQuitting = true; private boolean allowQuitting = true;
@ -23,6 +21,9 @@ public class BukkitOptions implements Options {
private int shareProgressLevel = 1; private int shareProgressLevel = 1;
private boolean shareSameQuestOnly = true; private boolean shareSameQuestOnly = true;
private boolean ignoreBlockReplace = true; private boolean ignoreBlockReplace = true;
private boolean giveGloballyAtLogin = false;
private boolean allowStackingGlobal = true;
private boolean informOnStart = true;
public boolean canAllowCommands() { public boolean canAllowCommands() {
return allowCommands; return allowCommands;
@ -103,4 +104,28 @@ public class BukkitOptions implements Options {
public void setIgnoreBlockReplace(final boolean ignoreBlockReplace) { public void setIgnoreBlockReplace(final boolean ignoreBlockReplace) {
this.ignoreBlockReplace = ignoreBlockReplace; this.ignoreBlockReplace = ignoreBlockReplace;
} }
public boolean canGiveGloballyAtLogin() {
return giveGloballyAtLogin;
}
public void setGiveGloballyAtLogin(final boolean giveGloballyAtLogin) {
this.giveGloballyAtLogin = giveGloballyAtLogin;
}
public boolean canAllowStackingGlobal() {
return allowStackingGlobal;
}
public void setAllowStackingGlobal(final boolean allowStackingGlobal) {
this.allowStackingGlobal = allowStackingGlobal;
}
public boolean canInformOnStart() {
return informOnStart;
}
public void setInformOnStart(final boolean informOnStart) {
this.informOnStart = informOnStart;
}
} }

View File

@ -296,8 +296,6 @@ public class BukkitQuestYamlStorage implements QuestStorageImpl {
if (config.contains("quests." + questId + ".options")) { if (config.contains("quests." + questId + ".options")) {
loadQuestOptions(config, quest, questId); loadQuestOptions(config, quest, questId);
} }
// TODO was this necessary?
//quest.setPlugin(this);
loadQuestStages(quest, config, questId); loadQuestStages(quest, config, questId);
loadQuestRewards(config, quest, questId); loadQuestRewards(config, quest, questId);
final Collection<Quest> loadedQuests = plugin.getLoadedQuests(); final Collection<Quest> loadedQuests = plugin.getLoadedQuests();
@ -769,6 +767,15 @@ public class BukkitQuestYamlStorage implements QuestStorageImpl {
if (config.contains("quests." + questKey + ".options.ignore-block-replace")) { if (config.contains("quests." + questKey + ".options.ignore-block-replace")) {
opts.setIgnoreBlockReplace(config.getBoolean("quests." + questKey + ".options.ignore-block-replace")); opts.setIgnoreBlockReplace(config.getBoolean("quests." + questKey + ".options.ignore-block-replace"));
} }
if (config.contains("quests." + questKey + ".options.give-at-login")) {
opts.setGiveGloballyAtLogin(config.getBoolean("quests." + questKey + ".options.give-at-login"));
}
if (config.contains("quests." + questKey + ".options.allow-stacking-global")) {
opts.setAllowStackingGlobal(config.getBoolean("quests." + questKey + ".options.allow-stacking-global"));
}
if (config.contains("quests." + questKey + ".options.inform-on-start")) {
opts.setInformOnStart(config.getBoolean("quests." + questKey + ".options.inform-on-start"));
}
} }
@SuppressWarnings({ "unchecked", "unused"}) @SuppressWarnings({ "unchecked", "unused"})