aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main/java/com/leonardobishop/quests/QuestsConfigLoader.java34
-rw-r--r--src/main/java/com/leonardobishop/quests/api/QuestsPlaceholders.java99
-rw-r--r--src/main/java/com/leonardobishop/quests/api/events/PlayerStartTrackQuestEvent.java33
-rw-r--r--src/main/java/com/leonardobishop/quests/api/events/PlayerStopTrackQuestEvent.java28
-rw-r--r--src/main/java/com/leonardobishop/quests/commands/CommandQuests.java6
-rw-r--r--src/main/java/com/leonardobishop/quests/events/EventInventory.java19
-rw-r--r--src/main/java/com/leonardobishop/quests/obj/Messages.java2
-rw-r--r--src/main/java/com/leonardobishop/quests/obj/Options.java8
-rw-r--r--src/main/java/com/leonardobishop/quests/obj/misc/QItemStack.java53
-rw-r--r--src/main/java/com/leonardobishop/quests/player/QPlayerManager.java3
-rw-r--r--src/main/java/com/leonardobishop/quests/player/questprogressfile/QPlayerPreferences.java18
-rw-r--r--src/main/java/com/leonardobishop/quests/player/questprogressfile/QuestProgressFile.java36
-rw-r--r--src/main/java/com/leonardobishop/quests/quests/Quest.java13
-rw-r--r--src/main/resources/config.yml23
-rw-r--r--src/main/resources/quests/example1.yml13
-rw-r--r--src/main/resources/quests/example2.yml15
-rw-r--r--src/main/resources/quests/example3.yml15
-rw-r--r--src/main/resources/quests/example4.yml11
-rw-r--r--src/main/resources/quests/example5.yml9
-rw-r--r--src/main/resources/quests/example6.yml9
-rw-r--r--src/main/resources/quests/example7.yml11
21 files changed, 349 insertions, 109 deletions
diff --git a/src/main/java/com/leonardobishop/quests/QuestsConfigLoader.java b/src/main/java/com/leonardobishop/quests/QuestsConfigLoader.java
index 8a25ee6d..24e89264 100644
--- a/src/main/java/com/leonardobishop/quests/QuestsConfigLoader.java
+++ b/src/main/java/com/leonardobishop/quests/QuestsConfigLoader.java
@@ -168,14 +168,21 @@ public class QuestsConfigLoader {
int cooldownTime = config.getInt("options.cooldown.time", 10);
int sortOrder = config.getInt("options.sort-order", 1);
String category = config.getString("options.category");
+ Map<String, String> placeholders = new HashMap<>();
+
+ if (config.isConfigurationSection("placeholders")) {
+ for (String p : config.getConfigurationSection("placeholders").getKeys(false)) {
+ placeholders.put(p, config.getString("placeholders." + p));
+ }
+ }
if (category == null) category = "";
Quest quest;
if (category.equals("")) {
- quest = new Quest(id, displayItem, rewards, requirements, repeatable, cooldown, cooldownTime, permissionRequired, rewardString, startString, sortOrder);
+ quest = new Quest(id, displayItem, rewards, requirements, repeatable, cooldown, cooldownTime, permissionRequired, rewardString, startString, placeholders, sortOrder);
} else {
- quest = new Quest(id, displayItem, rewards, requirements, repeatable, cooldown, cooldownTime, permissionRequired, rewardString, startString, category, sortOrder);
+ quest = new Quest(id, displayItem, rewards, requirements, repeatable, cooldown, cooldownTime, permissionRequired, rewardString, startString, placeholders, category, sortOrder);
Category c = plugin.getQuestManager().getCategoryById(category);
if (c != null) {
c.registerQuestId(id);
@@ -307,18 +314,9 @@ public class QuestsConfigLoader {
List<String> cLoreStarted = config.getStringList(path + ".lore-started");
String name;
- List<String> loreNormal = new ArrayList<>();
- if (cLoreNormal != null) {
- for (String s : cLoreNormal) {
- loreNormal.add(ChatColor.translateAlternateColorCodes('&', s));
- }
- }
- List<String> loreStarted = new ArrayList<>();
- if (cLoreStarted != null) {
- for (String s : cLoreStarted) {
- loreStarted.add(ChatColor.translateAlternateColorCodes('&', s));
- }
- }
+ List<String> loreNormal = translateColoursInList(cLoreNormal);
+ List<String> loreStarted = translateColoursInList(cLoreStarted);
+
name = ChatColor.translateAlternateColorCodes('&', cName);
ItemStack is = plugin.getItemStack(path, config,
@@ -327,6 +325,14 @@ public class QuestsConfigLoader {
return new QItemStack(plugin, name, loreNormal, loreStarted, is);
}
+ private List<String> translateColoursInList(List<String> list) {
+ List<String> coloured = new ArrayList<>();
+ for (String s : list) {
+ coloured.add(ChatColor.translateAlternateColorCodes('&', s));
+ }
+ return coloured;
+ }
+
public enum ConfigProblemDescriptions {
MALFORMED_YAML("Malformed YAML file, cannot read config"),
diff --git a/src/main/java/com/leonardobishop/quests/api/QuestsPlaceholders.java b/src/main/java/com/leonardobishop/quests/api/QuestsPlaceholders.java
index 2836f7ee..0b8e99f7 100644
--- a/src/main/java/com/leonardobishop/quests/api/QuestsPlaceholders.java
+++ b/src/main/java/com/leonardobishop/quests/api/QuestsPlaceholders.java
@@ -3,6 +3,7 @@ package com.leonardobishop.quests.api;
import com.leonardobishop.quests.Quests;
import com.leonardobishop.quests.api.enums.QuestStartResult;
import com.leonardobishop.quests.obj.Options;
+import com.leonardobishop.quests.obj.misc.QItemStack;
import com.leonardobishop.quests.player.QPlayer;
import com.leonardobishop.quests.player.questprogressfile.QuestProgressFile;
import com.leonardobishop.quests.quests.Category;
@@ -55,19 +56,20 @@ public class QuestsPlaceholders extends PlaceholderExpansion implements Cacheabl
@Override
public String onPlaceholderRequest(Player p, String params) {
if (p == null || !p.isOnline()) return null;
- if (cache.containsKey(p.getName()) && cache.get(p.getName()).containsKey(params)) return cache.get(p.getName()).get(params);
+ if (cache.containsKey(p.getName()) && cache.get(p.getName()).containsKey(params))
+ return cache.get(p.getName()).get(params);
String[] args = params.split("_", 4);
if (args.length < 1) return "Invalid Placeholder";
- final boolean save = args[args.length-1].toLowerCase().equals("cache");
+ final boolean save = args[args.length - 1].toLowerCase().equals("cache");
if (save) args = Arrays.copyOf(args, args.length - 1);
final QPlayer qPlayer = plugin.getPlayerManager().getPlayer(p.getUniqueId());
- String split = args[args.length-1];
+ String split = args[args.length - 1];
String result = "null";
- if (!args[0].contains(":")) {
+ if (!args[0].contains(":") && !args[0].equalsIgnoreCase("tracked")) {
if (args.length > 1 && split.equals(args[1])) split = ",";
switch (args[0].toLowerCase()) {
@@ -106,23 +108,36 @@ public class QuestsPlaceholders extends PlaceholderExpansion implements Cacheabl
plugin.getQuestManager().getCategories().forEach(c -> listCategories.add(c.getId()));
break;
default:
- return args[0] + "_" + args[1] + "is not a valid placeholder";
+ return args[0] + "_" + args[1] + " is not a valid placeholder";
}
result = String.join(split, listCategories);
}
break;
default:
- return args[0] + "is not a valid placeholder";
+ return args[0] + " is not a valid placeholder";
}
} else {
final String[] key = args[0].split(":");
switch (key[0].toLowerCase()) {
case "quest":
case "q":
- if (key.length == 1) return "Please specify quest name";
+ case "tracked":
+ if (!key[0].equalsIgnoreCase("tracked") && key.length == 1) return "Please specify quest name";
- final Quest quest = plugin.getQuestManager().getQuestById(key[1]);
- if (quest == null) return key[1] + "is not a quest";
+ final Quest quest;
+ if (!key[0].equalsIgnoreCase("tracked")) {
+ quest = plugin.getQuestManager().getQuestById(key[1]);
+ if (quest == null) return key[1] + " is not a quest";
+ } else {
+ quest = plugin.getQuestManager().getQuestById(qPlayer.getQuestProgressFile().getPlayerPreferences().getTrackedQuestId());
+ if (quest == null) {
+ if (args.length == 1) {
+ return "No tracked quest";
+ } else {
+ return "";
+ }
+ }
+ }
if (args.length == 1) {
result = quest.getDisplayNameStripped();
@@ -163,28 +178,41 @@ public class QuestsPlaceholders extends PlaceholderExpansion implements Cacheabl
result = (qPlayer.getQuestProgressFile().hasMetRequirements(quest) ? "true" : "false");
break;
default:
- if (!args[1].contains(":")) return args[0] + "_" + args[1] + "is not a valid placeholder";
+ if (!args[1].contains(":"))
+ return args[0] + "_" + args[1] + " is not a valid placeholder";
final String[] t = args[1].split(":");
- if (!t[0].toLowerCase().equals("task") && !t[0].toLowerCase().equals("t")) return args[0] + "_" + args[1] + "is not a valid placeholder";
- if (t.length == 1) return "Please specify task name";
+ if (t[0].equalsIgnoreCase("task") || t[0].equalsIgnoreCase("t")) {
+ if (t.length == 1) return "Please specify task name";
- if (args.length == 2) {
- result = qPlayer.getQuestProgressFile().getQuestProgress(quest).getTaskProgress(t[1]).getTaskId();
- } else {
- switch (args[2].toLowerCase()) {
- case "progress":
- case "p":
- final Object progress = qPlayer.getQuestProgressFile().getQuestProgress(quest).getTaskProgress(t[1]).getProgress();
- result = (progress == null ? "0" : String.valueOf(progress));
- break;
- case "completed":
- case "c":
- result = String.valueOf(qPlayer.getQuestProgressFile().getQuestProgress(quest).getTaskProgress(t[1]).isCompleted());
- break;
- default:
- return args[0] + "_" + args[1] + "_" + args[2] + "is not a valid placeholder";
+ if (args.length == 2) {
+ result = qPlayer.getQuestProgressFile().getQuestProgress(quest).getTaskProgress(t[1]).getTaskId();
+ } else {
+ switch (args[2].toLowerCase()) {
+ case "progress":
+ case "p":
+ final Object progress = qPlayer.getQuestProgressFile().getQuestProgress(quest).getTaskProgress(t[1]).getProgress();
+ result = (progress == null ? "0" : String.valueOf(progress));
+ break;
+ case "completed":
+ case "c":
+ result = String.valueOf(qPlayer.getQuestProgressFile().getQuestProgress(quest).getTaskProgress(t[1]).isCompleted());
+ break;
+ default:
+ return args[0] + "_" + args[1] + "_" + args[2] + " is not a valid placeholder";
+ }
}
+ } else if (t[0].equalsIgnoreCase("placeholder") || t[0].equalsIgnoreCase("p")) {
+ if (t.length == 1) return "Please specify placeholder name";
+
+ String placeholder = quest.getPlaceholders().get(t[1]);
+ if (placeholder == null) {
+ return t[1] + " is not a valid placeholder within quest " + quest.getId();
+ }
+ placeholder = QItemStack.processPlaceholders(Options.color(placeholder), qPlayer.getQuestProgressFile().getQuestProgress(quest));
+ return placeholder;
+ } else {
+ return args[0] + "_" + args[1] + " is not a valid placeholder";
}
}
}
@@ -195,7 +223,7 @@ public class QuestsPlaceholders extends PlaceholderExpansion implements Cacheabl
if (key.length == 1) return "Please specify category name";
final Category category = plugin.getQuestManager().getCategoryById(key[1]);
- if (category == null) return key[1] + "is not a category";
+ if (category == null) return key[1] + " is not a category";
if (args.length == 1) {
result = category.getDisplayNameStripped();
@@ -223,12 +251,12 @@ public class QuestsPlaceholders extends PlaceholderExpansion implements Cacheabl
result = (args.length == 2 ? String.valueOf(listStarted.size()) : parseList(listStarted, args[2], split));
break;
default:
- return args[0] + "_" + args[1] + "is not a valid placeholder";
+ return args[0] + "_" + args[1] + " is not a valid placeholder";
}
}
break;
default:
- return args[0] + "is not a valid placeholder";
+ return args[0] + " is not a valid placeholder";
}
}
return (save ? cache(p.getName(), params, result) : result);
@@ -245,7 +273,7 @@ public class QuestsPlaceholders extends PlaceholderExpansion implements Cacheabl
}
private String parseDate(String[] args, Long date) {
- final String format = (args[args.length-1].equals(args[1]) ? "dd/MM/yyyy" : args[args.length-1]);
+ final String format = (args[args.length - 1].equals(args[1]) ? "dd/MM/yyyy" : args[args.length - 1]);
SimpleDateFormat sdf;
if (formats.containsKey(format)) {
sdf = formats.get(format);
@@ -280,13 +308,16 @@ public class QuestsPlaceholders extends PlaceholderExpansion implements Cacheabl
if (quest != null) {
switch (filter) {
case STARTED:
- if (questP.getQuestProgressFile().getQuestProgress(quest).isStarted()) categoryQuests.add(quest);
+ if (questP.getQuestProgressFile().getQuestProgress(quest).isStarted())
+ categoryQuests.add(quest);
break;
case COMPLETED:
- if (questP.getQuestProgressFile().getQuestProgress(quest).isCompleted()) categoryQuests.add(quest);
+ if (questP.getQuestProgressFile().getQuestProgress(quest).isCompleted())
+ categoryQuests.add(quest);
break;
case COMPLETED_BEFORE:
- if (questP.getQuestProgressFile().getQuestProgress(quest).isCompletedBefore()) categoryQuests.add(quest);
+ if (questP.getQuestProgressFile().getQuestProgress(quest).isCompletedBefore())
+ categoryQuests.add(quest);
break;
default:
categoryQuests.add(quest);
diff --git a/src/main/java/com/leonardobishop/quests/api/events/PlayerStartTrackQuestEvent.java b/src/main/java/com/leonardobishop/quests/api/events/PlayerStartTrackQuestEvent.java
new file mode 100644
index 00000000..f16d755d
--- /dev/null
+++ b/src/main/java/com/leonardobishop/quests/api/events/PlayerStartTrackQuestEvent.java
@@ -0,0 +1,33 @@
+package com.leonardobishop.quests.api.events;
+
+import com.leonardobishop.quests.player.questprogressfile.QuestProgressFile;
+import org.bukkit.entity.Player;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.player.PlayerEvent;
+import org.jetbrains.annotations.NotNull;
+
+public class PlayerStartTrackQuestEvent extends PlayerEvent {
+ private final static HandlerList handlers = new HandlerList();
+ private final QuestProgressFile questProgressFile;
+
+ public PlayerStartTrackQuestEvent(@NotNull Player who, QuestProgressFile questProgressFile) {
+ super(who);
+ this.questProgressFile = questProgressFile;
+ }
+
+ public QuestProgressFile getQuestProgressFile() {
+ return questProgressFile;
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
+
+}
diff --git a/src/main/java/com/leonardobishop/quests/api/events/PlayerStopTrackQuestEvent.java b/src/main/java/com/leonardobishop/quests/api/events/PlayerStopTrackQuestEvent.java
new file mode 100644
index 00000000..b9f2114e
--- /dev/null
+++ b/src/main/java/com/leonardobishop/quests/api/events/PlayerStopTrackQuestEvent.java
@@ -0,0 +1,28 @@
+package com.leonardobishop.quests.api.events;
+
+import com.leonardobishop.quests.player.questprogressfile.QuestProgressFile;
+import org.bukkit.entity.Player;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.player.PlayerEvent;
+import org.jetbrains.annotations.NotNull;
+
+public class PlayerStopTrackQuestEvent extends PlayerEvent {
+
+ private final static HandlerList handlers = new HandlerList();
+ private final QuestProgressFile questProgressFile;
+
+ public PlayerStopTrackQuestEvent(@NotNull Player who, QuestProgressFile questProgressFile) {
+ super(who);
+ this.questProgressFile = questProgressFile;
+ }
+
+ public QuestProgressFile getQuestProgressFile() {
+ return questProgressFile;
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+}
diff --git a/src/main/java/com/leonardobishop/quests/commands/CommandQuests.java b/src/main/java/com/leonardobishop/quests/commands/CommandQuests.java
index 351806e2..6eabc12e 100644
--- a/src/main/java/com/leonardobishop/quests/commands/CommandQuests.java
+++ b/src/main/java/com/leonardobishop/quests/commands/CommandQuests.java
@@ -109,6 +109,9 @@ public class CommandQuests implements TabExecutor {
}
});
return true;
+ } else if (args[1].equalsIgnoreCase("wiki")) {
+ sender.sendMessage(ChatColor.RED + "Link to Quests wiki: " + ChatColor.GRAY + "https://github.com/LMBishop/Quests/wiki");
+ return true;
}
} else if (args.length == 3) {
if (args[1].equalsIgnoreCase("opengui")) {
@@ -516,6 +519,7 @@ public class CommandQuests implements TabExecutor {
sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a reload " + ChatColor.DARK_GRAY + ": reload Quests configuration");
sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a config " + ChatColor.DARK_GRAY + ": see detected problems in config");
sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a update " + ChatColor.DARK_GRAY + ": check for updates");
+ sender.sendMessage(ChatColor.DARK_GRAY + " * " + ChatColor.RED + "/quests a wiki " + ChatColor.DARK_GRAY + ": get a link to the Quests wiki");
}
sender.sendMessage(ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH + "-----=[" + ChatColor.RED + " requires permission: quests.admin " +
ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH + "]=-----");
@@ -561,7 +565,7 @@ public class CommandQuests implements TabExecutor {
return tabCompleteQuests(args[1]);
} else if (args[0].equalsIgnoreCase("a") || args[0].equalsIgnoreCase("admin")
&& sender.hasPermission("quests.admin")) {
- List<String> options = Arrays.asList("opengui", "moddata", "types", "reload", "update", "config", "info");
+ List<String> options = Arrays.asList("opengui", "moddata", "types", "reload", "update", "config", "info", "wiki");
return matchTabComplete(args[1], options);
}
} else if (args.length == 3) {
diff --git a/src/main/java/com/leonardobishop/quests/events/EventInventory.java b/src/main/java/com/leonardobishop/quests/events/EventInventory.java
index 10f76538..9554eb5e 100644
--- a/src/main/java/com/leonardobishop/quests/events/EventInventory.java
+++ b/src/main/java/com/leonardobishop/quests/events/EventInventory.java
@@ -9,6 +9,8 @@ import com.leonardobishop.quests.obj.misc.QMenuCancel;
import com.leonardobishop.quests.obj.misc.QMenuCategory;
import com.leonardobishop.quests.obj.misc.QMenuQuest;
import com.leonardobishop.quests.quests.Quest;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.ClickType;
@@ -79,16 +81,31 @@ public class EventInventory implements Listener {
// map which maps quests to slots so you do not have to compare the item stack
} else if (event.getSlot() < qMenuQuest.getPageSize() && qMenuQuest.getSlotsToMenu().containsKey(event.getSlot() + (((qMenuQuest
.getCurrentPage()) - 1) * qMenuQuest.getPageSize()))) {
- if (Options.QUEST_AUTOSTART.getBooleanValue()) return;
String questid = qMenuQuest.getSlotsToMenu().get(event.getSlot() + (((qMenuQuest.getCurrentPage()) - 1) * qMenuQuest.getPageSize()));
Quest quest = plugin.getQuestManager().getQuestById(questid);
if (event.getClick() == ClickType.LEFT) {
+ if (Options.QUEST_AUTOSTART.getBooleanValue()) return;
if (qMenuQuest.getOwner().getQuestProgressFile().startQuest(quest) == QuestStartResult.QUEST_SUCCESS) {
event.getWhoClicked().closeInventory(); //TODO Option to keep the menu open
}
+ } else if (event.getClick() == ClickType.MIDDLE) {
+ if (qMenuQuest.getOwner().getQuestProgressFile().hasStartedQuest(quest)) {
+ Player player = Bukkit.getPlayer(qMenuQuest.getOwner().getUuid());
+ String tracked = qMenuQuest.getOwner().getQuestProgressFile().getPlayerPreferences().getTrackedQuestId();
+
+ if (questid.equals(tracked)) {
+ player.sendMessage(Messages.QUEST_TRACK_STOP.getMessage().replace("{quest}", quest.getDisplayNameStripped()));
+ qMenuQuest.getOwner().getQuestProgressFile().trackQuest(null);
+ } else {
+ player.sendMessage(Messages.QUEST_TRACK.getMessage().replace("{quest}", quest.getDisplayNameStripped()));
+ qMenuQuest.getOwner().getQuestProgressFile().trackQuest(quest);
+ }
+ event.getWhoClicked().closeInventory();
+ }
} else if (event.getClick() == ClickType.RIGHT && Options.ALLOW_QUEST_CANCEL.getBooleanValue()
&& qMenuQuest.getOwner().getQuestProgressFile().hasStartedQuest(quest)) {
+ if (Options.QUEST_AUTOSTART.getBooleanValue()) return;
QMenuCancel qMenuCancel = new QMenuCancel(qMenuQuest.getOwner(), qMenuQuest, quest);
buffer.add(event.getWhoClicked().getUniqueId());
event.getWhoClicked().openInventory(qMenuCancel.toInventory());
diff --git a/src/main/java/com/leonardobishop/quests/obj/Messages.java b/src/main/java/com/leonardobishop/quests/obj/Messages.java
index 31e74b0b..44d82f07 100644
--- a/src/main/java/com/leonardobishop/quests/obj/Messages.java
+++ b/src/main/java/com/leonardobishop/quests/obj/Messages.java
@@ -9,6 +9,8 @@ public enum Messages {
QUEST_START("messages.quest-start"),
QUEST_COMPLETE("messages.quest-complete"),
QUEST_CANCEL("messages.quest-cancel"),
+ QUEST_TRACK("messages.quest-track"),
+ QUEST_TRACK_STOP("messages.quest-track-stop"),
QUEST_START_LIMIT("messages.quest-start-limit"),
QUEST_START_DISABLED("messages.quest-start-disabled"),
QUEST_START_LOCKED("messages.quest-start-locked"),
diff --git a/src/main/java/com/leonardobishop/quests/obj/Options.java b/src/main/java/com/leonardobishop/quests/obj/Options.java
index 18611dfb..537ba18a 100644
--- a/src/main/java/com/leonardobishop/quests/obj/Options.java
+++ b/src/main/java/com/leonardobishop/quests/obj/Options.java
@@ -27,7 +27,11 @@ public enum Options {
TAB_COMPLETE_ENABLED("options.tab-completion.enabled"),
ERROR_CHECKING_OVERRIDE("options.error-checking.override-errors"),
QUEST_AUTOSTART("options.quest-autostart"),
- GLOBAL_TASK_CONFIGURATION_OVERRIDE("options.global-task-configuration-override");
+ QUEST_AUTOTRACK("options.quest-autotrack"),
+ GLOBAL_TASK_CONFIGURATION_OVERRIDE("options.global-task-configuration-override"),
+ GLOBAL_QUEST_DISPLAY_LORE_APPEND_NOT_STARTED("global-quest-display.lore.append-not-started"),
+ GLOBAL_QUEST_DISPLAY_LORE_APPEND_STARTED("global-quest-display.lore.append-started"),
+ GLOBAL_QUEST_DISPLAY_LORE_APPEND_TRACKED("global-quest-display.lore.append-tracked");
private static final Map<String, Boolean> cachedBooleans = new HashMap<>();
@@ -76,6 +80,8 @@ public enum Options {
}
public static List<String> color(List<String> s) {
+ if (s == null || s.size() == 0) return s;
+
List<String> colored = new ArrayList<>();
for (String line : s) {
colored.add(ChatColor.translateAlternateColorCodes('&', line));
diff --git a/src/main/java/com/leonardobishop/quests/obj/misc/QItemStack.java b/src/main/java/com/leonardobishop/quests/obj/misc/QItemStack.java
index 20bb3eb7..edb1b963 100644
--- a/src/main/java/com/leonardobishop/quests/obj/misc/QItemStack.java
+++ b/src/main/java/com/leonardobishop/quests/obj/misc/QItemStack.java
@@ -24,6 +24,9 @@ public class QItemStack {
private String name;
private List<String> loreNormal;
private List<String> loreStarted;
+ private List<String> globalLoreAppendNotStarted;
+ private List<String> globalLoreAppendStarted;
+ private List<String> globalLoreAppendTracked;
private ItemStack startingItemStack;
public QItemStack(Quests plugin, String name, List<String> loreNormal, List<String> loreStarted, ItemStack startingItemStack) {
@@ -32,6 +35,10 @@ public class QItemStack {
this.loreNormal = loreNormal;
this.loreStarted = loreStarted;
this.startingItemStack = startingItemStack;
+
+ this.globalLoreAppendNotStarted = Options.color(Options.GLOBAL_QUEST_DISPLAY_LORE_APPEND_NOT_STARTED.getStringListValue());
+ this.globalLoreAppendStarted = Options.color(Options.GLOBAL_QUEST_DISPLAY_LORE_APPEND_STARTED.getStringListValue());
+ this.globalLoreAppendTracked = Options.color(Options.GLOBAL_QUEST_DISPLAY_LORE_APPEND_TRACKED.getStringListValue());
}
public String getName() {
@@ -84,29 +91,20 @@ public class QItemStack {
} catch (Exception ignored) {
}
+ if (quest.getId().equals(questProgressFile.getPlayerPreferences().getTrackedQuestId())) {
+ if (globalLoreAppendTracked != null) tempLore.addAll(globalLoreAppendTracked);
+ } else {
+ if (globalLoreAppendStarted != null) tempLore.addAll(globalLoreAppendStarted);
+ }
+ } else {
+ if (globalLoreAppendNotStarted != null) tempLore.addAll(globalLoreAppendNotStarted);
}
if (plugin.getPlaceholderAPIHook() != null && Options.GUI_USE_PLACEHOLDERAPI.getBooleanValue()) {
ism.setDisplayName(plugin.getPlaceholderAPIHook().replacePlaceholders(player, ism.getDisplayName()));
}
if (questProgress != null) {
for (String s : tempLore) {
- Matcher m = Pattern.compile("\\{([^}]+)}").matcher(s);
- while (m.find()) {
- String[] parts = m.group(1).split(":");
- if (parts.length > 1) {
- if (questProgress.getTaskProgress(parts[0]) == null) {
- continue;
- }
- if (parts[1].equals("progress")) {
- String str = String.valueOf(questProgress.getTaskProgress(parts[0]).getProgress());
- s = s.replace("{" + m.group(1) + "}", (str.equals("null") ? String.valueOf(0) : str));
- }
- if (parts[1].equals("complete")) {
- String str = String.valueOf(questProgress.getTaskProgress(parts[0]).isCompleted());
- s = s.replace("{" + m.group(1) + "}", str);
- }
- }
- }
+ s = processPlaceholders(s, questProgress);
if (plugin.getPlaceholderAPIHook() != null && Options.GUI_USE_PLACEHOLDERAPI.getBooleanValue()) {
s = plugin.getPlaceholderAPIHook().replacePlaceholders(player, s);
}
@@ -117,4 +115,25 @@ public class QItemStack {
is.setItemMeta(ism);
return is;
}
+
+ public static String processPlaceholders(String s, QuestProgress questProgress) {
+ Matcher m = Pattern.compile("\\{([^}]+)}").matcher(s);
+ while (m.find()) {
+ String[] parts = m.group(1).split(":");
+ if (parts.length > 1) {
+ if (questProgress.getTaskProgress(parts[0]) == null) {
+ continue;
+ }
+ if (parts[1].equals("progress")) {
+ String str = String.valueOf(questProgress.getTaskProgress(parts[0]).getProgress());
+ s = s.replace("{" + m.group(1) + "}", (str.equals("null") ? String.valueOf(0) : str));
+ }
+ if (parts[1].equals("complete")) {
+ String str = String.valueOf(questProgress.getTaskProgress(parts[0]).isCompleted());
+ s = s.replace("{" + m.group(1) + "}", str);
+ }
+ }
+ }
+ return s;
+ }
}
diff --git a/src/main/java/com/leonardobishop/quests/player/QPlayerManager.java b/src/main/java/com/leonardobishop/quests/player/QPlayerManager.java
index d531f168..dfbb4bab 100644
--- a/src/main/java/com/leonardobishop/quests/player/QPlayerManager.java
+++ b/src/main/java/com/leonardobishop/quests/player/QPlayerManager.java
@@ -2,6 +2,7 @@ package com.leonardobishop.quests.player;
import com.leonardobishop.quests.Quests;
import com.leonardobishop.quests.QuestsLogger;
+import com.leonardobishop.quests.player.questprogressfile.QPlayerPreferences;
import com.leonardobishop.quests.player.questprogressfile.QuestProgress;
import com.leonardobishop.quests.player.questprogressfile.QuestProgressFile;
import com.leonardobishop.quests.player.questprogressfile.TaskProgress;
@@ -75,7 +76,7 @@ public class QPlayerManager {
public void loadPlayer(UUID uuid) {
plugin.getQuestsLogger().debug("Loading player " + uuid + " from disk.");
if (qPlayers.get(uuid) == null) {
- QuestProgressFile questProgressFile = new QuestProgressFile(uuid, plugin);
+ QuestProgressFile questProgressFile = new QuestProgressFile(uuid, new QPlayerPreferences(null), plugin);
try {
File directory = new File(plugin.getDataFolder() + File.separator + "playerdata");
diff --git a/src/main/java/com/leonardobishop/quests/player/questprogressfile/QPlayerPreferences.java b/src/main/java/com/leonardobishop/quests/player/questprogressfile/QPlayerPreferences.java
new file mode 100644
index 00000000..764189f9
--- /dev/null
+++ b/src/main/java/com/leonardobishop/quests/player/questprogressfile/QPlayerPreferences.java
@@ -0,0 +1,18 @@
+package com.leonardobishop.quests.player.questprogressfile;
+
+public class QPlayerPreferences {
+
+ private String trackedQuestId;
+
+ public QPlayerPreferences(String trackedQuestId) {
+ this.trackedQuestId = trackedQuestId;
+ }
+
+ public String getTrackedQuestId() {
+ return trackedQuestId;
+ }
+
+ public void setTrackedQuestId(String trackedQuestId) {
+ this.trackedQuestId = trackedQuestId;
+ }
+}
diff --git a/src/main/java/com/leonardobishop/quests/player/questprogressfile/QuestProgressFile.java b/src/main/java/com/leonardobishop/quests/player/questprogressfile/QuestProgressFile.java
index 83dcb80a..b2845e67 100644
--- a/src/main/java/com/leonardobishop/quests/player/questprogressfile/QuestProgressFile.java
+++ b/src/main/java/com/leonardobishop/quests/player/questprogressfile/QuestProgressFile.java
@@ -3,10 +3,7 @@ package com.leonardobishop.quests.player.questprogressfile;
import com.leonardobishop.quests.Quests;
import com.leonardobishop.quests.api.QuestsAPI;
import com.leonardobishop.quests.api.enums.QuestStartResult;
-import com.leonardobishop.quests.api.events.PlayerCancelQuestEvent;
-import com.leonardobishop.quests.api.events.PlayerFinishQuestEvent;
-import com.leonardobishop.quests.api.events.PlayerStartQuestEvent;
-import com.leonardobishop.quests.api.events.PreStartQuestEvent;
+import com.leonardobishop.quests.api.events.*;
import com.leonardobishop.quests.obj.Messages;
import com.leonardobishop.quests.obj.Options;
import com.leonardobishop.quests.player.QPlayer;
@@ -25,11 +22,13 @@ import java.util.concurrent.TimeUnit;
public class QuestProgressFile {
private final Map<String, QuestProgress> questProgress = new HashMap<>();
+ private final QPlayerPreferences playerPreferences;
private final UUID playerUUID;
private final Quests plugin;
- public QuestProgressFile(UUID player, Quests plugin) {
- this.playerUUID = player;
+ public QuestProgressFile(UUID playerUUID, QPlayerPreferences playerPreferences, Quests plugin) {
+ this.playerUUID = playerUUID;
+ this.playerPreferences = playerPreferences;
this.plugin = plugin;
}
@@ -43,6 +42,9 @@ public class QuestProgressFile {
questProgress.setCompleted(true);
questProgress.setCompletedBefore(true);
questProgress.setCompletionDate(System.currentTimeMillis());
+ if (Options.QUEST_AUTOTRACK.getBooleanValue() && !(quest.isRepeatable() && !quest.isCooldownEnabled())) {
+ trackQuest(null);
+ }
Player player = Bukkit.getPlayer(this.playerUUID);
if (player != null) {
QPlayer questPlayer = QuestsAPI.getPlayerManager().getPlayer(this.playerUUID);
@@ -70,6 +72,21 @@ public class QuestProgressFile {
return true;
}
+ public void trackQuest(Quest quest) {
+ Player player = Bukkit.getPlayer(playerUUID);
+ if (quest == null) {
+ playerPreferences.setTrackedQuestId(null);
+ if (player != null) {
+ Bukkit.getPluginManager().callEvent(new PlayerStopTrackQuestEvent(player, this));
+ }
+ } else if (hasStartedQuest(quest)) {
+ playerPreferences.setTrackedQuestId(quest.getId());
+ if (player != null) {
+ Bukkit.getPluginManager().callEvent(new PlayerStartTrackQuestEvent(player, this));
+ }
+ }
+ }
+
/**
* Check if the player can start a quest.
* <p>
@@ -179,6 +196,9 @@ public class QuestProgressFile {
taskProgress.setCompleted(false);
taskProgress.setProgress(null);
}
+ if (Options.QUEST_AUTOTRACK.getBooleanValue()) {
+ trackQuest(quest);
+ }
questProgress.setCompleted(false);
if (player != null) {
QPlayer questPlayer = QuestsAPI.getPlayerManager().getPlayer(this.playerUUID);
@@ -390,6 +410,10 @@ public class QuestProgressFile {
return false;
}
+ public QPlayerPreferences getPlayerPreferences() {
+ return playerPreferences;
+ }
+
public void saveToDisk() {
saveToDisk(false, false);
}
diff --git a/src/main/java/com/leonardobishop/quests/quests/Quest.java b/src/main/java/com/leonardobishop/quests/quests/Quest.java
index 78bbc6b0..8c02a6c4 100644
--- a/src/main/java/com/leonardobishop/quests/quests/Quest.java
+++ b/src/main/java/com/leonardobishop/quests/quests/Quest.java
@@ -8,7 +8,6 @@ import java.util.*;
public class Quest implements Comparable<Quest> {
private Map<String, Task> tasks = new HashMap<>();
- //TODO: maybe ALSO store by <tasktypename (string), list<task>>
private final String id;
private final QItemStack displayItem;
private final List<String> rewards;
@@ -20,15 +19,16 @@ public class Quest implements Comparable<Quest> {
private final int cooldown;
private final int sortOrder;
private final boolean permissionRequired;
+ private final Map<String, String> placeholders;
private String categoryid;
- public Quest(String id, QItemStack displayItem, List<String> rewards, List<String> requirements, boolean repeatable, boolean cooldownEnabled, int cooldown, boolean permissionRequired, List<String> rewardString, List<String> startString, String categoryid, int sortOrder) {
- this(id, displayItem, rewards, requirements, repeatable, cooldownEnabled, cooldown, permissionRequired, rewardString, startString, sortOrder);
+ public Quest(String id, QItemStack displayItem, List<String> rewards, List<String> requirements, boolean repeatable, boolean cooldownEnabled, int cooldown, boolean permissionRequired, List<String> rewardString, List<String> startString, Map<String, String> placeholders, String categoryid, int sortOrder) {
+ this(id, displayItem, rewards, requirements, repeatable, cooldownEnabled, cooldown, permissionRequired, rewardString, startString, placeholders, sortOrder);
this.categoryid = categoryid;
}
- public Quest(String id, QItemStack displayItem, List<String> rewards, List<String> requirements, boolean repeatable, boolean cooldownEnabled, int cooldown, boolean permissionRequired, List<String> rewardString, List<String> startString, int sortOrder) {
+ public Quest(String id, QItemStack displayItem, List<String> rewards, List<String> requirements, boolean repeatable, boolean cooldownEnabled, int cooldown, boolean permissionRequired, List<String> rewardString, List<String> startString, Map<String, String> placeholders, int sortOrder) {
this.id = id;
this.displayItem = displayItem;
this.rewards = rewards;
@@ -39,6 +39,7 @@ public class Quest implements Comparable<Quest> {
this.permissionRequired = permissionRequired;
this.rewardString = rewardString;
this.startString = startString;
+ this.placeholders = placeholders;
this.sortOrder = sortOrder;
}
@@ -113,6 +114,10 @@ public class Quest implements Comparable<Quest> {
return ChatColor.stripColor(this.displayItem.getName());
}
+ public Map<String, String> getPlaceholders() {
+ return placeholders;
+ }
+
@Override
public int compareTo(Quest quest) {
return (sortOrder - quest.sortOrder);
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 0d6e8fa5..7379458d 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -137,7 +137,7 @@ options:
quest-started-limit: 2
# Hide locked quests, quests on cooldown and completed (but not repeatable) quests
gui-hide-locked: false
- # Allow players to cancel a quest
+ # Allow players to cancel a quest (you may want to remove the cancel instructions in the global item lore)
allow-quest-cancel: true
# Titles for the GUIs
guinames:
@@ -155,6 +155,8 @@ options:
gui-use-placeholderapi: false
# Make it so players do not have to start quest themselves
quest-autostart: false
+ # Automatically track quests on start, and stop tracking on completion
+ quest-autotrack: true
# How much quests should log, 0 = errors only, 1 = warnings, 2 = info, 3 = debug
verbose-logging-level: 2
# Automatically clean player's quest progress files when they join.
@@ -202,6 +204,23 @@ daily-quests:
# inventory:
# update-progress: true
+global-quest-display:
+ lore:
+ append-not-started:
+ - ""
+ - "&eLeft Click &7to start this quest."
+ append-started:
+ - ""
+ - "&aYou have started this quest."
+ - "&eMiddle Click &7to track this quest."
+ - "&eRight Click &7to cancel this quest."
+ append-tracked:
+ - ""
+ - "&aYou are &etracking &athis quest."
+ - "&eMiddle Click &7to stop tracking this quest."
+ - "&eRight Click &7to cancel this quest."
+
+
# Configure titles
titles:
quest-start:
@@ -217,6 +236,8 @@ messages:
quest-start: "&7Quest &c{quest} &7started!"
quest-complete: "&7Quest &c{quest} &7completed!"
quest-cancel: "&7Quest &c{quest} &7cancelled!"
+ quest-track: "&7Tracking quest &c{quest}&7."
+ quest-track-stop: "&7No longer tracking quest &c{quest}&7."
quest-start-limit: "&7Players are limited to &c{limit} &7started quests at a time."
quest-start-disabled: "&7You cannot repeat this quest."
quest-start-locked: "&7You have not unlocked this quest yet."
diff --git a/src/main/resources/quests/example1.yml b/src/main/resources/quests/example1.yml
index d0d44203..ca57f714 100644
--- a/src/main/resources/quests/example1.yml
+++ b/src/main/resources/quests/example1.yml
@@ -21,16 +21,16 @@ display:
- "&cattributes a quest can have."
- ""
- "&7This quest requires you to:"
- - "&7 - Break 30 blocks."
+ - "&7 - Break &f30 blocks&7."
- ""
- "&7Rewards:"
- - "&7 - 10 diamonds."
+ - "&7 - &f10 &7diamonds."
# This lore will be appended to the bottom of the above lore when the player starts their quest.
# To get the players progress through a task, use {TASKID:progress} and replace TASKID with the ID of the task.
lore-started:
- ""
- "&7Your current progression:"
- - "&7 - {mining:progress}/30 blocks broken."
+ - "&7 - &f{mining:progress}&7/30 blocks broken."
# This is the material of the item. It is recommended to stick to bukkit names.
type: "WOODEN_PICKAXE"
@@ -38,6 +38,13 @@ display:
rewards:
- "give {player} diamond 10"
+# These placeholders are accessible using PlaceholderAPI, for example %quests_tracked_p:description%
+# They are useful for putting information about the players tracked quest on a scoreboard
+# You may want to keep the names of them the same for ALL quests for this use-case
+placeholders:
+ description: "&7Break &f30 blocks &7of any type."
+ progress: " &8- &f{mining:progress}&7/30 broken"
+
# Everything inside this section define quest-specific options
options:
# This is the category for the quest, it will appear under the "examples" category. Categories can be disabled.
diff --git a/src/main/resources/quests/example2.yml b/src/main/resources/quests/example2.yml
index 1ed6c0e1..7ad82bea 100644
--- a/src/main/resources/quests/example2.yml
+++ b/src/main/resources/quests/example2.yml
@@ -16,21 +16,24 @@ display:
- "&cmultiple things to be done, unlike the previous one."
- ""
- "&7This quest requires you to:"
- - "&7 - Break 100 blocks."
- - "&7 - Place 100 blocks."
+ - "&7 - Break &f100 &7blocks."
+ - "&7 - Place &f100 &7blocks."
- ""
- "&7Rewards:"
- - "&7 - 15 diamonds."
- - "&7 - $50 added to your in-game balance."
+ - "&7 - &f15 diamonds&7."
+ - "&7 - &f$50&7 added to your in-game balance."
lore-started:
- ""
- "&7Your current progression:"
- - "&7 - {mining:progress}/100 blocks broken."
- - "&7 - {building:progress}/100 blocks placed."
+ - "&7 - &f{mining:progress}&7/100 blocks broken."
+ - "&7 - &f{building:progress}&7/100 blocks placed."
type: "GRASS_BLOCK"
rewards:
- "give {player} diamond 15"
- "eco give {player} 50"
+placeholders:
+ description: "&7Break and place &f100 blocks &7of any type."
+ progress: " &8- &f{mining:progress}&7/100 broken, &f{building:progress}&7/100 placed"
options:
category: "examples"
# Unlike the previous quest, this quest has "example1" as a required quest. You cannot start this quest without "example1" quest complete.
diff --git a/src/main/resources/quests/example3.yml b/src/main/resources/quests/example3.yml
index 29e4f91a..dbd893e4 100644
--- a/src/main/resources/quests/example3.yml
+++ b/src/main/resources/quests/example3.yml
@@ -19,21 +19,24 @@ display:
- "&cafter a cooldown, unlike the previous one."
- ""
- "&7This quest requires you to:"
- - "&7 - Break 81 gold ore."
- - "&7 - Place 9 gold blocks."
+ - "&7 - Break &f81 gold ore&7."
+ - "&7 - Place &f9 gold blocks&7."
- ""
- "&7Rewards:"
- - "&7 - 30 diamonds."
- - "&7 - $10 added to your in-game balance."
+ - "&7 - &f30 diamonds&7."
+ - "&7 - &f$10&7 added to your in-game balance."
lore-started:
- ""
- "&7Your current progression:"
- - "&7 - {mining:progress}/81 gold ore broken."
- - "&7 - {building:progress}/9 gold blocks placed."
+ - "&7 - &f{mining:progress}&7/81 gold ore broken."
+ - "&7 - &f{building:progress}&7/9 gold blocks placed."
type: "GOLD_ORE"
rewards:
- "give {player} diamond 30"
- "eco give {player} 10"
+placeholders:
+ description: "&7Break &f81 gold ore &7and place &f9 gold blocks."
+ progress: " &8- &f{mining:progress}&7/81 gold ore, &f{building:progress}&7/9 gold blocks"
options:
category: "examples"
requires:
diff --git a/src/main/resources/quests/example4.yml b/src/main/resources/quests/example4.yml
index 28734ae7..4e9c27a8 100644
--- a/src/main/resources/quests/example4.yml
+++ b/src/main/resources/quests/example4.yml
@@ -14,15 +14,15 @@ display:
- "&cunlike the previous one."
- ""
- "&7This quest requires you to:"
- - "&7 - Kill 3 mobs."
+ - "&7 - Kill &f3 &7mobs."
- ""
- "&7Rewards:"
- - "&7 - $50 added to your in-game balance."
- - "&7 - 1 diamond ."
+ - "&7 - &f$50 &7added to your in-game balance."
+ - "&7 - &f1 diamond&7."
lore-started:
- ""
- "&7Your current progression:"
- - "&7 - {mobkilling:progress}/3 mobs killed."
+ - "&7 - &f{mobkilling:progress}&7/3 mobs killed."
type: "STRING"
# Here you can list messages which will be sent to the player (if they are online) upon the quest starting.
startstring:
@@ -32,6 +32,9 @@ startstring:
rewards:
- "eco give {player} 50"
- "give {player} diamond 1"
+placeholders:
+ description: "&7Kill &f3 &7mobs."
+ progress: " &8- &f{mobkilling:progress}&7/3 mobs"
# Here you can list messages which will be sent to the player (if they are online) upon completion.
rewardstring:
- " &8* &c$1000 &7was added to your in-game balance."
diff --git a/src/main/resources/quests/example5.yml b/src/main/resources/quests/example5.yml
index bfdc8d94..cb92f0f1 100644
--- a/src/main/resources/quests/example5.yml
+++ b/src/main/resources/quests/example5.yml
@@ -10,17 +10,20 @@ display:
- "&ccertain permissions."
- ""
- "&7This quest requires you to:"
- - "&7 - Place 10 blocks."
+ - "&7 - Place &f10 &7blocks."
- ""
- "&7Rewards:"
- - "&7 - $10 added to your in-game balance."
+ - "&7 - &f$10 &7added to your in-game balance."
lore-started:
- ""
- "&7Your current progression:"
- - "&7 - {building:progress}/10 blocks placed."
+ - "&7 - &f{building:progress}&7/10 blocks placed."
type: "GRASS_BLOCK"
rewards:
- "eco give {player} 10"
+placeholders:
+ description: "&7Place &f10 &7blocks of any type."
+ progress: " &8- &f{building:progress}&7/10 blocks"
options:
category: "examples"
requires:
diff --git a/src/main/resources/quests/example6.yml b/src/main/resources/quests/example6.yml
index abadba2a..0913a7cb 100644
--- a/src/main/resources/quests/example6.yml
+++ b/src/main/resources/quests/example6.yml
@@ -10,17 +10,20 @@ display:
- "&ccertain permissions."
- ""
- "&7This quest requires you to:"
- - "&7 - Place 10 blocks."
+ - "&7 - Place &f10 &7blocks."
- ""
- "&7Rewards:"
- - "&7 - $10 added to your in-game balance."
+ - "&7 - &f$10 &7added to your in-game balance."
lore-started:
- ""
- "&7Your current progression:"
- - "&7 - {building:progress}/10 blocks placed."
+ - "&7 - &f{building:progress}&7/10 blocks placed."
type: "GRASS_BLOCK"
rewards:
- "eco give {player} 10"
+placeholders:
+ description: "&7Place &f10 &7blocks of any type."
+ progress: " &8- &f{building:progress}&7/10 blocks"
options:
category: "permissionexample"
# This quest has no specific permission, however its category does. The permission for the category is "quests.category.permissionexample"
diff --git a/src/main/resources/quests/example7.yml b/src/main/resources/quests/example7.yml
index 96bbb376..32bb7e04 100644
--- a/src/main/resources/quests/example7.yml
+++ b/src/main/resources/quests/example7.yml
@@ -5,24 +5,27 @@ tasks:
worlds:
- "world"
display:
- name: "&cExample VI (Different category, world restricted)"
+ name: "&cExample VII (Different category, world restricted)"
lore-normal:
- "&cThis category is designed to show you the different"
- "&cattributes a quest can have. This quest requires"
- "&chas a task which requires you to be in a world called 'world'."
- ""
- "&7This quest requires you to:"
- - "&7 - Place 10 blocks."
+ - "&7 - Place &f10 &7blocks."
- ""
- "&7Rewards:"
- - "&7 - $10 added to your in-game balance."
+ - "&7 - &f$10 &7added to your in-game balance."
lore-started:
- ""
- "&7Your current progression:"
- - "&7 - {building:progress}/10 blocks placed."
+ - "&7 - &f{building:progress}&7/10 blocks placed."
type: "GRASS_BLOCK"
rewards:
- "eco give {player} 10"
+placeholders:
+ description: "&7Place &f10 &7blocks of any type in world &fworld."
+ progress: " &8- &f{building:progress}&7/10 blocks"
options:
category: "permissionexample"
# This quest has no specific permission, however its category does. The permission for the category is "quests.category.permissionexample"