From 3d02c6b6169848c1277022e39ed6dc0297fb0854 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 8 May 2022 22:07:36 +0200 Subject: [PATCH 001/111] Started 0.20 development --- core/pom.xml | 2 +- core/src/main/resources/plugin.yml | 4 ++-- dist/pom.xml | 2 +- pom.xml | 4 ++-- v1_10_R1/pom.xml | 2 +- v1_11_R1/pom.xml | 2 +- v1_12_R1/pom.xml | 2 +- v1_13_R2/pom.xml | 2 +- v1_14_R1/pom.xml | 2 +- v1_15_R1/pom.xml | 2 +- v1_16_R1/pom.xml | 2 +- v1_16_R2/pom.xml | 2 +- v1_16_R3/pom.xml | 2 +- v1_17_R1/pom.xml | 2 +- v1_18_R1/pom.xml | 2 +- v1_18_R2/pom.xml | 2 +- v1_9_R1/pom.xml | 2 +- v1_9_R2/pom.xml | 2 +- 18 files changed, 20 insertions(+), 20 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index 1573a189..382a2ab3 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -8,7 +8,7 @@ fr.skytasul beautyquests-parent - 0.19.3 + 0.20.0-SNAPSHOT diff --git a/core/src/main/resources/plugin.yml b/core/src/main/resources/plugin.yml index afb3640c..40a557e3 100644 --- a/core/src/main/resources/plugin.yml +++ b/core/src/main/resources/plugin.yml @@ -1,7 +1,7 @@ name: BeautyQuests author: SkytAsul -#version: "${human.version}_BUILD${build.number}" -version: ${human.version} +version: "${human.version}_BUILD${build.number}" +#version: ${human.version} description: Quests system with a simple graphical interface. website: https://www.spigotmc.org/resources/beautyquests.39255/ api-version: 1.13 diff --git a/dist/pom.xml b/dist/pom.xml index 8d3f032f..3022238f 100644 --- a/dist/pom.xml +++ b/dist/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.3 + 0.20.0-SNAPSHOT diff --git a/pom.xml b/pom.xml index 3fc9018c..1af23378 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ fr.skytasul beautyquests-parent - 0.19.3 + 0.20.0-SNAPSHOT pom beautyquests @@ -16,7 +16,7 @@ 1.8 1.8 unknown - 0.19.3 + 0.20 diff --git a/v1_10_R1/pom.xml b/v1_10_R1/pom.xml index ea5cbf9a..d4f38303 100644 --- a/v1_10_R1/pom.xml +++ b/v1_10_R1/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.3 + 0.20.0-SNAPSHOT true diff --git a/v1_11_R1/pom.xml b/v1_11_R1/pom.xml index 53d2b1eb..5cf22d9b 100644 --- a/v1_11_R1/pom.xml +++ b/v1_11_R1/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.3 + 0.20.0-SNAPSHOT true diff --git a/v1_12_R1/pom.xml b/v1_12_R1/pom.xml index 2d297536..cdbfc778 100644 --- a/v1_12_R1/pom.xml +++ b/v1_12_R1/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.3 + 0.20.0-SNAPSHOT true diff --git a/v1_13_R2/pom.xml b/v1_13_R2/pom.xml index 9d3dd94f..94b65337 100644 --- a/v1_13_R2/pom.xml +++ b/v1_13_R2/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.3 + 0.20.0-SNAPSHOT true diff --git a/v1_14_R1/pom.xml b/v1_14_R1/pom.xml index 34414885..6f9f59f0 100644 --- a/v1_14_R1/pom.xml +++ b/v1_14_R1/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.3 + 0.20.0-SNAPSHOT true diff --git a/v1_15_R1/pom.xml b/v1_15_R1/pom.xml index 543ad18a..9309a84e 100644 --- a/v1_15_R1/pom.xml +++ b/v1_15_R1/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.3 + 0.20.0-SNAPSHOT true diff --git a/v1_16_R1/pom.xml b/v1_16_R1/pom.xml index e23ee759..4541489a 100644 --- a/v1_16_R1/pom.xml +++ b/v1_16_R1/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.3 + 0.20.0-SNAPSHOT true diff --git a/v1_16_R2/pom.xml b/v1_16_R2/pom.xml index 5005ce85..88a0ccd0 100644 --- a/v1_16_R2/pom.xml +++ b/v1_16_R2/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.3 + 0.20.0-SNAPSHOT true diff --git a/v1_16_R3/pom.xml b/v1_16_R3/pom.xml index feb39508..bf8df9d8 100644 --- a/v1_16_R3/pom.xml +++ b/v1_16_R3/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.3 + 0.20.0-SNAPSHOT true diff --git a/v1_17_R1/pom.xml b/v1_17_R1/pom.xml index 0162d94b..a132b720 100644 --- a/v1_17_R1/pom.xml +++ b/v1_17_R1/pom.xml @@ -8,7 +8,7 @@ fr.skytasul beautyquests-parent - 0.19.3 + 0.20.0-SNAPSHOT diff --git a/v1_18_R1/pom.xml b/v1_18_R1/pom.xml index 16223b03..f9284ec9 100644 --- a/v1_18_R1/pom.xml +++ b/v1_18_R1/pom.xml @@ -8,7 +8,7 @@ fr.skytasul beautyquests-parent - 0.19.3 + 0.20.0-SNAPSHOT diff --git a/v1_18_R2/pom.xml b/v1_18_R2/pom.xml index b1723286..ab400092 100644 --- a/v1_18_R2/pom.xml +++ b/v1_18_R2/pom.xml @@ -8,7 +8,7 @@ fr.skytasul beautyquests-parent - 0.19.3 + 0.20.0-SNAPSHOT diff --git a/v1_9_R1/pom.xml b/v1_9_R1/pom.xml index 6559d14b..dff05d30 100644 --- a/v1_9_R1/pom.xml +++ b/v1_9_R1/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.3 + 0.20.0-SNAPSHOT true diff --git a/v1_9_R2/pom.xml b/v1_9_R2/pom.xml index 546ef8b6..21aedbaf 100644 --- a/v1_9_R2/pom.xml +++ b/v1_9_R2/pom.xml @@ -7,7 +7,7 @@ fr.skytasul beautyquests-parent - 0.19.3 + 0.20.0-SNAPSHOT true From 500131a8b1f54c15dbaf1eb473622a20c1273c91 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Wed, 11 May 2022 20:53:46 +0200 Subject: [PATCH 002/111] Refactored quest objects -> serializable --- .../fr/skytasul/quests/api/QuestsAPI.java | 5 + .../quests/api/objects/QuestObject.java | 78 +--------------- .../api/objects/QuestObjectClickEvent.java | 6 +- .../api/objects/QuestObjectCreator.java | 10 +- .../api/objects/QuestObjectsRegistry.java | 39 +------- .../api/serializable/SerializableCreator.java | 17 ++++ .../api/serializable/SerializableObject.java | 92 +++++++++++++++++++ .../serializable/SerializableRegistry.java | 45 +++++++++ .../quests/api/stages/AbstractStage.java | 4 +- .../quests/gui/creation/QuestObjectGUI.java | 5 - .../quests/gui/creation/stages/StagesGUI.java | 2 - .../worldguard/BQWorldGuard.java | 4 + 12 files changed, 177 insertions(+), 130 deletions(-) create mode 100644 core/src/main/java/fr/skytasul/quests/api/serializable/SerializableCreator.java create mode 100644 core/src/main/java/fr/skytasul/quests/api/serializable/SerializableObject.java create mode 100644 core/src/main/java/fr/skytasul/quests/api/serializable/SerializableRegistry.java diff --git a/core/src/main/java/fr/skytasul/quests/api/QuestsAPI.java b/core/src/main/java/fr/skytasul/quests/api/QuestsAPI.java index 5bb5579c..8b5f3536 100644 --- a/core/src/main/java/fr/skytasul/quests/api/QuestsAPI.java +++ b/core/src/main/java/fr/skytasul/quests/api/QuestsAPI.java @@ -53,6 +53,11 @@ public static void registerStage(StageType creator) DebugUtils.logMessage("Stage registered (" + creator.name + ", " + (stages.size() - 1) + ")"); } + public static StageType getStageType(Class stageClass){ + return (StageType) stages.stream().filter(type -> type.clazz == stageClass).findAny() + .orElseThrow(() -> new IllegalArgumentException(stageClass.getName() + "has not been registered as a stage type via the API.")); + } + /** * Register new mob factory * @param factory MobFactory instance diff --git a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java index 762a98a5..afbdee58 100644 --- a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java +++ b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java @@ -1,32 +1,24 @@ package fr.skytasul.quests.api.objects; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Function; - import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -import fr.skytasul.quests.BeautyQuests; +import fr.skytasul.quests.api.serializable.SerializableObject; import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.structure.Quest; import fr.skytasul.quests.utils.Lang; -public abstract class QuestObject implements Cloneable { - - private final QuestObjectCreator creator; +public abstract class QuestObject extends SerializableObject implements Cloneable { private Quest quest; protected QuestObject(QuestObjectsRegistry registry) { - this.creator = registry.getByClass(getClass()); - if (creator == null) throw new IllegalArgumentException(getClass().getName() + " has not been registered as a reward via the API."); + super(registry); } + @Override public QuestObjectCreator getCreator() { - return creator; + return (QuestObjectCreator) super.getCreator(); } public void attach(Quest quest) { @@ -41,10 +33,6 @@ public Quest getAttachedQuest() { return quest; } - public String getName() { - return getCreator().id; - } - public String debugName() { return getClass().getSimpleName() + (quest == null ? ", unknown quest" : (", quest " + quest.getID())); } @@ -52,10 +40,6 @@ public String debugName() { @Override public abstract QuestObject clone(); - protected abstract void save(Map datas); - - protected abstract void load(Map savedDatas); - public String getDescription(Player p) { // will maybe eventually be abstract (and therefore needs to be implemented) return null; } @@ -70,56 +54,4 @@ public ItemStack getItemStack() { public abstract void itemClick(QuestObjectClickEvent event); - public final Map serialize() { - Map map = new HashMap<>(); - - save(map); - map.put("id", creator.id); - - return map; - } - - public static List deserializeList(List> objectList, Function, T> deserializeFunction) { - List objects = new ArrayList<>(objectList.size()); - for (Map objectMap : objectList) { - try { - T object = deserializeFunction.apply((Map) objectMap); - if (object == null) { - BeautyQuests.loadingFailure = true; - BeautyQuests.getInstance().getLogger().severe("The quest object for class " + String.valueOf(objectMap.get("class")) + " has not been deserialized."); - }else objects.add(object); - }catch (Exception e) { - BeautyQuests.logger.severe("An exception occured while deserializing a quest object (class " + objectMap.get("class") + ").", e); - BeautyQuests.loadingFailure = true; - } - } - return objects; - } - - public static > T deserialize(Map map, QuestObjectsRegistry registry) { - QuestObjectCreator creator = null; - - String id = (String) map.get("id"); - if (id != null) creator = registry.getByID(id); - - if (creator == null && map.containsKey("class")) { - String className = (String) map.get("class"); - try { - creator = registry.getByClass(Class.forName(className)); - }catch (ClassNotFoundException e) {} - - if (creator == null) { - BeautyQuests.logger.severe("Cannot find object class " + className); - return null; - } - } - if (creator == null) { - BeautyQuests.logger.severe("Cannot find object creator with id: " + id); - return null; - } - T reward = creator.newObjectSupplier.get(); - reward.load(map); - return reward; - } - } \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectClickEvent.java b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectClickEvent.java index 4f88fa44..d572bb10 100644 --- a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectClickEvent.java +++ b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectClickEvent.java @@ -12,12 +12,12 @@ public class QuestObjectClickEvent { private final Player player; - private final QuestObjectGUI gui; + private final QuestObjectGUI gui; private final ItemStack item; private final ClickType click; private final boolean creation; - public QuestObjectClickEvent(Player player, QuestObjectGUI gui, ItemStack item, ClickType click, boolean creation) { + public QuestObjectClickEvent(Player player, QuestObjectGUI gui, ItemStack item, ClickType click, boolean creation) { this.player = player; this.gui = gui; this.item = item; @@ -29,7 +29,7 @@ public Player getPlayer() { return player; } - public QuestObjectGUI getGUI() { + public QuestObjectGUI getGUI() { return gui; } diff --git a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectCreator.java b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectCreator.java index fe632c69..9d900092 100644 --- a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectCreator.java +++ b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectCreator.java @@ -4,14 +4,12 @@ import org.bukkit.inventory.ItemStack; +import fr.skytasul.quests.api.serializable.SerializableCreator; import fr.skytasul.quests.gui.creation.QuestObjectGUI; -public class QuestObjectCreator { +public class QuestObjectCreator extends SerializableCreator { - public final String id; - public final Class clazz; public final ItemStack item; - public final Supplier newObjectSupplier; public final boolean multiple; private QuestObjectLocation[] allowedLocations; @@ -34,10 +32,8 @@ public QuestObjectCreator(String id, Class clazz, ItemStack item, S * @param allowedLocations if present, specifies where the object can be used. If no location specified, the */ public QuestObjectCreator(String id, Class clazz, ItemStack item, Supplier newObjectSupplier, boolean multiple, QuestObjectLocation... allowedLocations) { - this.id = id; - this.clazz = clazz; + super(id, clazz, newObjectSupplier); this.item = item; - this.newObjectSupplier = newObjectSupplier; this.multiple = multiple; this.allowedLocations = allowedLocations; } diff --git a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectsRegistry.java b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectsRegistry.java index 0889cda2..e64a5e5c 100644 --- a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectsRegistry.java +++ b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectsRegistry.java @@ -1,55 +1,20 @@ package fr.skytasul.quests.api.objects; -import java.util.ArrayList; import java.util.Collection; -import java.util.Iterator; import java.util.List; import java.util.function.Consumer; +import fr.skytasul.quests.api.serializable.SerializableRegistry; import fr.skytasul.quests.gui.creation.QuestObjectGUI; -import fr.skytasul.quests.utils.DebugUtils; -public class QuestObjectsRegistry> implements Iterable { +public class QuestObjectsRegistry> extends SerializableRegistry { - private final List creators = new ArrayList<>(); private final String inventoryName; public QuestObjectsRegistry(String inventoryName) { this.inventoryName = inventoryName; } - public void register(C creator) { - if (creators.stream().anyMatch(x -> x.id.equals(creator.id))) - throw new IllegalStateException("A creator with the same id " + creator.id + " has been registered."); - creators.add(creator); - DebugUtils.logMessage("Quest object registered (id: " + creator.id + ", class: " + creator.clazz.getName() + ")"); - } - - public C getByClass(Class clazz) { - return creators - .stream() - .filter(creator -> creator.clazz.equals(clazz)) - .findAny() - .orElse(null); - } - - public C getByID(String id) { - return creators - .stream() - .filter(creator -> creator.id.equals(id)) - .findAny() - .orElse(null); - } - - public List getCreators() { - return creators; - } - - @Override - public Iterator iterator() { - return creators.iterator(); - } - public QuestObjectGUI createGUI(QuestObjectLocation location, Consumer> end, List objects) { return createGUI(inventoryName, location, end, objects); } diff --git a/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableCreator.java b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableCreator.java new file mode 100644 index 00000000..76656d21 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableCreator.java @@ -0,0 +1,17 @@ +package fr.skytasul.quests.api.serializable; + +import java.util.function.Supplier; + +public class SerializableCreator { + + public final String id; + public final Class clazz; + public final Supplier newObjectSupplier; + + public SerializableCreator(String id, Class clazz, Supplier newObjectSupplier) { + this.id = id; + this.clazz = clazz; + this.newObjectSupplier = newObjectSupplier; + } + +} \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableObject.java b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableObject.java new file mode 100644 index 00000000..e3531b9a --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableObject.java @@ -0,0 +1,92 @@ +package fr.skytasul.quests.api.serializable; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +import fr.skytasul.quests.BeautyQuests; + +public abstract class SerializableObject { + + protected final SerializableCreator creator; + + protected SerializableObject(SerializableRegistry registry) { + this.creator = registry.getByClass(getClass()); + if (creator == null) throw new IllegalArgumentException(getClass().getName() + " has not been registered as an object."); + } + + protected SerializableObject(SerializableCreator creator) { + this.creator = creator; + if (creator == null) throw new IllegalArgumentException("Creator cannot be null."); + } + + public SerializableCreator getCreator() { + return creator; + } + + public String getName() { + return getCreator().id; + } + + @Override + public abstract SerializableObject clone(); + + protected abstract void save(Map datas); + + protected abstract void load(Map savedDatas); + + public final Map serialize() { + Map map = new HashMap<>(); + + save(map); + map.put("id", creator.id); + + return map; + } + + public static > T deserialize(Map map, SerializableRegistry registry) { + SerializableCreator creator = null; + + String id = (String) map.get("id"); + if (id != null) creator = registry.getByID(id); + + if (creator == null && map.containsKey("class")) { + String className = (String) map.get("class"); + try { + creator = registry.getByClass(Class.forName(className)); + }catch (ClassNotFoundException e) {} + + if (creator == null) { + BeautyQuests.logger.severe("Cannot find object class " + className); + return null; + } + } + if (creator == null) { + BeautyQuests.logger.severe("Cannot find object creator with id: " + id); + return null; + } + T reward = creator.newObjectSupplier.get(); + reward.load(map); + return reward; + } + + public static List deserializeList(List> objectList, Function, T> deserializeFunction) { + List objects = new ArrayList<>(objectList.size()); + for (Map objectMap : objectList) { + try { + T object = deserializeFunction.apply((Map) objectMap); + if (object == null) { + BeautyQuests.loadingFailure = true; + BeautyQuests.getInstance().getLogger().severe("The quest object for class " + String.valueOf(objectMap.get("class")) + " has not been deserialized."); + }else objects.add(object); + }catch (Exception e) { + BeautyQuests.logger.severe("An exception occured while deserializing a quest object (class " + objectMap.get("class") + ").", e); + BeautyQuests.loadingFailure = true; + } + } + return objects; + } + +} \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableRegistry.java b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableRegistry.java new file mode 100644 index 00000000..0fd11da7 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableRegistry.java @@ -0,0 +1,45 @@ +package fr.skytasul.quests.api.serializable; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import fr.skytasul.quests.utils.DebugUtils; + +public class SerializableRegistry> implements Iterable { + + protected final List creators = new ArrayList<>(); + + public void register(C creator) { + if (creators.stream().anyMatch(x -> x.id.equals(creator.id))) + throw new IllegalStateException("A creator with the same id " + creator.id + " has been registered."); + creators.add(creator); + DebugUtils.logMessage("Quest object registered (id: " + creator.id + ", class: " + creator.clazz.getName() + ")"); + } + + public SerializableCreator getByClass(Class clazz) { + return creators + .stream() + .filter(creator -> creator.clazz.equals(clazz)) + .findAny() + .orElse(null); + } + + public SerializableCreator getByID(String id) { + return creators + .stream() + .filter(creator -> creator.id.equals(id)) + .findAny() + .orElse(null); + } + + public List getCreators() { + return creators; + } + + @Override + public Iterator iterator() { + return creators.iterator(); + } + +} \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java index 1f43f75d..55179c25 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java @@ -43,9 +43,7 @@ public abstract class AbstractStage implements Listener{ protected AbstractStage(QuestBranch branch) { this.branch = branch; - - this.type = QuestsAPI.stages.stream().filter(type -> type.clazz == getClass()).findAny() - .orElseThrow(() -> new IllegalArgumentException(getClass().getName() + "has not been registered as a stage type via the API.")); + this.type = QuestsAPI.getStageType(getClass()); Bukkit.getPluginManager().registerEvents(this, BeautyQuests.getInstance()); } diff --git a/core/src/main/java/fr/skytasul/quests/gui/creation/QuestObjectGUI.java b/core/src/main/java/fr/skytasul/quests/gui/creation/QuestObjectGUI.java index bff7a2fc..23593c83 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/creation/QuestObjectGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/creation/QuestObjectGUI.java @@ -56,11 +56,6 @@ public ItemStack getObjectItemStack(QuestObject object) { return object.getItemStack(); } - @Override - public boolean remove(QuestObject object) { - return super.remove((T) object); - } - @Override protected void removed(T object) { if (!object.getCreator().multiple) creators.add(object.getCreator()); diff --git a/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java b/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java index a3532425..1f2d6c32 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java @@ -311,7 +311,6 @@ private void editBranch(QuestBranch branch){ private static final ItemStack stageNPC = ItemUtils.item(XMaterial.OAK_SIGN, Lang.stageNPC.toString()); private static final ItemStack stageItems = ItemUtils.item(XMaterial.CHEST, Lang.stageBring.toString()); - private static final ItemStack stageArea = ItemUtils.item(XMaterial.WOODEN_AXE, Lang.stageGoTo.toString()); private static final ItemStack stageMobs = ItemUtils.item(XMaterial.WOODEN_SWORD, Lang.stageMobs.toString()); private static final ItemStack stageMine = ItemUtils.item(XMaterial.WOODEN_PICKAXE, Lang.stageMine.toString()); private static final ItemStack stagePlace = ItemUtils.item(XMaterial.OAK_STAIRS, Lang.stagePlace.toString()); @@ -330,7 +329,6 @@ private void editBranch(QuestBranch branch){ public static void initialize(){ DebugUtils.logMessage("Initlializing default stage types."); - QuestsAPI.registerStage(new StageType("REGION", StageArea.class, Lang.Find.name(), StageArea::deserialize, stageArea, StageArea.Creator::new, "WorldGuard")); QuestsAPI.registerStage(new StageType<>("NPC", StageNPC.class, Lang.Talk.name(), StageNPC::deserialize, stageNPC, StageNPC.Creator::new)); QuestsAPI.registerStage(new StageType<>("ITEMS", StageBringBack.class, Lang.Items.name(), StageBringBack::deserialize, stageItems, StageBringBack.Creator::new)); QuestsAPI.registerStage(new StageType<>("MOBS", StageMobs.class, Lang.Mobs.name(), StageMobs::deserialize, stageMobs, StageMobs.Creator::new)); diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/worldguard/BQWorldGuard.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/worldguard/BQWorldGuard.java index 5ea3c299..96bd0afb 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/compatibility/worldguard/BQWorldGuard.java +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/worldguard/BQWorldGuard.java @@ -15,8 +15,10 @@ import fr.skytasul.quests.api.QuestsAPI; import fr.skytasul.quests.api.requirements.RequirementCreator; +import fr.skytasul.quests.api.stages.StageType; import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.requirements.RegionRequirement; +import fr.skytasul.quests.stages.StageArea; import fr.skytasul.quests.utils.DebugUtils; import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.XMaterial; @@ -118,6 +120,8 @@ public ProtectedRegion getRegion(String name, World w) { public static void init() { Validate.isTrue(instance == null, "BQ WorldGuard integration already initialized."); instance = new BQWorldGuard(); + + QuestsAPI.registerStage(new StageType<>("REGION", StageArea.class, Lang.Find.name(), StageArea::deserialize, ItemUtils.item(XMaterial.WOODEN_AXE, Lang.stageGoTo.toString()), StageArea.Creator::new)); QuestsAPI.getRequirements().register(new RequirementCreator("regionRequired", RegionRequirement.class, ItemUtils.item(XMaterial.WOODEN_AXE, Lang.RRegion.toString()), RegionRequirement::new)); } From e1c2b7728333e529e9df22e6c73a2303666299a0 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Thu, 12 May 2022 21:41:21 +0200 Subject: [PATCH 003/111] Serializable with ConfigurationSection in quest objects --- core/pom.xml | 2 +- .../api/comparison/ItemComparisonMap.java | 7 +++ .../quests/api/options/QuestOptionObject.java | 7 +-- .../api/options/QuestOptionRewards.java | 6 --- .../requirements/TargetNumberRequirement.java | 15 +++--- .../api/serializable/SerializableObject.java | 54 ++++++++++++++----- .../quests/api/stages/AbstractStage.java | 5 +- .../skytasul/quests/api/stages/StageType.java | 18 +++++++ .../quests/options/OptionRequirements.java | 6 --- .../quests/requirements/ClassRequirement.java | 22 +++----- .../requirements/FactionRequirement.java | 18 +++---- .../requirements/JobLevelRequirement.java | 19 +++---- .../quests/requirements/LevelRequirement.java | 8 --- .../McCombatLevelRequirement.java | 16 ------ .../requirements/McMMOSkillRequirement.java | 19 +++---- .../quests/requirements/MoneyRequirement.java | 20 +++---- .../requirements/PermissionsRequirement.java | 14 ++--- .../requirements/PlaceholderRequirement.java | 25 ++++----- .../quests/requirements/QuestRequirement.java | 11 ++-- .../requirements/RegionRequirement.java | 19 +++---- .../requirements/ScoreboardRequirement.java | 15 +++--- .../SkillAPILevelRequirement.java | 8 --- .../logical/LogicalOrRequirement.java | 13 +++-- .../quests/rewards/CheckpointReward.java | 12 ++--- .../quests/rewards/CommandReward.java | 10 ++-- .../skytasul/quests/rewards/ItemReward.java | 10 ++-- .../quests/rewards/MessageReward.java | 10 ++-- .../skytasul/quests/rewards/MoneyReward.java | 17 +++--- .../quests/rewards/PermissionReward.java | 13 ++--- .../quests/rewards/QuestStopReward.java | 7 --- .../skytasul/quests/rewards/RandomReward.java | 19 ++++--- .../quests/rewards/RemoveItemsReward.java | 14 ++--- .../rewards/RequirementDependentReward.java | 16 +++--- .../quests/rewards/TeleportationReward.java | 10 ++-- .../skytasul/quests/rewards/TitleReward.java | 10 ++-- .../skytasul/quests/rewards/WaitReward.java | 10 ++-- .../fr/skytasul/quests/rewards/XPReward.java | 10 ++-- .../quests/structure/pools/QuestPool.java | 6 +-- .../java/fr/skytasul/quests/utils/Utils.java | 6 +-- .../fr/skytasul/quests/utils/types/Title.java | 32 +++++------ pom.xml | 2 +- 41 files changed, 252 insertions(+), 309 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index 382a2ab3..dcd73ae9 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -75,7 +75,7 @@ codemc-repo - https://repo.codemc.org/repository/maven-public/ + https://repo.codemc.io/repository/maven-public/ mineacademy-repo diff --git a/core/src/main/java/fr/skytasul/quests/api/comparison/ItemComparisonMap.java b/core/src/main/java/fr/skytasul/quests/api/comparison/ItemComparisonMap.java index 0796c083..f408ac1d 100644 --- a/core/src/main/java/fr/skytasul/quests/api/comparison/ItemComparisonMap.java +++ b/core/src/main/java/fr/skytasul/quests/api/comparison/ItemComparisonMap.java @@ -6,6 +6,7 @@ import java.util.Map; import org.bukkit.Material; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; @@ -24,6 +25,12 @@ public ItemComparisonMap(Map notDefault) { setNotDefaultComparisons(notDefault); } + public void setNotDefaultComparisons(ConfigurationSection section) { + Map map = new HashMap<>(); + section.getKeys(false).forEach(key -> notDefault.put(key, section.getBoolean(key))); + setNotDefaultComparisons(map); + } + public void setNotDefaultComparisons(Map comparisons) { this.notDefault = comparisons; diff --git a/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionObject.java b/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionObject.java index eca9c232..91174d71 100644 --- a/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionObject.java +++ b/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionObject.java @@ -3,7 +3,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.function.Function; import org.apache.commons.lang.Validate; import org.bukkit.configuration.ConfigurationSection; @@ -15,10 +14,10 @@ import fr.skytasul.quests.api.objects.QuestObjectCreator; import fr.skytasul.quests.api.objects.QuestObjectLocation; import fr.skytasul.quests.api.objects.QuestObjectsRegistry; +import fr.skytasul.quests.api.serializable.SerializableObject; import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.gui.creation.FinishGUI; import fr.skytasul.quests.structure.Quest; -import fr.skytasul.quests.utils.Utils; import fr.skytasul.quests.utils.XMaterial; public abstract class QuestOptionObject> extends QuestOption> { @@ -57,7 +56,7 @@ protected void attachObject(T object) { @Override public Object save() { - return Utils.serializeList(getValue(), getSerializeFunction()); + return SerializableObject.serializeList(getValue()); } @Override @@ -70,8 +69,6 @@ public List cloneValue(List value) { return new ArrayList<>(value); } - protected abstract Function> getSerializeFunction(); - protected abstract T deserialize(Map map); protected abstract String getSizeString(int size); diff --git a/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionRewards.java b/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionRewards.java index 6d16f80e..33c6db02 100644 --- a/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionRewards.java +++ b/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionRewards.java @@ -1,7 +1,6 @@ package fr.skytasul.quests.api.options; import java.util.Map; -import java.util.function.Function; import fr.skytasul.quests.api.QuestsAPI; import fr.skytasul.quests.api.objects.QuestObjectsRegistry; @@ -18,11 +17,6 @@ protected void attachObject(AbstractReward object) { } protected abstract void attachedAsyncReward(AbstractReward reward); - - @Override - protected Function> getSerializeFunction() { - return AbstractReward::serialize; - } @Override protected AbstractReward deserialize(Map map) { diff --git a/core/src/main/java/fr/skytasul/quests/api/requirements/TargetNumberRequirement.java b/core/src/main/java/fr/skytasul/quests/api/requirements/TargetNumberRequirement.java index c5277267..7264800d 100644 --- a/core/src/main/java/fr/skytasul/quests/api/requirements/TargetNumberRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/api/requirements/TargetNumberRequirement.java @@ -1,8 +1,8 @@ package fr.skytasul.quests.api.requirements; import java.text.NumberFormat; -import java.util.Map; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import fr.skytasul.quests.api.objects.QuestObjectClickEvent; @@ -10,7 +10,6 @@ import fr.skytasul.quests.editors.checkers.NumberParser; import fr.skytasul.quests.utils.ComparisonMethod; import fr.skytasul.quests.utils.Lang; -import fr.skytasul.quests.utils.Utils; public abstract class TargetNumberRequirement extends AbstractRequirement { @@ -58,15 +57,15 @@ public String[] getLore() { public abstract void sendHelpString(Player p); @Override - protected void save(Map datas) { - datas.put("comparison", comparison.name()); - datas.put("target", target); + protected void save(ConfigurationSection section) { + section.set("comparison", comparison.name()); + section.set("target", target); } @Override - protected void load(Map savedDatas) { - if (savedDatas.containsKey("comparison")) comparison = ComparisonMethod.valueOf((String) savedDatas.get("comparison")); - target = Utils.parseDouble(savedDatas.get("target")); + protected void load(ConfigurationSection section) { + if (section.contains("comparison")) comparison = ComparisonMethod.valueOf(section.getString("comparison")); + target = section.getDouble("target"); } @Override diff --git a/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableObject.java b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableObject.java index e3531b9a..948ecfc8 100644 --- a/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableObject.java +++ b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableObject.java @@ -5,6 +5,10 @@ import java.util.List; import java.util.Map; import java.util.function.Function; +import java.util.stream.Collectors; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.MemoryConfiguration; import fr.skytasul.quests.BeautyQuests; @@ -33,27 +37,41 @@ public String getName() { @Override public abstract SerializableObject clone(); - protected abstract void save(Map datas); - - protected abstract void load(Map savedDatas); + @Deprecated + protected void save(Map datas) {} - public final Map serialize() { - Map map = new HashMap<>(); - - save(map); - map.put("id", creator.id); - - return map; + @Deprecated + protected void load(Map savedDatas) {} + + protected void save(ConfigurationSection section) { + Map datas = new HashMap<>(); + save(datas); + datas.forEach(section::set); + } + + protected void load(ConfigurationSection section) { + load(section.getValues(false)); + } + + public final void serialize(ConfigurationSection section) { + section.set("id", creator.id); + save(section); } public static > T deserialize(Map map, SerializableRegistry registry) { + MemoryConfiguration section = new MemoryConfiguration(); + map.forEach(section::set); + return deserialize(section, registry); + } + + public static > T deserialize(ConfigurationSection section, SerializableRegistry registry) { SerializableCreator creator = null; - String id = (String) map.get("id"); + String id = section.getString("id"); if (id != null) creator = registry.getByID(id); - if (creator == null && map.containsKey("class")) { - String className = (String) map.get("class"); + if (creator == null && section.contains("class")) { + String className = section.getString("class"); try { creator = registry.getByClass(Class.forName(className)); }catch (ClassNotFoundException e) {} @@ -68,7 +86,7 @@ public static > T return null; } T reward = creator.newObjectSupplier.get(); - reward.load(map); + reward.load(section); return reward; } @@ -89,4 +107,12 @@ public static List deserializeList(List> serializeList(List objects) { + return objects.stream().map(object -> { + MemoryConfiguration section = new MemoryConfiguration(); + object.serialize(section); + return section.getValues(false); + }).collect(Collectors.toList()); + } + } \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java index 55179c25..e749748d 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java @@ -21,6 +21,7 @@ import fr.skytasul.quests.api.objects.QuestObject; import fr.skytasul.quests.api.requirements.AbstractRequirement; import fr.skytasul.quests.api.rewards.AbstractReward; +import fr.skytasul.quests.api.serializable.SerializableObject; import fr.skytasul.quests.players.PlayerAccount; import fr.skytasul.quests.players.PlayersManager; import fr.skytasul.quests.players.events.PlayerAccountJoinEvent; @@ -286,8 +287,8 @@ public final void save(ConfigurationSection section) { section.set("customText", customText); if (startMessage != null) section.set("text", startMessage); - if (!rewards.isEmpty()) section.set("rewards", Utils.serializeList(rewards, AbstractReward::serialize)); - if (!validationRequirements.isEmpty()) section.set("requirements", Utils.serializeList(validationRequirements, AbstractRequirement::serialize)); + if (!rewards.isEmpty()) section.set("rewards", SerializableObject.serializeList(rewards)); + if (!validationRequirements.isEmpty()) section.set("requirements", SerializableObject.serializeList(validationRequirements)); } public static AbstractStage deserialize(ConfigurationSection section, QuestBranch branch) { diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/StageType.java b/core/src/main/java/fr/skytasul/quests/api/stages/StageType.java index b9e338f4..2c70de41 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/StageType.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/StageType.java @@ -6,6 +6,10 @@ import org.bukkit.configuration.ConfigurationSection; import org.bukkit.inventory.ItemStack; +import fr.skytasul.quests.BeautyQuests; +import fr.skytasul.quests.api.serializable.SerializableCreator; +import fr.skytasul.quests.api.serializable.SerializableRegistry; +import fr.skytasul.quests.api.stages.options.StageOption; import fr.skytasul.quests.gui.creation.stages.Line; import fr.skytasul.quests.structure.QuestBranch; @@ -19,10 +23,18 @@ public class StageType { public final StageCreationSupplier creationSupplier; public final String[] dependencies; + private final SerializableRegistry, SerializableCreator>> optionsRegistry = new SerializableRegistry<>(); + + public StageType(String id, Class clazz, String name, StageLoader loader, ItemStack item, StageCreationSupplier creationSupplier) { + this(id, clazz, name, loader, item, creationSupplier, new String[0]); + } + + @Deprecated public StageType(String id, Class clazz, String name, StageDeserializationSupplier deserializationSupplier, ItemStack item, StageCreationSupplier creationSupplier, String... dependencies) { this(id, clazz, name, (StageLoader) deserializationSupplier, item, creationSupplier, dependencies); } + @Deprecated public StageType(String id, Class clazz, String name, StageLoader loader, ItemStack item, StageCreationSupplier creationSupplier, String... dependencies) { this.id = id; this.clazz = clazz; @@ -31,8 +43,14 @@ public StageType(String id, Class clazz, String name, StageLoader loader, this.loader = loader; this.creationSupplier = creationSupplier; this.dependencies = dependencies; + if (dependencies.length != 0) BeautyQuests.logger.warning("Nag author of the " + id + " stage type about its use of the deprecated \"dependencies\" feature."); + } + + public SerializableRegistry, SerializableCreator>> getOptionsRegistry() { + return optionsRegistry; } + @Deprecated public boolean isValid() { for (String depend : dependencies) { if (!Bukkit.getPluginManager().isPluginEnabled(depend)) return false; diff --git a/core/src/main/java/fr/skytasul/quests/options/OptionRequirements.java b/core/src/main/java/fr/skytasul/quests/options/OptionRequirements.java index df538d01..a91bec3d 100644 --- a/core/src/main/java/fr/skytasul/quests/options/OptionRequirements.java +++ b/core/src/main/java/fr/skytasul/quests/options/OptionRequirements.java @@ -1,7 +1,6 @@ package fr.skytasul.quests.options; import java.util.Map; -import java.util.function.Function; import fr.skytasul.quests.api.QuestsAPI; import fr.skytasul.quests.api.objects.QuestObjectsRegistry; @@ -13,11 +12,6 @@ public class OptionRequirements extends QuestOptionObject { - @Override - protected Function> getSerializeFunction() { - return AbstractRequirement::serialize; - } - @Override protected AbstractRequirement deserialize(Map map) { return AbstractRequirement.deserialize(map); diff --git a/core/src/main/java/fr/skytasul/quests/requirements/ClassRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/ClassRequirement.java index e3a4c4ec..86547926 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/ClassRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/ClassRequirement.java @@ -2,10 +2,11 @@ import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.function.Function; +import java.util.stream.Collectors; import org.bukkit.DyeColor; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.bukkit.event.inventory.ClickType; import org.bukkit.inventory.ItemStack; @@ -20,8 +21,6 @@ import fr.skytasul.quests.gui.templates.ListGUI; import fr.skytasul.quests.gui.templates.PagedGUI; import fr.skytasul.quests.utils.Lang; -import fr.skytasul.quests.utils.compatibility.DependenciesManager; -import fr.skytasul.quests.utils.compatibility.MissingDependencyException; import fr.skytasul.quests.utils.compatibility.SkillAPI; public class ClassRequirement extends AbstractRequirement { @@ -33,7 +32,6 @@ public ClassRequirement() { } public ClassRequirement(List classes) { - if (!DependenciesManager.skapi.isEnabled()) throw new MissingDependencyException("SkillAPI"); this.classes = classes; } @@ -108,19 +106,15 @@ public AbstractRequirement clone() { } @Override - protected void save(Map datas) { - if (classes.isEmpty()) return; - List ls = new ArrayList<>(); - for (RPGClass cl : classes) { - ls.add(cl.getName()); - } - datas.put("classes", ls); + protected void save(ConfigurationSection section) { + if (!classes.isEmpty()) + section.set("classes", classes.stream().map(RPGClass::getName).collect(Collectors.toList())); } @Override - protected void load(Map savedDatas) { - if (!savedDatas.containsKey("classes")) return; - for (String s : (List) savedDatas.get("classes")) { + protected void load(ConfigurationSection section) { + if (!section.contains("classes")) return; + for (String s : section.getStringList("classes")) { RPGClass classe = com.sucy.skill.SkillAPI.getClasses().get(s.toLowerCase()); if (classe == null) { BeautyQuests.getInstance().getLogger().warning("Class with name " + s + " no longer exists."); diff --git a/core/src/main/java/fr/skytasul/quests/requirements/FactionRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/FactionRequirement.java index f28fed67..70ef5e8d 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/FactionRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/FactionRequirement.java @@ -2,10 +2,11 @@ import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.function.Function; +import java.util.stream.Collectors; import org.bukkit.DyeColor; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.bukkit.event.inventory.ClickType; import org.bukkit.inventory.ItemStack; @@ -23,9 +24,7 @@ import fr.skytasul.quests.gui.templates.PagedGUI; import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.XMaterial; -import fr.skytasul.quests.utils.compatibility.DependenciesManager; import fr.skytasul.quests.utils.compatibility.Factions; -import fr.skytasul.quests.utils.compatibility.MissingDependencyException; public class FactionRequirement extends AbstractRequirement { @@ -36,7 +35,6 @@ public FactionRequirement() { } public FactionRequirement(List factions) { - if (!DependenciesManager.fac.isEnabled()) throw new MissingDependencyException("Factions"); this.factions = factions; } @@ -104,17 +102,13 @@ public AbstractRequirement clone() { } @Override - protected void save(Map datas) { - List ls = new ArrayList<>(); - for (Faction fac : factions) { - ls.add(fac.getId()); - } - datas.put("factions", factions); + protected void save(ConfigurationSection section) { + section.set("factions", factions.stream().map(Faction::getId).collect(Collectors.toList())); } @Override - protected void load(Map savedDatas) { - for (String s : (List) savedDatas.get("factions")) { + protected void load(ConfigurationSection section) { + for (String s : section.getStringList("factions")) { if (!FactionColl.get().containsId(s)) { BeautyQuests.getInstance().getLogger().warning("Faction with ID " + s + " no longer exists."); continue; diff --git a/core/src/main/java/fr/skytasul/quests/requirements/JobLevelRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/JobLevelRequirement.java index d439b03c..e8311036 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/JobLevelRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/JobLevelRequirement.java @@ -1,7 +1,6 @@ package fr.skytasul.quests.requirements; -import java.util.Map; - +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import fr.skytasul.quests.api.objects.QuestObjectClickEvent; @@ -10,9 +9,7 @@ import fr.skytasul.quests.editors.TextEditor; import fr.skytasul.quests.utils.ComparisonMethod; import fr.skytasul.quests.utils.Lang; -import fr.skytasul.quests.utils.compatibility.DependenciesManager; import fr.skytasul.quests.utils.compatibility.Jobs; -import fr.skytasul.quests.utils.compatibility.MissingDependencyException; public class JobLevelRequirement extends TargetNumberRequirement { @@ -24,7 +21,6 @@ public JobLevelRequirement() { public JobLevelRequirement(String jobName, double target, ComparisonMethod comparison) { super(target, comparison); - if (!DependenciesManager.jobs.isEnabled()) throw new MissingDependencyException("Jobs"); this.jobName = jobName; } @@ -77,16 +73,15 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(Map datas) { - super.save(datas); - datas.put("jobName", jobName); + protected void save(ConfigurationSection section) { + super.save(section); + section.set("jobName", jobName); } @Override - protected void load(Map savedDatas) { - super.load(savedDatas); - jobName = (String) savedDatas.get("jobName"); - if (savedDatas.containsKey("level")) super.target = (int) savedDatas.get("level"); + protected void load(ConfigurationSection section) { + super.load(section); + jobName = section.getString("jobName"); } } \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/requirements/LevelRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/LevelRequirement.java index ad2b67af..13198c6a 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/LevelRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/LevelRequirement.java @@ -1,7 +1,5 @@ package fr.skytasul.quests.requirements; -import java.util.Map; - import org.bukkit.entity.Player; import fr.skytasul.quests.api.requirements.AbstractRequirement; @@ -48,11 +46,5 @@ public String getDescription(Player p) { public AbstractRequirement clone() { return new LevelRequirement(target, comparison); } - - @Override - protected void load(Map savedDatas) { - super.load(savedDatas); - if (savedDatas.containsKey("level")) super.target = (int) savedDatas.get("level"); - } } diff --git a/core/src/main/java/fr/skytasul/quests/requirements/McCombatLevelRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/McCombatLevelRequirement.java index 28f4fa1b..61ba636f 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/McCombatLevelRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/McCombatLevelRequirement.java @@ -1,16 +1,12 @@ package fr.skytasul.quests.requirements; -import java.util.Map; - import org.bukkit.entity.Player; import fr.skytasul.quests.api.requirements.AbstractRequirement; import fr.skytasul.quests.api.requirements.TargetNumberRequirement; import fr.skytasul.quests.utils.ComparisonMethod; import fr.skytasul.quests.utils.Lang; -import fr.skytasul.quests.utils.compatibility.DependenciesManager; import fr.skytasul.quests.utils.compatibility.McCombatLevel; -import fr.skytasul.quests.utils.compatibility.MissingDependencyException; public class McCombatLevelRequirement extends TargetNumberRequirement { @@ -20,7 +16,6 @@ public McCombatLevelRequirement(){ public McCombatLevelRequirement(double target, ComparisonMethod comparison) { super(target, comparison); - if (!DependenciesManager.mmo.isEnabled()) throw new MissingDependencyException("McCombatLevel"); } @Override @@ -52,16 +47,5 @@ public void sendHelpString(Player p) { public AbstractRequirement clone() { return new McCombatLevelRequirement(target, comparison); } - - @Override - protected void save(Map datas) { - super.save(datas); - } - - @Override - protected void load(Map savedDatas) { - super.load(savedDatas); - if (savedDatas.containsKey("level")) super.target = (int) savedDatas.get("level"); - } } diff --git a/core/src/main/java/fr/skytasul/quests/requirements/McMMOSkillRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/McMMOSkillRequirement.java index e1972186..bd35b216 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/McMMOSkillRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/McMMOSkillRequirement.java @@ -1,7 +1,6 @@ package fr.skytasul.quests.requirements; -import java.util.Map; - +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import fr.skytasul.quests.api.objects.QuestObjectClickEvent; @@ -10,9 +9,7 @@ import fr.skytasul.quests.editors.TextEditor; import fr.skytasul.quests.utils.ComparisonMethod; import fr.skytasul.quests.utils.Lang; -import fr.skytasul.quests.utils.compatibility.DependenciesManager; import fr.skytasul.quests.utils.compatibility.McMMO; -import fr.skytasul.quests.utils.compatibility.MissingDependencyException; public class McMMOSkillRequirement extends TargetNumberRequirement { @@ -24,7 +21,6 @@ public McMMOSkillRequirement(){ public McMMOSkillRequirement(double target, ComparisonMethod comparison) { super(target, comparison); - if (!DependenciesManager.mmo.isEnabled()) throw new MissingDependencyException("mcMMO"); } @Override @@ -70,16 +66,15 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(Map datas) { - super.save(datas); - datas.put("skillName", skillName); + protected void save(ConfigurationSection section) { + super.save(section); + section.set("skillName", skillName); } @Override - protected void load(Map savedDatas) { - super.load(savedDatas); - skillName = (String) savedDatas.get("skillName"); - if (savedDatas.containsKey("level")) super.target = (int) savedDatas.get("level"); + protected void load(ConfigurationSection section) { + super.load(section); + skillName = section.getString("skillName"); } @Override diff --git a/core/src/main/java/fr/skytasul/quests/requirements/MoneyRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/MoneyRequirement.java index 790fe2c7..2c57d053 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/MoneyRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/MoneyRequirement.java @@ -1,7 +1,6 @@ package fr.skytasul.quests.requirements; -import java.util.Map; - +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import fr.skytasul.quests.api.objects.QuestObjectClickEvent; @@ -10,20 +9,15 @@ import fr.skytasul.quests.editors.TextEditor; import fr.skytasul.quests.editors.checkers.NumberParser; import fr.skytasul.quests.utils.Lang; -import fr.skytasul.quests.utils.compatibility.DependenciesManager; -import fr.skytasul.quests.utils.compatibility.MissingDependencyException; import fr.skytasul.quests.utils.compatibility.Vault; public class MoneyRequirement extends AbstractRequirement implements Actionnable { public double money = 0; - - public MoneyRequirement() { - if (!DependenciesManager.vault.isEnabled()) throw new MissingDependencyException("Vault"); - } + + public MoneyRequirement() {} public MoneyRequirement(double money) { - this(); this.money = money; } @@ -71,13 +65,13 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(Map datas) { - datas.put("money", money); + protected void save(ConfigurationSection section) { + section.set("money", money); } @Override - protected void load(Map savedDatas) { - money = (double) savedDatas.get("money"); + protected void load(ConfigurationSection section) { + money = section.getDouble("money"); } } diff --git a/core/src/main/java/fr/skytasul/quests/requirements/PermissionsRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/PermissionsRequirement.java index dfd14682..4ee19bb7 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/PermissionsRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/PermissionsRequirement.java @@ -2,11 +2,11 @@ import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; import org.bukkit.DyeColor; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -88,15 +88,15 @@ public AbstractRequirement clone() { } @Override - protected void save(Map datas) { - datas.put("permissions", permissions.stream().map(Permission::toString).collect(Collectors.toList())); - if (message != null) datas.put("message", message); + protected void save(ConfigurationSection section) { + section.set("permissions", permissions.stream().map(Permission::toString).collect(Collectors.toList())); + if (message != null) section.set("message", message); } @Override - protected void load(Map savedDatas) { - permissions = ((List) savedDatas.get("permissions")).stream().map(Permission::fromString).collect(Collectors.toList()); - if (savedDatas.containsKey("message")) message = (String) savedDatas.get("message"); + protected void load(ConfigurationSection section) { + permissions = section.getStringList("permissions").stream().map(Permission::fromString).collect(Collectors.toList()); + if (section.contains("message")) message = section.getString("message"); } public static class Permission { diff --git a/core/src/main/java/fr/skytasul/quests/requirements/PlaceholderRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/PlaceholderRequirement.java index 5d40f131..d51620fd 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/PlaceholderRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/PlaceholderRequirement.java @@ -1,8 +1,8 @@ package fr.skytasul.quests.requirements; import java.math.BigDecimal; -import java.util.Map; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import fr.skytasul.quests.BeautyQuests; @@ -13,8 +13,6 @@ import fr.skytasul.quests.utils.DebugUtils; import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.Utils; -import fr.skytasul.quests.utils.compatibility.DependenciesManager; -import fr.skytasul.quests.utils.compatibility.MissingDependencyException; import fr.skytasul.quests.utils.compatibility.QuestsPlaceholders; import me.clip.placeholderapi.PlaceholderAPIPlugin; @@ -36,7 +34,6 @@ public PlaceholderRequirement(){ } public PlaceholderRequirement(String placeholder, String value, ComparisonMethod comparison) { - if (!DependenciesManager.papi.isEnabled()) throw new MissingDependencyException("PlaceholderAPI"); if (placeholder != null) setPlaceholder(placeholder); this.value = value; this.comparison = comparison; @@ -104,19 +101,19 @@ public String getValue(){ } @Override - protected void save(Map datas){ - datas.put("placeholder", rawPlaceholder); - datas.put("value", value); - datas.put("comparison", comparison.name()); - datas.put("parseValue", parseValue); + protected void save(ConfigurationSection section) { + section.set("placeholder", rawPlaceholder); + section.set("value", value); + section.set("comparison", comparison.name()); + section.set("parseValue", parseValue); } @Override - protected void load(Map savedDatas){ - setPlaceholder((String) savedDatas.get("placeholder")); - this.value = (String) savedDatas.get("value"); - if (savedDatas.containsKey("comparison")) this.comparison = ComparisonMethod.valueOf((String) savedDatas.get("comparison")); - if (savedDatas.containsKey("parseValue")) this.parseValue = (boolean) savedDatas.get("parseValue"); + protected void load(ConfigurationSection section){ + setPlaceholder(section.getString("placeholder")); + this.value = section.getString("value"); + if (section.contains("comparison")) this.comparison = ComparisonMethod.valueOf(section.getString("comparison")); + if (section.contains("parseValue")) this.parseValue = section.getBoolean("parseValue"); } @Override diff --git a/core/src/main/java/fr/skytasul/quests/requirements/QuestRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/QuestRequirement.java index 7ecc3305..69db15e1 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/QuestRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/QuestRequirement.java @@ -1,7 +1,6 @@ package fr.skytasul.quests.requirements; -import java.util.Map; - +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import fr.skytasul.quests.api.QuestsAPI; @@ -82,13 +81,13 @@ public AbstractRequirement clone() { } @Override - protected void save(Map datas) { - datas.put("questID", questId); + protected void save(ConfigurationSection section) { + section.set("questID", questId); } @Override - protected void load(Map savedDatas) { - questId = (int) savedDatas.get("questID"); + protected void load(ConfigurationSection section) { + questId = section.getInt("questID"); } } diff --git a/core/src/main/java/fr/skytasul/quests/requirements/RegionRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/RegionRequirement.java index d1cae08c..4260016d 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/RegionRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/RegionRequirement.java @@ -1,8 +1,7 @@ package fr.skytasul.quests.requirements; -import java.util.Map; - import org.bukkit.Bukkit; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import com.sk89q.worldguard.protection.regions.ProtectedRegion; @@ -13,8 +12,6 @@ import fr.skytasul.quests.editors.TextEditor; import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.Utils; -import fr.skytasul.quests.utils.compatibility.DependenciesManager; -import fr.skytasul.quests.utils.compatibility.MissingDependencyException; import fr.skytasul.quests.utils.compatibility.worldguard.BQWorldGuard; public class RegionRequirement extends AbstractRequirement { @@ -28,8 +25,6 @@ public RegionRequirement() { } public RegionRequirement(String worldName, String regionName) { - if (!DependenciesManager.wg.isEnabled()) throw new MissingDependencyException("WorldGuard"); - this.worldName = worldName; setRegionName(regionName); } @@ -86,15 +81,15 @@ public AbstractRequirement clone() { } @Override - protected void save(Map datas) { - datas.put("world", worldName); - datas.put("region", regionName); + protected void save(ConfigurationSection section) { + section.set("world", worldName); + section.set("region", regionName); } @Override - protected void load(Map savedDatas) { - worldName = (String) savedDatas.get("world"); - setRegionName((String) savedDatas.get("region")); + protected void load(ConfigurationSection section) { + worldName = section.getString("world"); + setRegionName(section.getString("region")); } } diff --git a/core/src/main/java/fr/skytasul/quests/requirements/ScoreboardRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/ScoreboardRequirement.java index 207d1d5f..6c91f6ff 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/ScoreboardRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/ScoreboardRequirement.java @@ -1,8 +1,7 @@ package fr.skytasul.quests.requirements; -import java.util.Map; - import org.bukkit.Bukkit; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.bukkit.scoreboard.Objective; @@ -70,15 +69,15 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(Map datas) { - super.save(datas); - datas.put("objective", objectiveName); + protected void save(ConfigurationSection section) { + super.save(section); + section.set("objective", objectiveName); } @Override - protected void load(Map savedDatas) { - super.load(savedDatas); - setObjectiveName((String) savedDatas.get("objective")); + protected void load(ConfigurationSection section) { + super.load(section); + setObjectiveName(section.getString("objective")); } @Override diff --git a/core/src/main/java/fr/skytasul/quests/requirements/SkillAPILevelRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/SkillAPILevelRequirement.java index 53ed6036..4f7a821c 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/SkillAPILevelRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/SkillAPILevelRequirement.java @@ -1,7 +1,5 @@ package fr.skytasul.quests.requirements; -import java.util.Map; - import org.bukkit.entity.Player; import fr.skytasul.quests.api.requirements.AbstractRequirement; @@ -49,11 +47,5 @@ public String getDescription(Player p) { public AbstractRequirement clone() { return new SkillAPILevelRequirement(target, comparison); } - - @Override - protected void load(Map savedDatas) { - super.load(savedDatas); - if (savedDatas.containsKey("level")) super.target = (int) savedDatas.get("level"); - } } diff --git a/core/src/main/java/fr/skytasul/quests/requirements/logical/LogicalOrRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/logical/LogicalOrRequirement.java index babee3a7..dcd47ba3 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/logical/LogicalOrRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/logical/LogicalOrRequirement.java @@ -2,18 +2,17 @@ import java.util.ArrayList; import java.util.List; -import java.util.Map; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import fr.skytasul.quests.api.QuestsAPI; -import fr.skytasul.quests.api.objects.QuestObject; import fr.skytasul.quests.api.objects.QuestObjectClickEvent; import fr.skytasul.quests.api.objects.QuestObjectLocation; import fr.skytasul.quests.api.requirements.AbstractRequirement; +import fr.skytasul.quests.api.serializable.SerializableObject; import fr.skytasul.quests.structure.Quest; import fr.skytasul.quests.utils.Lang; -import fr.skytasul.quests.utils.Utils; public class LogicalOrRequirement extends AbstractRequirement { @@ -64,13 +63,13 @@ public AbstractRequirement clone() { } @Override - protected void save(Map datas) { - datas.put("requirements", Utils.serializeList(requirements, AbstractRequirement::serialize)); + protected void save(ConfigurationSection section) { + section.set("requirements", SerializableObject.serializeList(requirements)); } @Override - protected void load(Map savedDatas) { - requirements = QuestObject.deserializeList((List>) savedDatas.get("requirements"), AbstractRequirement::deserialize); + protected void load(ConfigurationSection section) { + requirements = SerializableObject.deserializeList(section.getMapList("requirements"), AbstractRequirement::deserialize); } } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/CheckpointReward.java b/core/src/main/java/fr/skytasul/quests/rewards/CheckpointReward.java index 9d2007f2..76d11572 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/CheckpointReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/CheckpointReward.java @@ -2,16 +2,16 @@ import java.util.ArrayList; import java.util.List; -import java.util.Map; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import fr.skytasul.quests.api.QuestsAPI; -import fr.skytasul.quests.api.objects.QuestObject; import fr.skytasul.quests.api.objects.QuestObjectClickEvent; import fr.skytasul.quests.api.objects.QuestObjectLocation; import fr.skytasul.quests.api.options.QuestOption; import fr.skytasul.quests.api.rewards.AbstractReward; +import fr.skytasul.quests.api.serializable.SerializableObject; import fr.skytasul.quests.structure.Quest; import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.Utils; @@ -70,13 +70,13 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(Map datas) { - datas.put("actions", Utils.serializeList(actions, AbstractReward::serialize)); + protected void save(ConfigurationSection section) { + section.set("actions", SerializableObject.serializeList(actions)); } @Override - protected void load(Map savedDatas) { - actions = QuestObject.deserializeList((List>) savedDatas.get("actions"), AbstractReward::deserialize); + protected void load(ConfigurationSection section) { + actions = SerializableObject.deserializeList(section.getMapList("actions"), AbstractReward::deserialize); } } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/CommandReward.java b/core/src/main/java/fr/skytasul/quests/rewards/CommandReward.java index 218f647c..af8c35fa 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/CommandReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/CommandReward.java @@ -2,10 +2,10 @@ import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.function.Function; import org.bukkit.DyeColor; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.bukkit.event.inventory.ClickType; import org.bukkit.inventory.ItemStack; @@ -83,13 +83,13 @@ public void finish(List objects) { } @Override - protected void save(Map datas){ - datas.put("commands", Utils.serializeList(commands, Command::serialize)); + protected void save(ConfigurationSection section) { + section.set("commands", Utils.serializeList(commands, Command::serialize)); } @Override - protected void load(Map savedDatas){ - commands.addAll(Utils.deserializeList((List>) savedDatas.get("commands"), Command::deserialize)); + protected void load(ConfigurationSection section){ + commands.addAll(Utils.deserializeList(section.getMapList("commands"), Command::deserialize)); } } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/ItemReward.java b/core/src/main/java/fr/skytasul/quests/rewards/ItemReward.java index 8a8cf4ec..40bd1a0d 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/ItemReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/ItemReward.java @@ -3,8 +3,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -58,13 +58,13 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(Map datas){ - datas.put("items", Utils.serializeList(items, ItemStack::serialize)); + protected void save(ConfigurationSection section) { + section.set("items", Utils.serializeList(items, ItemStack::serialize)); } @Override - protected void load(Map savedDatas){ - items.addAll(Utils.deserializeList((List>) savedDatas.get("items"), ItemStack::deserialize)); + protected void load(ConfigurationSection section){ + items.addAll(Utils.deserializeList(section.getMapList("items"), ItemStack::deserialize)); } } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/MessageReward.java b/core/src/main/java/fr/skytasul/quests/rewards/MessageReward.java index 2be81e39..b35b336d 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/MessageReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/MessageReward.java @@ -1,8 +1,8 @@ package fr.skytasul.quests.rewards; import java.util.List; -import java.util.Map; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import fr.skytasul.quests.api.objects.QuestObjectClickEvent; @@ -51,13 +51,13 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(Map datas) { - datas.put("text", text); + protected void save(ConfigurationSection section) { + section.set("text", text); } @Override - protected void load(Map savedDatas) { - text = (String) savedDatas.get("text"); + protected void load(ConfigurationSection section) { + text = section.getString("text"); } } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/MoneyReward.java b/core/src/main/java/fr/skytasul/quests/rewards/MoneyReward.java index 5008ce4d..abc94649 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/MoneyReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/MoneyReward.java @@ -2,8 +2,8 @@ import java.util.Arrays; import java.util.List; -import java.util.Map; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import fr.skytasul.quests.api.objects.QuestObjectClickEvent; @@ -11,20 +11,15 @@ import fr.skytasul.quests.editors.TextEditor; import fr.skytasul.quests.editors.checkers.NumberParser; import fr.skytasul.quests.utils.Lang; -import fr.skytasul.quests.utils.compatibility.DependenciesManager; -import fr.skytasul.quests.utils.compatibility.MissingDependencyException; import fr.skytasul.quests.utils.compatibility.Vault; public class MoneyReward extends AbstractReward { public double money = 0; - public MoneyReward(){ - if (!DependenciesManager.vault.isEnabled()) throw new MissingDependencyException("Vault"); - } + public MoneyReward() {} public MoneyReward(double money) { - this(); this.money = money; } @@ -65,13 +60,13 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(Map datas) { - datas.put("money", money); + protected void save(ConfigurationSection section) { + section.set("money", money); } @Override - protected void load(Map savedDatas) { - money = (double) savedDatas.get("money"); + protected void load(ConfigurationSection section) { + money = section.getDouble("money"); } } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/PermissionReward.java b/core/src/main/java/fr/skytasul/quests/rewards/PermissionReward.java index d6497c0f..52601f7e 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/PermissionReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/PermissionReward.java @@ -2,8 +2,8 @@ import java.util.ArrayList; import java.util.List; -import java.util.Map; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import fr.skytasul.quests.api.objects.QuestObjectClickEvent; @@ -11,8 +11,6 @@ import fr.skytasul.quests.gui.permissions.PermissionListGUI; import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.Utils; -import fr.skytasul.quests.utils.compatibility.DependenciesManager; -import fr.skytasul.quests.utils.compatibility.MissingDependencyException; import fr.skytasul.quests.utils.types.Permission; public class PermissionReward extends AbstractReward { @@ -24,7 +22,6 @@ public PermissionReward(){ } public PermissionReward(List permissions) { - if (!DependenciesManager.vault.isEnabled()) throw new MissingDependencyException("Vault"); this.permissions = permissions; } @@ -56,13 +53,13 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(Map datas){ - datas.put("perms", Utils.serializeList(permissions, Permission::serialize)); + protected void save(ConfigurationSection section) { + section.set("perms", Utils.serializeList(permissions, Permission::serialize)); } @Override - protected void load(Map savedDatas){ - permissions.addAll(Utils.deserializeList((List>) savedDatas.get("perms"), Permission::deserialize)); + protected void load(ConfigurationSection section){ + permissions.addAll(Utils.deserializeList(section.getMapList("perms"), Permission::deserialize)); } } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/QuestStopReward.java b/core/src/main/java/fr/skytasul/quests/rewards/QuestStopReward.java index 1fa4fcf1..8e4def64 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/QuestStopReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/QuestStopReward.java @@ -1,7 +1,6 @@ package fr.skytasul.quests.rewards; import java.util.List; -import java.util.Map; import org.bukkit.entity.Player; @@ -29,10 +28,4 @@ public AbstractReward clone() { return this; } - @Override - protected void save(Map datas) {} - - @Override - protected void load(Map savedDatas) {} - } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/RandomReward.java b/core/src/main/java/fr/skytasul/quests/rewards/RandomReward.java index 4553e109..867ec87d 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/RandomReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/RandomReward.java @@ -2,24 +2,23 @@ import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import fr.skytasul.quests.BeautyQuests; import fr.skytasul.quests.api.QuestsAPI; -import fr.skytasul.quests.api.objects.QuestObject; import fr.skytasul.quests.api.objects.QuestObjectClickEvent; import fr.skytasul.quests.api.objects.QuestObjectLocation; import fr.skytasul.quests.api.options.QuestOption; import fr.skytasul.quests.api.rewards.AbstractReward; +import fr.skytasul.quests.api.serializable.SerializableObject; import fr.skytasul.quests.editors.TextEditor; import fr.skytasul.quests.editors.checkers.NumberParser; import fr.skytasul.quests.utils.Lang; -import fr.skytasul.quests.utils.Utils; public class RandomReward extends AbstractReward { @@ -117,16 +116,16 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(Map datas) { - datas.put("rewards", Utils.serializeList(rewards, AbstractReward::serialize)); - datas.put("min", min); - datas.put("max", max); + protected void save(ConfigurationSection section) { + section.set("rewards", SerializableObject.serializeList(rewards)); + section.set("min", min); + section.set("max", max); } @Override - protected void load(Map savedDatas) { - rewards = QuestObject.deserializeList((List>) savedDatas.get("rewards"), AbstractReward::deserialize); - setMinMax((int) savedDatas.get("min"), (int) savedDatas.get("max")); + protected void load(ConfigurationSection section) { + rewards = SerializableObject.deserializeList(section.getMapList("rewards"), AbstractReward::deserialize); + setMinMax(section.getInt("min"), section.getInt("max")); } } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/RemoveItemsReward.java b/core/src/main/java/fr/skytasul/quests/rewards/RemoveItemsReward.java index dd124bae..4c1f2198 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/RemoveItemsReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/RemoveItemsReward.java @@ -3,8 +3,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; @@ -80,15 +80,15 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(Map datas){ - datas.put("items", Utils.serializeList(items, ItemStack::serialize)); - if (!comparisons.getNotDefault().isEmpty()) datas.put("comparisons", comparisons.getNotDefault()); + protected void save(ConfigurationSection section) { + section.set("items", Utils.serializeList(items, ItemStack::serialize)); + if (!comparisons.getNotDefault().isEmpty()) section.createSection("comparisons", comparisons.getNotDefault()); } @Override - protected void load(Map savedDatas){ - items.addAll(Utils.deserializeList((List>) savedDatas.get("items"), ItemStack::deserialize)); - if (savedDatas.containsKey("comparisons")) comparisons.setNotDefaultComparisons((Map) savedDatas.get("comparisons")); + protected void load(ConfigurationSection section){ + items.addAll(Utils.deserializeList(section.getMapList("items"), ItemStack::deserialize)); + if (section.contains("comparisons")) comparisons.setNotDefaultComparisons(section.getConfigurationSection("comparisons")); } } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/RequirementDependentReward.java b/core/src/main/java/fr/skytasul/quests/rewards/RequirementDependentReward.java index 1a9fd0c9..cafbf7fd 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/RequirementDependentReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/RequirementDependentReward.java @@ -2,11 +2,11 @@ import java.util.ArrayList; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; import org.bukkit.Bukkit; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.InventoryType; @@ -14,12 +14,12 @@ import org.bukkit.inventory.ItemStack; import fr.skytasul.quests.api.QuestsAPI; -import fr.skytasul.quests.api.objects.QuestObject; import fr.skytasul.quests.api.objects.QuestObjectClickEvent; import fr.skytasul.quests.api.objects.QuestObjectLocation; import fr.skytasul.quests.api.options.QuestOption; import fr.skytasul.quests.api.requirements.AbstractRequirement; import fr.skytasul.quests.api.rewards.AbstractReward; +import fr.skytasul.quests.api.serializable.SerializableObject; import fr.skytasul.quests.gui.CustomInventory; import fr.skytasul.quests.gui.Inventories; import fr.skytasul.quests.gui.ItemUtils; @@ -139,15 +139,15 @@ public boolean onClick(Player p, Inventory inv, ItemStack current, int slot, Cli } @Override - protected void save(Map datas) { - datas.put("requirements", Utils.serializeList(requirements, AbstractRequirement::serialize)); - datas.put("rewards", Utils.serializeList(rewards, AbstractReward::serialize)); + protected void save(ConfigurationSection section) { + section.set("requirements", SerializableObject.serializeList(requirements)); + section.set("rewards", SerializableObject.serializeList(rewards)); } @Override - protected void load(Map savedDatas) { - requirements = QuestObject.deserializeList((List>) savedDatas.get("requirements"), AbstractRequirement::deserialize); - rewards = QuestObject.deserializeList((List>) savedDatas.get("rewards"), AbstractReward::deserialize); + protected void load(ConfigurationSection section) { + requirements = SerializableObject.deserializeList(section.getMapList("requirements"), AbstractRequirement::deserialize); + rewards = SerializableObject.deserializeList(section.getMapList("rewards"), AbstractReward::deserialize); } } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/TeleportationReward.java b/core/src/main/java/fr/skytasul/quests/rewards/TeleportationReward.java index 64d40c76..bd49e130 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/TeleportationReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/TeleportationReward.java @@ -1,9 +1,9 @@ package fr.skytasul.quests.rewards; import java.util.List; -import java.util.Map; import org.bukkit.Location; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import fr.skytasul.quests.api.objects.QuestObjectClickEvent; @@ -53,13 +53,13 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(Map datas) { - datas.put("tp", teleportation.serialize()); + protected void save(ConfigurationSection section) { + section.set("tp", teleportation.serialize()); } @Override - protected void load(Map savedDatas) { - teleportation = Location.deserialize((Map) savedDatas.get("tp")); + protected void load(ConfigurationSection section) { + teleportation = Location.deserialize(section.getConfigurationSection("tp").getValues(false)); } } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/TitleReward.java b/core/src/main/java/fr/skytasul/quests/rewards/TitleReward.java index fb94eb84..efbeaa39 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/TitleReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/TitleReward.java @@ -1,8 +1,8 @@ package fr.skytasul.quests.rewards; import java.util.List; -import java.util.Map; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import fr.skytasul.quests.api.objects.QuestObjectClickEvent; @@ -49,13 +49,13 @@ public AbstractReward clone() { } @Override - protected void save(Map datas) { - if (title != null) datas.put("title", title.serialize()); + protected void save(ConfigurationSection section) { + if (title != null) title.serialize(section.createSection("title")); } @Override - protected void load(Map savedDatas) { - title = savedDatas.containsKey("title") ? Title.deserialize((Map) savedDatas.get("title")) : null; + protected void load(ConfigurationSection section) { + title = section.contains("title") ? Title.deserialize(section.getConfigurationSection("title")) : null; } } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/WaitReward.java b/core/src/main/java/fr/skytasul/quests/rewards/WaitReward.java index 2b814a27..b82141a6 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/WaitReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/WaitReward.java @@ -1,8 +1,8 @@ package fr.skytasul.quests.rewards; import java.util.List; -import java.util.Map; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import fr.skytasul.quests.api.objects.QuestObjectClickEvent; @@ -62,13 +62,13 @@ public AbstractReward clone() { } @Override - protected void save(Map datas) { - datas.put("delay", delay); + protected void save(ConfigurationSection section) { + section.set("delay", delay); } @Override - protected void load(Map savedDatas) { - delay = (int) savedDatas.get("delay"); + protected void load(ConfigurationSection section) { + delay = section.getInt("delay"); } } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/XPReward.java b/core/src/main/java/fr/skytasul/quests/rewards/XPReward.java index 609d816c..2ff6d25b 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/XPReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/XPReward.java @@ -2,8 +2,8 @@ import java.util.Arrays; import java.util.List; -import java.util.Map; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import fr.skytasul.quests.QuestsConfiguration; @@ -64,13 +64,13 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(Map datas) { - datas.put("xp", exp); + protected void save(ConfigurationSection section) { + section.set("xp", exp); } @Override - protected void load(Map savedDatas) { - exp = (int) savedDatas.get("xp"); + protected void load(ConfigurationSection section) { + exp = section.getInt("xp"); } } diff --git a/core/src/main/java/fr/skytasul/quests/structure/pools/QuestPool.java b/core/src/main/java/fr/skytasul/quests/structure/pools/QuestPool.java index 8d021d4c..29a8a2a9 100644 --- a/core/src/main/java/fr/skytasul/quests/structure/pools/QuestPool.java +++ b/core/src/main/java/fr/skytasul/quests/structure/pools/QuestPool.java @@ -13,8 +13,8 @@ import fr.skytasul.quests.BeautyQuests; import fr.skytasul.quests.api.QuestsAPI; import fr.skytasul.quests.api.npcs.BQNPC; -import fr.skytasul.quests.api.objects.QuestObject; import fr.skytasul.quests.api.requirements.AbstractRequirement; +import fr.skytasul.quests.api.serializable.SerializableObject; import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.players.PlayerAccount; import fr.skytasul.quests.players.PlayerPoolDatas; @@ -260,11 +260,11 @@ public void save(ConfigurationSection config) { config.set("timeDiff", timeDiff); config.set("npcID", npcID); config.set("avoidDuplicates", avoidDuplicates); - if (!requirements.isEmpty()) config.set("requirements", Utils.serializeList(requirements, AbstractRequirement::serialize)); + if (!requirements.isEmpty()) config.set("requirements", SerializableObject.serializeList(requirements)); } public static QuestPool deserialize(int id, ConfigurationSection config) { - List requirements = QuestObject.deserializeList(config.getMapList("requirements"), AbstractRequirement::deserialize); + List requirements = SerializableObject.deserializeList(config.getMapList("requirements"), AbstractRequirement::deserialize); return new QuestPool(id, config.getInt("npcID"), config.getString("hologram"), config.getInt("maxQuests"), config.getInt("questsPerLaunch", 1), config.getBoolean("redoAllowed"), config.getLong("timeDiff"), config.getBoolean("avoidDuplicates", true), requirements); } diff --git a/core/src/main/java/fr/skytasul/quests/utils/Utils.java b/core/src/main/java/fr/skytasul/quests/utils/Utils.java index c4e09e48..2e3733f3 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Utils.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Utils.java @@ -353,11 +353,11 @@ public static List> serializeList(Collection objects, return ls; } - public static List deserializeList(List> serialized, Function, T> deserialize){ + public static List deserializeList(List> serialized, Function, T> deserialize) { List ls = new ArrayList<>(); if (serialized != null) { - for (Map map : serialized) { - ls.add(deserialize.apply(map)); + for (Map map : serialized) { + ls.add(deserialize.apply((Map) map)); } } return ls; diff --git a/core/src/main/java/fr/skytasul/quests/utils/types/Title.java b/core/src/main/java/fr/skytasul/quests/utils/types/Title.java index 73941011..d58cd28d 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/types/Title.java +++ b/core/src/main/java/fr/skytasul/quests/utils/types/Title.java @@ -1,8 +1,6 @@ package fr.skytasul.quests.utils.types; -import java.util.HashMap; -import java.util.Map; - +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import fr.skytasul.quests.utils.Lang; @@ -48,24 +46,20 @@ public String toString() { return title + ", " + subtitle + ", " + Lang.Ticks.format(fadeIn + stay + fadeOut); } - public Map serialize(){ - Map map = new HashMap<>(); - - if (title != null) map.put("title", title); - if (subtitle != null) map.put("subtitle", subtitle); - if (fadeIn != FADE_IN) map.put("fadeIn", fadeIn); - if (stay != STAY) map.put("stay", stay); - if (fadeOut != FADE_OUT) map.put("fadeOut", fadeOut); - - return map; + public void serialize(ConfigurationSection section) { + if (title != null) section.set("title", title); + if (subtitle != null) section.set("subtitle", subtitle); + if (fadeIn != FADE_IN) section.set("fadeIn", fadeIn); + if (stay != STAY) section.set("stay", stay); + if (fadeOut != FADE_OUT) section.set("fadeOut", fadeOut); } - public static Title deserialize(Map map) { - String title = (String) map.getOrDefault("title", null); - String subtitle = (String) map.getOrDefault("subtitle", null); - int fadeIn = map.containsKey("fadeIn") ? (int) map.get("fadeIn") : FADE_IN; - int stay = map.containsKey("stay") ? (int) map.get("stay") : STAY; - int fadeOut = map.containsKey("fadeOut") ? (int) map.get("fadeOut") : FADE_OUT; + public static Title deserialize(ConfigurationSection section) { + String title = section.getString("title", null); + String subtitle = section.getString("subtitle", null); + int fadeIn = section.getInt("fadeIn", FADE_IN); + int stay = section.getInt("stay", STAY); + int fadeOut = section.getInt("fadeOut", FADE_OUT); return new Title(title, subtitle, fadeIn, stay, fadeOut); } diff --git a/pom.xml b/pom.xml index 1af23378..f940f8fe 100644 --- a/pom.xml +++ b/pom.xml @@ -22,7 +22,7 @@ codemc-nms - https://repo.codemc.org/repository/nms/ + https://repo.codemc.io/repository/nms/ From 803fa3a8df9053356e3fe19f6e1138f74d9723e9 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Thu, 12 May 2022 22:49:28 +0200 Subject: [PATCH 004/111] Stage options! --- .../fr/skytasul/quests/api/QuestsHandler.java | 16 ++-------- .../quests/api/objects/QuestObject.java | 2 +- .../api/objects/QuestObjectCreator.java | 12 +++++-- .../api/serializable/SerializableCreator.java | 18 +++++++++-- .../api/serializable/SerializableObject.java | 6 ++-- .../serializable/SerializableRegistry.java | 10 +++--- .../quests/api/stages/AbstractStage.java | 32 ++++++++++++++++--- .../quests/api/stages/StageCreation.java | 17 ++++++++++ .../quests/api/stages/StageHandler.java | 21 ++++++++++++ .../api/stages/options/StageOption.java | 27 ++++++++++++++++ .../quests/gui/creation/QuestObjectGUI.java | 18 +++++------ 11 files changed, 137 insertions(+), 42 deletions(-) create mode 100644 core/src/main/java/fr/skytasul/quests/api/stages/StageHandler.java create mode 100644 core/src/main/java/fr/skytasul/quests/api/stages/options/StageOption.java diff --git a/core/src/main/java/fr/skytasul/quests/api/QuestsHandler.java b/core/src/main/java/fr/skytasul/quests/api/QuestsHandler.java index f59c22d8..4d89d34b 100644 --- a/core/src/main/java/fr/skytasul/quests/api/QuestsHandler.java +++ b/core/src/main/java/fr/skytasul/quests/api/QuestsHandler.java @@ -2,11 +2,11 @@ import org.bukkit.entity.Player; -import fr.skytasul.quests.api.stages.AbstractStage; +import fr.skytasul.quests.api.stages.StageHandler; import fr.skytasul.quests.players.PlayerAccount; import fr.skytasul.quests.structure.Quest; -public interface QuestsHandler { +public interface QuestsHandler extends StageHandler { public default void load() {} @@ -30,16 +30,4 @@ public default void questReset(PlayerAccount acc, Quest quest) {} public default void questUpdated(PlayerAccount acc, Player p, Quest quest) {} - public default void stageStart(PlayerAccount acc, AbstractStage stage) {} - - public default void stageEnd(PlayerAccount acc, AbstractStage stage) {} - - public default void stageJoin(PlayerAccount acc, Player p, AbstractStage stage) {} - - public default void stageLeave(PlayerAccount acc, Player p, AbstractStage stage) {} - - public default void stageLoad(AbstractStage stage) {} - - public default void stageUnload(AbstractStage stage) {} - } diff --git a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java index afbdee58..d9e9f0a7 100644 --- a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java +++ b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java @@ -49,7 +49,7 @@ public String[] getLore() { } public ItemStack getItemStack() { - return ItemUtils.lore(getCreator().item.clone(), getLore()); + return ItemUtils.lore(getCreator().getItem().clone(), getLore()); } public abstract void itemClick(QuestObjectClickEvent event); diff --git a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectCreator.java b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectCreator.java index 9d900092..ab4e113b 100644 --- a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectCreator.java +++ b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectCreator.java @@ -9,8 +9,8 @@ public class QuestObjectCreator extends SerializableCreator { - public final ItemStack item; - public final boolean multiple; + private final ItemStack item; + private final boolean multiple; private QuestObjectLocation[] allowedLocations; /** @@ -38,6 +38,14 @@ public QuestObjectCreator(String id, Class clazz, ItemStack item, S this.allowedLocations = allowedLocations; } + public ItemStack getItem() { + return item; + } + + public boolean canBeMultiple() { + return multiple; + } + public boolean isAllowed(QuestObjectLocation location) { if (allowedLocations.length == 0) return true; for (QuestObjectLocation allowed : allowedLocations) { diff --git a/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableCreator.java b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableCreator.java index 76656d21..71cc7431 100644 --- a/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableCreator.java +++ b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableCreator.java @@ -4,9 +4,9 @@ public class SerializableCreator { - public final String id; - public final Class clazz; - public final Supplier newObjectSupplier; + private final String id; + private final Class clazz; + private final Supplier newObjectSupplier; public SerializableCreator(String id, Class clazz, Supplier newObjectSupplier) { this.id = id; @@ -14,4 +14,16 @@ public SerializableCreator(String id, Class clazz, Supplier newO this.newObjectSupplier = newObjectSupplier; } + public String getID() { + return id; + } + + public Class getSerializableClass() { + return clazz; + } + + public T newObject() { + return newObjectSupplier.get(); + } + } \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableObject.java b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableObject.java index 948ecfc8..535a47b7 100644 --- a/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableObject.java +++ b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableObject.java @@ -31,7 +31,7 @@ public SerializableCreator getCreator() { } public String getName() { - return getCreator().id; + return getCreator().getID(); } @Override @@ -54,7 +54,7 @@ protected void load(ConfigurationSection section) { } public final void serialize(ConfigurationSection section) { - section.set("id", creator.id); + section.set("id", creator.getID()); save(section); } @@ -85,7 +85,7 @@ public static > T BeautyQuests.logger.severe("Cannot find object creator with id: " + id); return null; } - T reward = creator.newObjectSupplier.get(); + T reward = creator.newObject(); reward.load(section); return reward; } diff --git a/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableRegistry.java b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableRegistry.java index 0fd11da7..4293123a 100644 --- a/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableRegistry.java +++ b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableRegistry.java @@ -11,16 +11,16 @@ public class SerializableRegistry creators = new ArrayList<>(); public void register(C creator) { - if (creators.stream().anyMatch(x -> x.id.equals(creator.id))) - throw new IllegalStateException("A creator with the same id " + creator.id + " has been registered."); + if (creators.stream().anyMatch(x -> x.getID().equals(creator.getID()))) + throw new IllegalStateException("A creator with the same id " + creator.getID() + " has been registered."); creators.add(creator); - DebugUtils.logMessage("Quest object registered (id: " + creator.id + ", class: " + creator.clazz.getName() + ")"); + DebugUtils.logMessage("Quest object registered (id: " + creator.getID() + ", class: " + creator.getSerializableClass().getName() + ")"); } public SerializableCreator getByClass(Class clazz) { return creators .stream() - .filter(creator -> creator.clazz.equals(clazz)) + .filter(creator -> creator.getSerializableClass().equals(clazz)) .findAny() .orElse(null); } @@ -28,7 +28,7 @@ public SerializableCreator getByClass(Class clazz) { public SerializableCreator getByID(String id) { return creators .stream() - .filter(creator -> creator.id.equals(id)) + .filter(creator -> creator.getID().equals(id)) .findAny() .orElse(null); } diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java index e749748d..6276ee06 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.function.Consumer; import org.apache.commons.lang.Validate; import org.bukkit.Bukkit; @@ -22,6 +23,7 @@ import fr.skytasul.quests.api.requirements.AbstractRequirement; import fr.skytasul.quests.api.rewards.AbstractReward; import fr.skytasul.quests.api.serializable.SerializableObject; +import fr.skytasul.quests.api.stages.options.StageOption; import fr.skytasul.quests.players.PlayerAccount; import fr.skytasul.quests.players.PlayersManager; import fr.skytasul.quests.players.events.PlayerAccountJoinEvent; @@ -42,6 +44,8 @@ public abstract class AbstractStage implements Listener{ private List rewards = new ArrayList<>(); private List validationRequirements = new ArrayList<>(); + private List options = new ArrayList<>(); + protected AbstractStage(QuestBranch branch) { this.branch = branch; this.type = QuestsAPI.getStageType(getClass()); @@ -79,6 +83,14 @@ public void setValidationRequirements(List validationRequir this.validationRequirements = validationRequirements; validationRequirements.forEach(requirement -> requirement.attach(branch.getQuest())); } + + public List getOptions() { + return options; + } + + public void setOptions(List options) { + this.options = options; + } public String getCustomText(){ return customText; @@ -143,6 +155,18 @@ public String debugName() { return "quest " + branch.getQuest().getID() + ", branch " + branch.getID() + ", stage " + getID() + "(" + type.id + ")"; } + private void propagateStageHandlers(Consumer consumer) { + Consumer newConsumer = handler -> { + try { + consumer.accept(handler); + }catch (Exception ex) { + BeautyQuests.logger.severe("An error occurred while updating stage handler.", ex); + } + }; + QuestsAPI.getQuestsHandlers().forEach(newConsumer); + options.forEach(newConsumer); + } + /** * Called internally when a player finish stage's objectives * @param p Player who finish the stage @@ -169,7 +193,7 @@ public void start(PlayerAccount acc) { Map datas = new HashMap<>(); initPlayerDatas(acc, datas); acc.getQuestDatas(branch.getQuest()).setStageDatas(getStoredID(), datas); - QuestsAPI.propagateQuestsHandlers(handler -> handler.stageStart(acc, this)); + propagateStageHandlers(handler -> handler.stageStart(acc, this)); } protected void initPlayerDatas(PlayerAccount acc, Map datas) {} @@ -180,7 +204,7 @@ protected void initPlayerDatas(PlayerAccount acc, Map datas) {} */ public void end(PlayerAccount acc) { acc.getQuestDatas(branch.getQuest()).setStageDatas(getStoredID(), null); - QuestsAPI.propagateQuestsHandlers(handler -> handler.stageEnd(acc, this)); + propagateStageHandlers(handler -> handler.stageEnd(acc, this)); } /** @@ -188,7 +212,7 @@ public void end(PlayerAccount acc) { * @param acc PlayerAccount which just joined */ public void joins(PlayerAccount acc, Player p) { - QuestsAPI.propagateQuestsHandlers(handler -> handler.stageJoin(acc, p, this)); + propagateStageHandlers(handler -> handler.stageJoin(acc, p, this)); } /** @@ -196,7 +220,7 @@ public void joins(PlayerAccount acc, Player p) { * @param acc PlayerAccount which just left */ public void leaves(PlayerAccount acc, Player p) { - QuestsAPI.propagateQuestsHandlers(handler -> handler.stageLeave(acc, p, this)); + propagateStageHandlers(handler -> handler.stageLeave(acc, p, this)); } public final String getDescriptionLine(PlayerAccount acc, Source source){ diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/StageCreation.java b/core/src/main/java/fr/skytasul/quests/api/stages/StageCreation.java index ec004ead..ea716d3d 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/StageCreation.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/StageCreation.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; import org.bukkit.entity.Player; @@ -10,6 +11,8 @@ import fr.skytasul.quests.api.options.QuestOption; import fr.skytasul.quests.api.requirements.AbstractRequirement; import fr.skytasul.quests.api.rewards.AbstractReward; +import fr.skytasul.quests.api.serializable.SerializableCreator; +import fr.skytasul.quests.api.stages.options.StageOption; import fr.skytasul.quests.editors.TextEditor; import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.gui.creation.stages.Line; @@ -21,10 +24,13 @@ public abstract class StageCreation { protected final Line line; private final boolean ending; + private StageType type; private List rewards; private List requirements; + private List> options; + private String customDescription, startMessage; private StagesGUI leadingBranch; @@ -129,6 +135,10 @@ public void setLeadingBranch(StagesGUI leadingBranch) { this.leadingBranch = leadingBranch; } + public final void setup(StageType type) { + this.type = type; + } + /** * Called when stage item clicked * @param p player who click on the item @@ -138,6 +148,9 @@ public void start(Player p) { setRequirements(new ArrayList<>()); setCustomDescription(null); setStartMessage(null); + + options = type.getOptionsRegistry().getCreators().stream().map(SerializableCreator::newObject).collect(Collectors.toList()); + options.forEach(option -> option.startEdition(this)); } /** @@ -149,6 +162,9 @@ public void edit(T stage) { setRequirements(stage.getValidationRequirements()); setStartMessage(stage.getStartMessage()); setCustomDescription(stage.getCustomText()); + + options = stage.getOptions().stream().map(StageOption::clone).collect(Collectors.toList()); + options.forEach(option -> option.startEdition(this)); } public final T finish(QuestBranch branch) { @@ -157,6 +173,7 @@ public final T finish(QuestBranch branch) { stage.setValidationRequirements(requirements); stage.setCustomText(customDescription); stage.setStartMessage(startMessage); + stage.setOptions((List) options); return stage; } diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/StageHandler.java b/core/src/main/java/fr/skytasul/quests/api/stages/StageHandler.java new file mode 100644 index 00000000..f153f3c4 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/api/stages/StageHandler.java @@ -0,0 +1,21 @@ +package fr.skytasul.quests.api.stages; + +import org.bukkit.entity.Player; + +import fr.skytasul.quests.players.PlayerAccount; + +public interface StageHandler { + + default void stageStart(PlayerAccount acc, AbstractStage stage) {} + + default void stageEnd(PlayerAccount acc, AbstractStage stage) {} + + default void stageJoin(PlayerAccount acc, Player p, AbstractStage stage) {} + + default void stageLeave(PlayerAccount acc, Player p, AbstractStage stage) {} + + default void stageLoad(AbstractStage stage) {} + + default void stageUnload(AbstractStage stage) {} + +} \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/options/StageOption.java b/core/src/main/java/fr/skytasul/quests/api/stages/options/StageOption.java new file mode 100644 index 00000000..e18b6301 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/api/stages/options/StageOption.java @@ -0,0 +1,27 @@ +package fr.skytasul.quests.api.stages.options; + +import fr.skytasul.quests.api.QuestsAPI; +import fr.skytasul.quests.api.serializable.SerializableCreator; +import fr.skytasul.quests.api.serializable.SerializableObject; +import fr.skytasul.quests.api.stages.AbstractStage; +import fr.skytasul.quests.api.stages.StageCreation; +import fr.skytasul.quests.api.stages.StageHandler; + +public abstract class StageOption extends SerializableObject implements StageHandler { + + protected StageOption(Class stageClass) { + super(QuestsAPI.getStageType(stageClass).getOptionsRegistry()); + } + + protected StageOption(SerializableCreator> creator) { + super(creator); + } + + @Override + public abstract StageOption clone(); + + public abstract boolean shouldSave(); + + public abstract void startEdition(StageCreation creation); + +} diff --git a/core/src/main/java/fr/skytasul/quests/gui/creation/QuestObjectGUI.java b/core/src/main/java/fr/skytasul/quests/gui/creation/QuestObjectGUI.java index 23593c83..8aa391fd 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/creation/QuestObjectGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/creation/QuestObjectGUI.java @@ -42,14 +42,12 @@ public class QuestObjectGUI extends ListGUI { public QuestObjectGUI(String name, QuestObjectLocation objectLocation, Collection> creators, Consumer> end, List objects) { super(name, DyeColor.CYAN, (List) objects.stream().map(QuestObject::clone).collect(Collectors.toList())); this.name = name; - this.creators = creators.stream().filter(creator -> creator.isAllowed(objectLocation) && (creator.multiple || !objects.stream().anyMatch(object -> object.getCreator() == creator))).collect(Collectors.toList()); + this.creators = creators.stream() + .filter(creator -> creator.isAllowed(objectLocation)) + .filter(creator -> creator.canBeMultiple() || objects.stream().noneMatch(object -> object.getCreator() == creator)) + .collect(Collectors.toList()); this.end = end; } - - @Deprecated - public void reopen(Player p) { - super.reopen(); - } @Override public ItemStack getObjectItemStack(QuestObject object) { @@ -58,7 +56,7 @@ public ItemStack getObjectItemStack(QuestObject object) { @Override protected void removed(T object) { - if (!object.getCreator().multiple) creators.add(object.getCreator()); + if (!object.getCreator().canBeMultiple()) creators.add(object.getCreator()); } @Override @@ -67,13 +65,13 @@ public void createObject(Function callback) { @Override public ItemStack getItemStack(QuestObjectCreator object) { - return object.item; + return object.getItem(); } @Override public void click(QuestObjectCreator existing, ItemStack item, ClickType clickType) { - T object = existing.newObjectSupplier.get(); - if (!existing.multiple) creators.remove(existing); + T object = existing.newObject(); + if (!existing.canBeMultiple()) creators.remove(existing); object.itemClick(new QuestObjectClickEvent(p, QuestObjectGUI.this, callback.apply(object), clickType, true)); } From 24aca7c807e50f2ea071e2a8c20e0411f603fe73 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Fri, 13 May 2022 13:22:43 +0200 Subject: [PATCH 005/111] Refactored stage types code --- .../fr/skytasul/quests/api/QuestsAPI.java | 17 ++++--- .../api/stages/AbstractCountableStage.java | 12 ++--- .../api/stages/AbstractEntityStage.java | 2 +- .../quests/api/stages/AbstractStage.java | 19 +++---- .../quests/api/stages/StageTypeRegistry.java | 50 +++++++++++++++++++ .../api/stages/options/StageOption.java | 5 +- .../quests/gui/creation/stages/StagesGUI.java | 2 +- .../fr/skytasul/quests/stages/StageArea.java | 2 +- .../fr/skytasul/quests/stages/StageNPC.java | 4 +- .../skytasul/quests/stages/StagePlayTime.java | 2 +- .../quests/structure/QuestBranch.java | 2 +- 11 files changed, 86 insertions(+), 31 deletions(-) create mode 100644 core/src/main/java/fr/skytasul/quests/api/stages/StageTypeRegistry.java diff --git a/core/src/main/java/fr/skytasul/quests/api/QuestsAPI.java b/core/src/main/java/fr/skytasul/quests/api/QuestsAPI.java index 8b5f3536..33f2add3 100644 --- a/core/src/main/java/fr/skytasul/quests/api/QuestsAPI.java +++ b/core/src/main/java/fr/skytasul/quests/api/QuestsAPI.java @@ -24,6 +24,7 @@ import fr.skytasul.quests.api.rewards.RewardCreator; import fr.skytasul.quests.api.stages.AbstractStage; import fr.skytasul.quests.api.stages.StageType; +import fr.skytasul.quests.api.stages.StageTypeRegistry; import fr.skytasul.quests.structure.QuestsManager; import fr.skytasul.quests.structure.pools.QuestPoolsManager; import fr.skytasul.quests.utils.DebugUtils; @@ -33,7 +34,7 @@ public final class QuestsAPI { private static final QuestObjectsRegistry requirements = new QuestObjectsRegistry<>(Lang.INVENTORY_REQUIREMENTS.toString()); private static final QuestObjectsRegistry rewards = new QuestObjectsRegistry<>(Lang.INVENTORY_REWARDS.toString()); - public static final List> stages = new LinkedList<>(); + private static final StageTypeRegistry stages = new StageTypeRegistry(); public static final List itemComparisons = new LinkedList<>(); private static BQNPCsManager npcsManager = null; @@ -46,16 +47,16 @@ private QuestsAPI() {} /** * Register new stage type into the plugin - * @param creator StageType instance + * @param type StageType instance + * @deprecated use {@link StageTypeRegistry#register(StageType)} */ - public static void registerStage(StageType creator) { - stages.add(creator); - DebugUtils.logMessage("Stage registered (" + creator.name + ", " + (stages.size() - 1) + ")"); + @Deprecated + public static void registerStage(StageType type) { // TODO remove, edited on 0.20 + stages.register(type); } - public static StageType getStageType(Class stageClass){ - return (StageType) stages.stream().filter(type -> type.clazz == stageClass).findAny() - .orElseThrow(() -> new IllegalArgumentException(stageClass.getName() + "has not been registered as a stage type via the API.")); + public static StageTypeRegistry getStages() { + return stages; } /** diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractCountableStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractCountableStage.java index 3d19d118..ca6e5728 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractCountableStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractCountableStage.java @@ -75,7 +75,7 @@ protected Supplier[] descriptionFormat(PlayerAccount acc, Source source) private String[] buildRemainingArray(PlayerAccount acc, Source source) { Map playerAmounts = getPlayerRemainings(acc); if (playerAmounts == null) { - BeautyQuests.logger.severe("The plugin has been unable to retrieve stage datas for account " + acc.debugName() + " on " + super.debugName()); + BeautyQuests.logger.severe("The plugin has been unable to retrieve stage datas for account " + acc.debugName() + " on " + super.toString()); return new String[] { "§4§lerror" }; } String[] elements = new String[playerAmounts.size()]; @@ -116,7 +116,7 @@ public boolean event(PlayerAccount acc, Player p, Object object, int amount) { if (objectApplies(entry.getValue().getKey(), object)) { Map playerAmounts = getPlayerRemainings(acc); if (playerAmounts == null) { - BeautyQuests.logger.warning(p.getName() + " does not have object datas for stage " + debugName() + ". This is a bug!"); + BeautyQuests.logger.warning(p.getName() + " does not have object datas for stage " + toString() + ". This is a bug!"); return true; } if (playerAmounts.containsKey(id)) { @@ -133,7 +133,7 @@ public boolean event(PlayerAccount acc, Player p, Object object, int amount) { if (barsEnabled) { BossBar bar = bars.get(p); if (bar == null) { - BeautyQuests.logger.warning(p.getName() + " does not have boss bar for stage " + debugName() + ". This is a bug!"); + BeautyQuests.logger.warning(p.getName() + " does not have boss bar for stage " + toString() + ". This is a bug!"); }else bar.update(playerAmounts.values().stream().mapToInt(Integer::intValue).sum()); } updateObjective(acc, p, "remaining", playerAmounts); @@ -167,7 +167,7 @@ public void joins(PlayerAccount acc, Player p) { super.joins(acc, p); Map remainings = getPlayerRemainings(acc); if (remainings == null) { - BeautyQuests.logger.severe(p.getName() + " does not have remaining datas for stage " + debugName() + ". This is a bug!"); + BeautyQuests.logger.severe(p.getName() + " does not have remaining datas for stage " + toString() + ". This is a bug!"); return; } createBar(p, remainings.values().stream().mapToInt(Integer::intValue).sum()); @@ -242,7 +242,7 @@ protected void deserialize(ConfigurationSection section) { } } - if (objects.isEmpty()) BeautyQuests.logger.warning("Stage with no content: " + debugName()); + if (objects.isEmpty()) BeautyQuests.logger.warning("Stage with no content: " + toString()); calculateSize(); } @@ -276,7 +276,7 @@ public void remove() { public void update(int amount) { if (amount >= 0 && amount <= cachedSize) { bar.setProgress((double) (cachedSize - amount) / (double) cachedSize); - }else BeautyQuests.logger.warning("Amount of objects superior to max objects in " + debugName() + " for player " + p.getName() + ": " + amount + " > " + cachedSize); + }else BeautyQuests.logger.warning("Amount of objects superior to max objects in " + AbstractCountableStage.this.toString() + " for player " + p.getName() + ": " + amount + " > " + cachedSize); bar.setTitle(Lang.MobsProgression.format(branch.getQuest().getName(), cachedSize - amount, cachedSize)); bar.addPlayer(p); timer(); diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractEntityStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractEntityStage.java index a9cceff2..79c84779 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractEntityStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractEntityStage.java @@ -40,7 +40,7 @@ protected void event(Player p, EntityType type) { if (entity == null || type.equals(entity)) { Integer playerAmount = getPlayerAmount(acc); if (playerAmount == null) { - BeautyQuests.logger.warning(p.getName() + " does not have object datas for stage " + debugName() + ". This is a bug!"); + BeautyQuests.logger.warning(p.getName() + " does not have object datas for stage " + toString() + ". This is a bug!"); }else if (playerAmount.intValue() <= 1) { finishStage(p); }else { diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java index 6276ee06..7a622784 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java @@ -32,7 +32,7 @@ import fr.skytasul.quests.structure.QuestBranch.Source; import fr.skytasul.quests.utils.Utils; -public abstract class AbstractStage implements Listener{ +public abstract class AbstractStage implements Listener { private final StageType type; protected boolean asyncEnd = false; @@ -48,7 +48,7 @@ public abstract class AbstractStage implements Listener{ protected AbstractStage(QuestBranch branch) { this.branch = branch; - this.type = QuestsAPI.getStageType(getClass()); + this.type = QuestsAPI.getStages().getType(getClass()).orElseThrow(() -> new IllegalArgumentException(getClass().getName() + "has not been registered as a stage type via the API.")); Bukkit.getPluginManager().registerEvents(this, BeautyQuests.getInstance()); } @@ -151,8 +151,9 @@ protected boolean canUpdate(Player p, boolean msg) { return true; } - public String debugName() { - return "quest " + branch.getQuest().getID() + ", branch " + branch.getID() + ", stage " + getID() + "(" + type.id + ")"; + @Override + public String toString() { + return "stage " + getID() + "(" + type.id + ") of quest " + branch.getQuest().getID() + ", branch " + branch.getID(); } private void propagateStageHandlers(Consumer consumer) { @@ -228,7 +229,7 @@ public final String getDescriptionLine(PlayerAccount acc, Source source){ try{ return descriptionLine(acc, source); }catch (Exception ex){ - BeautyQuests.logger.severe("An error occurred while getting the description line for player " + acc.getName() + " in " + debugName(), ex); + BeautyQuests.logger.severe("An error occurred while getting the description line for player " + acc.getName() + " in " + toString(), ex); return "§a" + type.name; } } @@ -250,7 +251,7 @@ public final String getDescriptionLine(PlayerAccount acc, Source source){ public void updateObjective(PlayerAccount acc, Player p, String dataKey, Object dataValue) { Map datas = acc.getQuestDatas(branch.getQuest()).getStageDatas(getStoredID()); - Validate.notNull(datas, "Account " + acc.debugName() + " does not have datas for " + debugName()); + Validate.notNull(datas, "Account " + acc.debugName() + " does not have datas for " + toString()); datas.put(dataKey, dataValue); acc.getQuestDatas(branch.getQuest()).setStageDatas(getStoredID(), datas); branch.getBranchesManager().objectiveUpdated(p, acc); @@ -265,7 +266,7 @@ protected T getData(PlayerAccount acc, String dataKey) { * Called when the stage has to be unloaded */ public void unload(){ - QuestsAPI.propagateQuestsHandlers(handler -> handler.stageUnload(this)); + propagateStageHandlers(handler -> handler.stageUnload(this)); HandlerList.unregisterAll(this); rewards.forEach(AbstractReward::detach); validationRequirements.forEach(AbstractRequirement::detach); @@ -275,7 +276,7 @@ public void unload(){ * Called when the stage loads */ public void load() { - QuestsAPI.propagateQuestsHandlers(handler -> handler.stageLoad(this)); + propagateStageHandlers(handler -> handler.stageLoad(this)); } @EventHandler @@ -318,7 +319,7 @@ public final void save(ConfigurationSection section) { public static AbstractStage deserialize(ConfigurationSection section, QuestBranch branch) { String typeID = section.getString("stageType"); - Optional> stageTypeOptional = QuestsAPI.stages.stream().filter(type -> type.id.equals(typeID)).findAny(); + Optional> stageTypeOptional = QuestsAPI.getStages().getType(typeID); if (!stageTypeOptional.isPresent()) { BeautyQuests.getInstance().getLogger().severe("Unknown stage type : " + typeID); return null; diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/StageTypeRegistry.java b/core/src/main/java/fr/skytasul/quests/api/stages/StageTypeRegistry.java new file mode 100644 index 00000000..4012e804 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/api/stages/StageTypeRegistry.java @@ -0,0 +1,50 @@ +package fr.skytasul.quests.api.stages; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; + +import org.apache.commons.lang.Validate; + +import fr.skytasul.quests.utils.DebugUtils; + +public class StageTypeRegistry implements Iterable> { + + private List> types = new LinkedList<>(); + + /** + * Register new stage type into the plugin + * @param type StageType instance + */ + public void register(StageType type) { + Validate.notNull(type); + types.add(type); + DebugUtils.logMessage("Stage registered (" + type.name + ", " + (types.size() - 1) + ")"); + } + + public List> getTypes() { + return types; + } + + public Optional> getType(Class stageClass) { + return types + .stream() + .filter(type -> type.clazz == stageClass) + .map(type -> (StageType) type) + .findAny(); + } + + public Optional> getType(String id) { + return types + .stream() + .filter(type -> type.id.equals(id)) + .findAny(); + } + + @Override + public Iterator> iterator() { + return types.iterator(); + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/options/StageOption.java b/core/src/main/java/fr/skytasul/quests/api/stages/options/StageOption.java index e18b6301..7720ab9b 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/options/StageOption.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/options/StageOption.java @@ -10,7 +10,10 @@ public abstract class StageOption extends SerializableObject implements StageHandler { protected StageOption(Class stageClass) { - super(QuestsAPI.getStageType(stageClass).getOptionsRegistry()); + super(QuestsAPI.getStages() + .getType(stageClass) + .orElseThrow(() -> new IllegalArgumentException(stageClass.getName() + "has not been registered as a stage type via the API.")) + .getOptionsRegistry()); } protected StageOption(SerializableCreator> creator) { diff --git a/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java b/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java index 1f2d6c32..a8dbc35e 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java @@ -104,7 +104,7 @@ private void setStageCreate(Line line, boolean branches){ line.setItem(0, stageCreate.clone(), (p, item) -> { line.setItem(0, null, null, true, false); int i = 0; - for (StageType creator : QuestsAPI.stages) { + for (StageType creator : QuestsAPI.getStages()) { if (creator.isValid()) { line.setItem(++i, creator.item, (p1, item1) -> { runClick(line, creator, branches).start(p1); diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageArea.java b/core/src/main/java/fr/skytasul/quests/stages/StageArea.java index d87cf97c..ae085330 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageArea.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageArea.java @@ -59,7 +59,7 @@ public void onPlayerMove(PlayerMoveEvent e){ @EventHandler public void onRegionEntry(WorldGuardEntryEvent e) { if (region == null) { - DebugUtils.printError("No region for " + debugName(), "area" + debugName(), 5); + DebugUtils.printError("No region for " + toString(), "area" + toString(), 5); return; } if (e.getRegionsEntered().stream().anyMatch(eventRegion -> eventRegion.getId().equals(region.getId()))) { diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java b/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java index b5ceef4e..8a552858 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java @@ -114,7 +114,7 @@ public void setNPC(int npcID) { this.npcID = npcID; if (npcID >= 0) this.npc = QuestsAPI.getNPCsManager().getById(npcID); if (npc == null) { - BeautyQuests.logger.warning("The NPC " + npcID + " does not exist for " + debugName()); + BeautyQuests.logger.warning("The NPC " + npcID + " does not exist for " + toString()); }else { initDialogRunner(); } @@ -267,7 +267,7 @@ protected void loadDatas(ConfigurationSection section) { if (section.contains("msg")) setDialog(Dialog.deserialize(section.getConfigurationSection("msg"))); if (section.contains("npcID")) { setNPC(section.getInt("npcID")); - }else BeautyQuests.logger.warning("No NPC specified for " + debugName()); + }else BeautyQuests.logger.warning("No NPC specified for " + toString()); if (section.contains("hid")) hide = section.getBoolean("hid"); } diff --git a/core/src/main/java/fr/skytasul/quests/stages/StagePlayTime.java b/core/src/main/java/fr/skytasul/quests/stages/StagePlayTime.java index 02bc4cbe..d8a39ace 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StagePlayTime.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StagePlayTime.java @@ -75,7 +75,7 @@ public void leaves(PlayerAccount acc, Player p) { task.cancel(); updateObjective(acc, null, "remainingTime", getRemaining(acc)); }else { - BeautyQuests.logger.warning("Unavailable task in \"Play Time\" stage " + debugName() + " for player " + acc.getName()); + BeautyQuests.logger.warning("Unavailable task in \"Play Time\" stage " + toString() + " for player " + acc.getName()); } } diff --git a/core/src/main/java/fr/skytasul/quests/structure/QuestBranch.java b/core/src/main/java/fr/skytasul/quests/structure/QuestBranch.java index e4aab983..e0f88753 100644 --- a/core/src/main/java/fr/skytasul/quests/structure/QuestBranch.java +++ b/core/src/main/java/fr/skytasul/quests/structure/QuestBranch.java @@ -161,7 +161,7 @@ public void finishStage(Player p, AbstractStage stage){ PlayerAccount acc = PlayersManager.getPlayerAccount(p); PlayerQuestDatas datas = acc.getQuestDatas(getQuest()); if (datas.getBranch() != getID() || (datas.isInEndingStages() && isRegularStage(stage)) || (!datas.isInEndingStages() && datas.getStage() != stage.getID())) { - BeautyQuests.logger.warning("Trying to finish stage " + stage.debugName() + " for player " + p.getName() + ", but the player didn't have started it."); + BeautyQuests.logger.warning("Trying to finish stage " + stage.toString() + " for player " + p.getName() + ", but the player didn't have started it."); return; } AdminMode.broadcast("Player " + p.getName() + " has finished the stage " + getID(stage) + " of quest " + getQuest().getID()); From 4267a14bd316861e05018804da8ec0452d27c919 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 15 May 2022 22:50:30 +0200 Subject: [PATCH 006/111] Refactored stages, locatable, extracted abstract classes --- .../api/comparison/ItemComparisonMap.java | 11 +- .../fr/skytasul/quests/api/npcs/BQNPC.java | 5 +- .../skytasul/quests/api/stages/Locatable.java | 11 -- .../types/AbstractCountableBlockStage.java | 99 ++++++++++++++++ .../{ => types}/AbstractEntityStage.java | 30 ++++- .../stages/{ => types}/AbstractItemStage.java | 6 +- .../api/stages/{ => types}/Dialogable.java | 2 +- .../quests/api/stages/types/Locatable.java | 111 ++++++++++++++++++ .../fr/skytasul/quests/commands/Commands.java | 2 +- .../quests/gui/quests/DialogHistoryGUI.java | 2 +- .../quests/options/OptionStartDialog.java | 2 +- .../fr/skytasul/quests/stages/StageBreed.java | 2 +- .../quests/stages/StageBringBack.java | 2 +- .../fr/skytasul/quests/stages/StageCraft.java | 2 +- .../skytasul/quests/stages/StageEnchant.java | 2 +- .../fr/skytasul/quests/stages/StageFish.java | 2 +- .../skytasul/quests/stages/StageInteract.java | 33 ++++-- .../skytasul/quests/stages/StageLocation.java | 10 +- .../fr/skytasul/quests/stages/StageMelt.java | 2 +- .../fr/skytasul/quests/stages/StageMine.java | 57 ++------- .../fr/skytasul/quests/stages/StageNPC.java | 16 +-- .../quests/stages/StagePlaceBlocks.java | 73 ++---------- .../fr/skytasul/quests/stages/StageTame.java | 2 +- .../skytasul/quests/utils/types/BQBlock.java | 27 +++++ .../quests/utils/types/BQLocation.java | 34 +++++- 25 files changed, 375 insertions(+), 170 deletions(-) delete mode 100644 core/src/main/java/fr/skytasul/quests/api/stages/Locatable.java create mode 100644 core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableBlockStage.java rename core/src/main/java/fr/skytasul/quests/api/stages/{ => types}/AbstractEntityStage.java (80%) rename core/src/main/java/fr/skytasul/quests/api/stages/{ => types}/AbstractItemStage.java (94%) rename core/src/main/java/fr/skytasul/quests/api/stages/{ => types}/Dialogable.java (87%) create mode 100644 core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java diff --git a/core/src/main/java/fr/skytasul/quests/api/comparison/ItemComparisonMap.java b/core/src/main/java/fr/skytasul/quests/api/comparison/ItemComparisonMap.java index f408ac1d..e38a15ff 100644 --- a/core/src/main/java/fr/skytasul/quests/api/comparison/ItemComparisonMap.java +++ b/core/src/main/java/fr/skytasul/quests/api/comparison/ItemComparisonMap.java @@ -21,14 +21,19 @@ public ItemComparisonMap() { this(new HashMap<>()); } + public ItemComparisonMap(ConfigurationSection notDefault) { + setNotDefaultComparisons(notDefault); + } + public ItemComparisonMap(Map notDefault) { setNotDefaultComparisons(notDefault); } public void setNotDefaultComparisons(ConfigurationSection section) { - Map map = new HashMap<>(); - section.getKeys(false).forEach(key -> notDefault.put(key, section.getBoolean(key))); - setNotDefaultComparisons(map); + effective = new ArrayList<>(); + for (ItemComparison comp : QuestsAPI.itemComparisons) { + if (section.getBoolean(comp.getID(), comp.isEnabledByDefault())) effective.add(comp); + } } public void setNotDefaultComparisons(Map comparisons) { diff --git a/core/src/main/java/fr/skytasul/quests/api/npcs/BQNPC.java b/core/src/main/java/fr/skytasul/quests/api/npcs/BQNPC.java index 29b5837e..ccc77c3f 100644 --- a/core/src/main/java/fr/skytasul/quests/api/npcs/BQNPC.java +++ b/core/src/main/java/fr/skytasul/quests/api/npcs/BQNPC.java @@ -17,6 +17,7 @@ import fr.skytasul.quests.QuestsConfiguration; import fr.skytasul.quests.api.AbstractHolograms; import fr.skytasul.quests.api.QuestsAPI; +import fr.skytasul.quests.api.stages.types.Locatable.Located; import fr.skytasul.quests.options.OptionHologramLaunch; import fr.skytasul.quests.options.OptionHologramLaunchNo; import fr.skytasul.quests.options.OptionHologramText; @@ -29,7 +30,7 @@ import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.Utils; -public abstract class BQNPC { +public abstract class BQNPC implements Located.LocatedEntity { private Map> quests = new TreeMap<>(); private Set pools = new TreeSet<>(); @@ -64,8 +65,10 @@ protected BQNPC() { public abstract boolean isSpawned(); + @Override public abstract Entity getEntity(); + @Override public abstract Location getLocation(); public abstract void setSkin(String skin); diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/Locatable.java b/core/src/main/java/fr/skytasul/quests/api/stages/Locatable.java deleted file mode 100644 index cf865395..00000000 --- a/core/src/main/java/fr/skytasul/quests/api/stages/Locatable.java +++ /dev/null @@ -1,11 +0,0 @@ -package fr.skytasul.quests.api.stages; - -import org.bukkit.Location; - -public interface Locatable { - - Location getLocation(); - - boolean isShown(); - -} diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableBlockStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableBlockStage.java new file mode 100644 index 00000000..e2825462 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableBlockStage.java @@ -0,0 +1,99 @@ +package fr.skytasul.quests.api.stages.types; + +import java.util.Collection; +import java.util.Map; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import org.bukkit.block.Block; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; + +import fr.skytasul.quests.api.stages.AbstractCountableStage; +import fr.skytasul.quests.api.stages.StageCreation; +import fr.skytasul.quests.gui.Inventories; +import fr.skytasul.quests.gui.ItemUtils; +import fr.skytasul.quests.gui.blocks.BlocksGUI; +import fr.skytasul.quests.gui.creation.stages.Line; +import fr.skytasul.quests.structure.QuestBranch; +import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.types.BQBlock; + +public abstract class AbstractCountableBlockStage extends AbstractCountableStage implements Locatable.MultipleLocatable { + + protected AbstractCountableBlockStage(QuestBranch branch, Map> objects) { + super(branch, objects); + } + + @Override + protected boolean objectApplies(BQBlock object, Object other) { + if (other instanceof Block) return object.applies((Block) other); + return super.objectApplies(object, other); + } + + @Override + protected String getName(BQBlock object) { + return object.getName(); + } + + @Override + protected Object serialize(BQBlock object) { + return object.getAsString(); + } + + @Override + protected BQBlock deserialize(Object object) { + return BQBlock.fromString((String) object); + } + + @Override + public Collection getNearbyLocated(NearbyFetcher fetcher) { + return BQBlock.getNearbyBlocks(fetcher, objects.values().stream().map(Entry::getKey).collect(Collectors.toList())); + } + + public abstract static class AbstractCreator extends StageCreation { + + protected Map> blocks; + + protected AbstractCreator(Line line, boolean ending) { + super(line, ending); + + line.setItem(getBlocksSlot(), getBlocksItem(), (p, item) -> { + BlocksGUI blocksGUI = Inventories.create(p, new BlocksGUI()); + blocksGUI.setBlocksFromMap(blocks); + blocksGUI.run = obj -> { + setBlocks(obj); + reopenGUI(p, true); + }; + }); + } + + protected abstract ItemStack getBlocksItem(); + + protected int getBlocksSlot() { + return 7; + } + + public void setBlocks(Map> blocks) { + this.blocks = blocks; + line.editItem(getBlocksSlot(), ItemUtils.lore(line.getItem(getBlocksSlot()), Lang.optionValue.format(blocks.size() + " blocks"))); + } + + @Override + public void start(Player p) { + super.start(p); + Inventories.create(p, new BlocksGUI()).run = obj -> { + setBlocks(obj); + reopenGUI(p, true); + }; + } + + @Override + public void edit(T stage) { + super.edit(stage); + setBlocks(stage.cloneObjects()); + } + + } + +} \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractEntityStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java similarity index 80% rename from core/src/main/java/fr/skytasul/quests/api/stages/AbstractEntityStage.java rename to core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java index 79c84779..8fd46fed 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractEntityStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java @@ -1,7 +1,13 @@ -package fr.skytasul.quests.api.stages; +package fr.skytasul.quests.api.stages.types; +import java.util.AbstractMap; +import java.util.Collection; +import java.util.Comparator; import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; import java.util.function.Supplier; +import java.util.stream.Collectors; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.EntityType; @@ -9,6 +15,8 @@ import fr.skytasul.quests.BeautyQuests; import fr.skytasul.quests.QuestsConfiguration; +import fr.skytasul.quests.api.stages.AbstractStage; +import fr.skytasul.quests.api.stages.StageCreation; import fr.skytasul.quests.editors.TextEditor; import fr.skytasul.quests.editors.checkers.NumberParser; import fr.skytasul.quests.gui.ItemUtils; @@ -23,7 +31,7 @@ import fr.skytasul.quests.utils.Utils; import fr.skytasul.quests.utils.XMaterial; -public abstract class AbstractEntityStage extends AbstractStage { +public abstract class AbstractEntityStage extends AbstractStage implements Locatable.MultipleLocatable { protected EntityType entity; protected int amount; @@ -78,6 +86,24 @@ protected Supplier[] descriptionFormat(PlayerAccount acc, Source source) return new Supplier[] { () -> getMobsLeft(acc) }; } + @Override + public Collection getNearbyLocated(NearbyFetcher fetcher) { + double distanceSquared = fetcher.getMaxDistance() * fetcher.getMaxDistance(); + return fetcher.getCenter().getWorld() + .getEntitiesByClass(entity.getEntityClass()) + .stream() + .map(x -> { + double ds = x.getLocation().distanceSquared(fetcher.getCenter()); + if (ds > distanceSquared) return null; + return new AbstractMap.SimpleEntry<>(x, ds); + }) + .filter(Objects::nonNull) + .sorted(Comparator.comparing(Entry::getValue)) + .limit(fetcher.getMaxAmount()) + .map(entry -> Located.LocatedEntity.create(entry.getKey())) + .collect(Collectors.toList()); + } + public abstract static class AbstractCreator extends StageCreation { protected EntityType entity = null; diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractItemStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractItemStage.java similarity index 94% rename from core/src/main/java/fr/skytasul/quests/api/stages/AbstractItemStage.java rename to core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractItemStage.java index 905cbdb1..c01a32bf 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractItemStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractItemStage.java @@ -1,4 +1,4 @@ -package fr.skytasul.quests.api.stages; +package fr.skytasul.quests.api.stages.types; import java.util.AbstractMap; import java.util.Collections; @@ -13,6 +13,8 @@ import org.bukkit.inventory.ItemStack; import fr.skytasul.quests.api.comparison.ItemComparisonMap; +import fr.skytasul.quests.api.stages.AbstractCountableStage; +import fr.skytasul.quests.api.stages.StageCreation; import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.gui.creation.ItemsGUI; import fr.skytasul.quests.gui.creation.stages.Line; @@ -35,7 +37,7 @@ protected AbstractItemStage(QuestBranch branch, ConfigurationSection section) { super(branch, new HashMap<>()); if (section.contains("itemComparisons")) { - comparisons = new ItemComparisonMap((Map) section.getConfigurationSection("itemComparisons").getValues(false)); + comparisons = new ItemComparisonMap(section.getConfigurationSection("itemComparisons")); }else comparisons = new ItemComparisonMap(); super.deserialize(section); diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/Dialogable.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/Dialogable.java similarity index 87% rename from core/src/main/java/fr/skytasul/quests/api/stages/Dialogable.java rename to core/src/main/java/fr/skytasul/quests/api/stages/types/Dialogable.java index 697a37cb..4d5dd4a2 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/Dialogable.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/Dialogable.java @@ -1,4 +1,4 @@ -package fr.skytasul.quests.api.stages; +package fr.skytasul.quests.api.stages.types; import fr.skytasul.quests.api.npcs.BQNPC; import fr.skytasul.quests.utils.types.Dialog; diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java new file mode 100644 index 00000000..7444a340 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java @@ -0,0 +1,111 @@ +package fr.skytasul.quests.api.stages.types; + +import java.util.Collection; + +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.entity.Entity; + +public interface Locatable { + + default boolean isShown() { + return true; + } + + interface PreciseLocatable extends Locatable { + + Located getLocated(); + + } + + interface MultipleLocatable extends Locatable { + + Collection getNearbyLocated(NearbyFetcher fetcher); + + interface NearbyFetcher { + + Location getCenter(); + + double getMaxDistance(); + + int getMaxAmount(); + + } + + } + + interface Located { + + Location getLocation(); + + static Located create(Location location) { + return new LocatedImpl(location); + } + + class LocatedImpl implements Located { + private Location location; + + public LocatedImpl(Location location) { + this.location = location; + } + + @Override + public Location getLocation() { + return location; + } + } + + interface LocatedEntity extends Located { + + Entity getEntity(); + + @Override + default Location getLocation() { + Entity entity = getEntity(); + return entity == null ? null : entity.getLocation(); + } + + static LocatedEntity create(Entity entity) { + return new LocatedEntityImpl(entity); + } + + class LocatedEntityImpl implements LocatedEntity { + private Entity entity; + + public LocatedEntityImpl(Entity entity) { + this.entity = entity; + } + + @Override + public Entity getEntity() { + return entity; + } + } + + } + + interface LocatedBlock extends Located { + + default Block getBlock() { + return getLocation().getBlock(); + } + + static LocatedBlock create(Block block) { + return new LocatedBlockImpl(block.getLocation()); + } + + static LocatedBlock create(Location location) { + return new LocatedBlockImpl(location); + } + + class LocatedBlockImpl extends LocatedImpl implements LocatedBlock { + public LocatedBlockImpl(Location location) { + super(location); + } + } + + } + + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/commands/Commands.java b/core/src/main/java/fr/skytasul/quests/commands/Commands.java index 5f3c69ad..1af23def 100644 --- a/core/src/main/java/fr/skytasul/quests/commands/Commands.java +++ b/core/src/main/java/fr/skytasul/quests/commands/Commands.java @@ -22,7 +22,7 @@ import fr.skytasul.quests.BeautyQuests; import fr.skytasul.quests.api.QuestsAPI; import fr.skytasul.quests.api.stages.AbstractStage; -import fr.skytasul.quests.api.stages.Dialogable; +import fr.skytasul.quests.api.stages.types.Dialogable; import fr.skytasul.quests.editors.Editor; import fr.skytasul.quests.editors.SelectNPC; import fr.skytasul.quests.gui.Inventories; diff --git a/core/src/main/java/fr/skytasul/quests/gui/quests/DialogHistoryGUI.java b/core/src/main/java/fr/skytasul/quests/gui/quests/DialogHistoryGUI.java index 1ba1d779..ade56677 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/quests/DialogHistoryGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/quests/DialogHistoryGUI.java @@ -16,7 +16,7 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; -import fr.skytasul.quests.api.stages.Dialogable; +import fr.skytasul.quests.api.stages.types.Dialogable; import fr.skytasul.quests.gui.quests.DialogHistoryGUI.WrappedDialogable; import fr.skytasul.quests.gui.templates.PagedGUI; import fr.skytasul.quests.options.OptionStartDialog; diff --git a/core/src/main/java/fr/skytasul/quests/options/OptionStartDialog.java b/core/src/main/java/fr/skytasul/quests/options/OptionStartDialog.java index b664be02..f7783fa8 100644 --- a/core/src/main/java/fr/skytasul/quests/options/OptionStartDialog.java +++ b/core/src/main/java/fr/skytasul/quests/options/OptionStartDialog.java @@ -9,7 +9,7 @@ import fr.skytasul.quests.api.npcs.BQNPC; import fr.skytasul.quests.api.options.OptionSet; import fr.skytasul.quests.api.options.QuestOption; -import fr.skytasul.quests.api.stages.Dialogable; +import fr.skytasul.quests.api.stages.types.Dialogable; import fr.skytasul.quests.editors.DialogEditor; import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.gui.creation.FinishGUI; diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageBreed.java b/core/src/main/java/fr/skytasul/quests/stages/StageBreed.java index 64a5d9b7..42475175 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageBreed.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageBreed.java @@ -7,7 +7,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.entity.EntityBreedEvent; -import fr.skytasul.quests.api.stages.AbstractEntityStage; +import fr.skytasul.quests.api.stages.types.AbstractEntityStage; import fr.skytasul.quests.gui.creation.stages.Line; import fr.skytasul.quests.players.PlayerAccount; import fr.skytasul.quests.structure.QuestBranch; diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageBringBack.java b/core/src/main/java/fr/skytasul/quests/stages/StageBringBack.java index 42dc18e7..3175f459 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageBringBack.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageBringBack.java @@ -146,7 +146,7 @@ public static StageBringBack deserialize(ConfigurationSection section, QuestBran String customMessage = section.getString("customMessage", null); ItemComparisonMap comparisons; if (section.contains("itemComparisons")) { - comparisons = new ItemComparisonMap((Map) section.getConfigurationSection("itemComparisons").getValues(false)); + comparisons = new ItemComparisonMap(section.getConfigurationSection("itemComparisons")); }else comparisons = new ItemComparisonMap(); StageBringBack st = new StageBringBack(branch, items, customMessage, comparisons); st.loadDatas(section); diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageCraft.java b/core/src/main/java/fr/skytasul/quests/stages/StageCraft.java index 1e6c7d24..44541dee 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageCraft.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageCraft.java @@ -138,7 +138,7 @@ protected void serialize(ConfigurationSection section) { } public static StageCraft deserialize(ConfigurationSection section, QuestBranch branch) { - return new StageCraft(branch, ItemStack.deserialize(section.getConfigurationSection("result").getValues(false)), section.contains("itemComparisons") ? new ItemComparisonMap((Map) section.getConfigurationSection("itemComparisons").getValues(false)) : new ItemComparisonMap()); + return new StageCraft(branch, ItemStack.deserialize(section.getConfigurationSection("result").getValues(false)), section.contains("itemComparisons") ? new ItemComparisonMap(section.getConfigurationSection("itemComparisons")) : new ItemComparisonMap()); } public static int getMaxCraftAmount(CraftingInventory inv) { diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageEnchant.java b/core/src/main/java/fr/skytasul/quests/stages/StageEnchant.java index aa5c9327..35ea649f 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageEnchant.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageEnchant.java @@ -11,7 +11,7 @@ import org.bukkit.inventory.meta.ItemMeta; import fr.skytasul.quests.api.comparison.ItemComparisonMap; -import fr.skytasul.quests.api.stages.AbstractItemStage; +import fr.skytasul.quests.api.stages.types.AbstractItemStage; import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.gui.creation.stages.Line; import fr.skytasul.quests.players.PlayerAccount; diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageFish.java b/core/src/main/java/fr/skytasul/quests/stages/StageFish.java index ccdda3d7..326772b5 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageFish.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageFish.java @@ -13,7 +13,7 @@ import org.bukkit.inventory.ItemStack; import fr.skytasul.quests.api.comparison.ItemComparisonMap; -import fr.skytasul.quests.api.stages.AbstractItemStage; +import fr.skytasul.quests.api.stages.types.AbstractItemStage; import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.gui.creation.stages.Line; import fr.skytasul.quests.players.PlayerAccount; diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java b/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java index b40ca5c9..78c6fd06 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java @@ -1,5 +1,8 @@ package fr.skytasul.quests.stages; +import java.util.Collection; +import java.util.Collections; + import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.configuration.ConfigurationSection; @@ -15,6 +18,7 @@ import fr.skytasul.quests.api.options.QuestOption; import fr.skytasul.quests.api.stages.AbstractStage; import fr.skytasul.quests.api.stages.StageCreation; +import fr.skytasul.quests.api.stages.types.Locatable; import fr.skytasul.quests.editors.WaitBlockClick; import fr.skytasul.quests.gui.CustomInventory; import fr.skytasul.quests.gui.ItemUtils; @@ -27,17 +31,18 @@ import fr.skytasul.quests.utils.Utils; import fr.skytasul.quests.utils.XMaterial; import fr.skytasul.quests.utils.types.BQBlock; +import fr.skytasul.quests.utils.types.BQLocation; -public class StageInteract extends AbstractStage { +public class StageInteract extends AbstractStage implements Locatable.MultipleLocatable, Locatable.PreciseLocatable { private boolean left; - private Location lc; + private BQLocation lc; private BQBlock block; - public StageInteract(QuestBranch branch, boolean leftClick, Location location) { + public StageInteract(QuestBranch branch, boolean leftClick, BQLocation location) { super(branch); this.left = leftClick; - this.lc = location.getBlock().getLocation(); + this.lc = new BQLocation(location.getBlock().getLocation()); } public StageInteract(QuestBranch branch, boolean leftClick, BQBlock block) { @@ -46,7 +51,7 @@ public StageInteract(QuestBranch branch, boolean leftClick, BQBlock block) { this.block = block; } - public Location getLocation(){ + public BQLocation getLocation() { return lc; } @@ -58,6 +63,18 @@ public boolean needLeftClick(){ return left; } + @Override + public Located getLocated() { + return lc; + } + + @Override + public Collection getNearbyLocated(NearbyFetcher fetcher) { + if (block == null) return null; + + return BQBlock.getNearbyBlocks(fetcher, Collections.singleton(block)); + } + @EventHandler public void onInteract(PlayerInteractEvent e){ if (e.getClickedBlock() == null) return; @@ -65,7 +82,7 @@ public void onInteract(PlayerInteractEvent e){ if (e.getAction() != Action.LEFT_CLICK_BLOCK) return; }else if (e.getAction() != Action.RIGHT_CLICK_BLOCK) return; if (lc != null) { - if (!e.getClickedBlock().getLocation().equals(lc)) return; + if (!lc.equals(e.getClickedBlock().getLocation())) return; }else if (!block.applies(e.getClickedBlock())) return; Player p = e.getPlayer(); @@ -90,7 +107,7 @@ protected void serialize(ConfigurationSection section) { public static StageInteract deserialize(ConfigurationSection section, QuestBranch branch) { if (section.contains("location")) { - return new StageInteract(branch, section.getBoolean("leftClick"), Location.deserialize(section.getConfigurationSection("location").getValues(false))); + return new StageInteract(branch, section.getBoolean("leftClick"), BQLocation.deserialize(section.getConfigurationSection("location").getValues(false))); }else { BQBlock block; if (section.contains("material")) { @@ -178,7 +195,7 @@ public void edit(StageInteract stage) { @Override public StageInteract finishStage(QuestBranch branch) { if (location != null) { - return new StageInteract(branch, leftClick, location); + return new StageInteract(branch, leftClick, new BQLocation(location)); }else return new StageInteract(branch, leftClick, block); } diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageLocation.java b/core/src/main/java/fr/skytasul/quests/stages/StageLocation.java index 99c21a69..d4f25742 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageLocation.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageLocation.java @@ -11,8 +11,8 @@ import fr.skytasul.quests.QuestsConfiguration; import fr.skytasul.quests.api.options.QuestOption; import fr.skytasul.quests.api.stages.AbstractStage; -import fr.skytasul.quests.api.stages.Locatable; import fr.skytasul.quests.api.stages.StageCreation; +import fr.skytasul.quests.api.stages.types.Locatable; import fr.skytasul.quests.editors.TextEditor; import fr.skytasul.quests.editors.WaitClick; import fr.skytasul.quests.editors.checkers.NumberParser; @@ -29,7 +29,7 @@ import fr.skytasul.quests.utils.compatibility.GPS; import fr.skytasul.quests.utils.types.BQLocation; -public class StageLocation extends AbstractStage implements Locatable { +public class StageLocation extends AbstractStage implements Locatable.PreciseLocatable { private final BQLocation lc; private final int radius; @@ -48,7 +48,6 @@ public StageLocation(QuestBranch branch, BQLocation lc, int radius, boolean gps) this.descMessage = Lang.SCOREBOARD_LOCATION.format(lc.getBlockX(), lc.getBlockY(), lc.getBlockZ(), lc.getWorldName()); } - @Override public BQLocation getLocation() { return lc; } @@ -58,6 +57,11 @@ public boolean isShown() { return isGPSEnabled(); } + @Override + public Located getLocated() { + return lc; + } + public int getRadius(){ return radius; } diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageMelt.java b/core/src/main/java/fr/skytasul/quests/stages/StageMelt.java index 4fa13d8f..1d677674 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageMelt.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageMelt.java @@ -10,7 +10,7 @@ import org.bukkit.inventory.ItemStack; import fr.skytasul.quests.api.comparison.ItemComparisonMap; -import fr.skytasul.quests.api.stages.AbstractItemStage; +import fr.skytasul.quests.api.stages.types.AbstractItemStage; import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.gui.creation.stages.Line; import fr.skytasul.quests.players.PlayerAccount; diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageMine.java b/core/src/main/java/fr/skytasul/quests/stages/StageMine.java index fc859adb..7b768af0 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageMine.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageMine.java @@ -10,15 +10,13 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.block.BlockPlaceEvent; +import org.bukkit.inventory.ItemStack; import org.bukkit.metadata.FixedMetadataValue; import fr.skytasul.quests.BeautyQuests; import fr.skytasul.quests.api.events.BQBlockBreakEvent; -import fr.skytasul.quests.api.stages.AbstractCountableStage; -import fr.skytasul.quests.api.stages.StageCreation; -import fr.skytasul.quests.gui.Inventories; +import fr.skytasul.quests.api.stages.types.AbstractCountableBlockStage; import fr.skytasul.quests.gui.ItemUtils; -import fr.skytasul.quests.gui.blocks.BlocksGUI; import fr.skytasul.quests.gui.creation.stages.Line; import fr.skytasul.quests.players.PlayerAccount; import fr.skytasul.quests.players.PlayersManager; @@ -28,7 +26,7 @@ import fr.skytasul.quests.utils.XMaterial; import fr.skytasul.quests.utils.types.BQBlock; -public class StageMine extends AbstractCountableStage { +public class StageMine extends AbstractCountableBlockStage { private boolean placeCancelled; @@ -78,27 +76,6 @@ public void onPlace(BlockPlaceEvent e){ } } - @Override - protected boolean objectApplies(BQBlock object, Object other) { - if (other instanceof Block) return object.applies((Block) other); - return super.objectApplies(object, other); - } - - @Override - protected String getName(BQBlock object) { - return object.getName(); - } - - @Override - protected Object serialize(BQBlock object) { - return object.getAsString(); - } - - @Override - protected BQBlock deserialize(Object object) { - return BQBlock.fromString((String) object); - } - @Override protected void serialize(ConfigurationSection section) { super.serialize(section); @@ -113,28 +90,19 @@ public static StageMine deserialize(ConfigurationSection section, QuestBranch br return stage; } - public static class Creator extends StageCreation { + public static class Creator extends AbstractCountableBlockStage.AbstractCreator { - private Map> blocks; private boolean prevent = false; public Creator(Line line, boolean ending) { super(line, ending); - line.setItem(7, ItemUtils.item(XMaterial.STONE_PICKAXE, Lang.editBlocksMine.toString()), (p, item) -> { - BlocksGUI blocksGUI = Inventories.create(p, new BlocksGUI()); - blocksGUI.setBlocksFromMap(blocks); - blocksGUI.run = obj -> { - setBlocks(obj); - reopenGUI(p, true); - }; - }); line.setItem(6, ItemUtils.itemSwitch(Lang.preventBlockPlace.toString(), prevent), (p, item) -> setPrevent(ItemUtils.toggle(item))); } - public void setBlocks(Map> blocks) { - this.blocks = blocks; - line.editItem(7, ItemUtils.lore(line.getItem(7), Lang.optionValue.format(blocks.size() + " block(s)"))); + @Override + protected ItemStack getBlocksItem() { + return ItemUtils.item(XMaterial.STONE_PICKAXE, Lang.editBlocksMine.toString()); } public void setPrevent(boolean prevent) { @@ -143,21 +111,10 @@ public void setPrevent(boolean prevent) { line.editItem(6, ItemUtils.set(line.getItem(6), prevent)); } } - - @Override - public void start(Player p) { - super.start(p); - BlocksGUI blocksGUI = Inventories.create(p, new BlocksGUI()); - blocksGUI.run = obj -> { - setBlocks(obj); - reopenGUI(p, true); - }; - } @Override public void edit(StageMine stage) { super.edit(stage); - setBlocks(stage.cloneObjects()); setPrevent(stage.isPlaceCancelled()); } diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java b/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java index 8a552858..125cbbfc 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java @@ -22,9 +22,9 @@ import fr.skytasul.quests.api.npcs.BQNPC; import fr.skytasul.quests.api.options.QuestOption; import fr.skytasul.quests.api.stages.AbstractStage; -import fr.skytasul.quests.api.stages.Dialogable; -import fr.skytasul.quests.api.stages.Locatable; import fr.skytasul.quests.api.stages.StageCreation; +import fr.skytasul.quests.api.stages.types.Dialogable; +import fr.skytasul.quests.api.stages.types.Locatable; import fr.skytasul.quests.editors.DialogEditor; import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.gui.creation.stages.Line; @@ -39,7 +39,7 @@ import fr.skytasul.quests.utils.types.Dialog; import fr.skytasul.quests.utils.types.DialogRunner; -public class StageNPC extends AbstractStage implements Locatable, Dialogable { +public class StageNPC extends AbstractStage implements Locatable.PreciseLocatable, Dialogable { private BQNPC npc; private int npcID; @@ -147,16 +147,16 @@ public void setHid(boolean hide){ this.hide = hide; } - @Override - public Location getLocation() { - return npc != null && npc.isSpawned() ? npc.getLocation() : null; - } - @Override public boolean isShown() { return !hide; } + @Override + public Located getLocated() { + return npc; + } + @Override public String descriptionLine(PlayerAccount acc, Source source){ return Utils.format(Lang.SCOREBOARD_NPC.toString(), descriptionFormat(acc, source)); diff --git a/core/src/main/java/fr/skytasul/quests/stages/StagePlaceBlocks.java b/core/src/main/java/fr/skytasul/quests/stages/StagePlaceBlocks.java index ed2e9189..c47130d3 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StagePlaceBlocks.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StagePlaceBlocks.java @@ -4,7 +4,6 @@ import java.util.Map; import java.util.Map.Entry; -import org.bukkit.block.Block; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; @@ -12,23 +11,18 @@ import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.inventory.ItemStack; -import fr.skytasul.quests.api.stages.AbstractCountableStage; -import fr.skytasul.quests.api.stages.StageCreation; -import fr.skytasul.quests.gui.Inventories; +import fr.skytasul.quests.api.stages.types.AbstractCountableBlockStage; import fr.skytasul.quests.gui.ItemUtils; -import fr.skytasul.quests.gui.blocks.BlocksGUI; import fr.skytasul.quests.gui.creation.stages.Line; -import fr.skytasul.quests.gui.creation.stages.StageRunnable; import fr.skytasul.quests.players.PlayerAccount; import fr.skytasul.quests.players.PlayersManager; import fr.skytasul.quests.structure.QuestBranch; import fr.skytasul.quests.structure.QuestBranch.Source; import fr.skytasul.quests.utils.Lang; -import fr.skytasul.quests.utils.MinecraftNames; import fr.skytasul.quests.utils.XMaterial; import fr.skytasul.quests.utils.types.BQBlock; -public class StagePlaceBlocks extends AbstractCountableStage { +public class StagePlaceBlocks extends AbstractCountableBlockStage { public StagePlaceBlocks(QuestBranch branch, Map> blocks) { super(branch, blocks); @@ -40,8 +34,8 @@ public String descriptionLine(PlayerAccount acc, Source source){ } @EventHandler (priority = EventPriority.MONITOR) - public void onMine(BlockPlaceEvent e) { - if (e.isCancelled() || e.getPlayer() == null) return; + public void onPlace(BlockPlaceEvent e) { + if (e.isCancelled()) return; Player p = e.getPlayer(); PlayerAccount acc = PlayersManager.getPlayerAccount(p); if (branch.hasStageLaunched(acc, this)){ @@ -49,74 +43,23 @@ public void onMine(BlockPlaceEvent e) { } } - @Override - protected boolean objectApplies(BQBlock object, Object other) { - if (other instanceof Block) return object.applies((Block) other); - return super.objectApplies(object, other); - } - - @Override - protected String getName(BQBlock object) { - return MinecraftNames.getMaterialName(object.getMaterial()); - } - - @Override - protected Object serialize(BQBlock object) { - return object.getAsString(); - } - - @Override - protected BQBlock deserialize(Object object) { - return BQBlock.fromString((String) object); - } - public static StagePlaceBlocks deserialize(ConfigurationSection section, QuestBranch branch) { StagePlaceBlocks stage = new StagePlaceBlocks(branch, new HashMap<>()); stage.deserialize(section); return stage; } - public static class Creator extends StageCreation { - - private Map> blocks; + public static class Creator extends AbstractCountableBlockStage.AbstractCreator { public Creator(Line line, boolean ending) { super(line, ending); - - line.setItem(7, ItemUtils.item(XMaterial.STONE, Lang.editBlocksPlace.toString()), new StageRunnable() { - @Override - public void run(Player p, ItemStack item) { - BlocksGUI blocksGUI = Inventories.create(p, new BlocksGUI()); - blocksGUI.setBlocksFromMap(blocks); - blocksGUI.run = (obj) -> { - setBlocks(obj); - reopenGUI(p, true); - }; - } - }); - } - - public void setBlocks(Map> blocks) { - this.blocks = blocks; - line.editItem(7, ItemUtils.lore(line.getItem(7), Lang.optionValue.format(blocks.size() + " blocks"))); - } - - @Override - public void start(Player p) { - super.start(p); - BlocksGUI blocks = Inventories.create(p, new BlocksGUI()); - blocks.run = (obj) -> { - setBlocks(obj); - reopenGUI(p, true); - }; } @Override - public void edit(StagePlaceBlocks stage) { - super.edit(stage); - setBlocks(stage.cloneObjects()); + protected ItemStack getBlocksItem() { + return ItemUtils.item(XMaterial.STONE, Lang.editBlocksPlace.toString()); } - + @Override public StagePlaceBlocks finishStage(QuestBranch branch) { return new StagePlaceBlocks(branch, blocks); diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageTame.java b/core/src/main/java/fr/skytasul/quests/stages/StageTame.java index a7e0c341..1dcc890a 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageTame.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageTame.java @@ -7,7 +7,7 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.entity.EntityTameEvent; -import fr.skytasul.quests.api.stages.AbstractEntityStage; +import fr.skytasul.quests.api.stages.types.AbstractEntityStage; import fr.skytasul.quests.gui.creation.stages.Line; import fr.skytasul.quests.players.PlayerAccount; import fr.skytasul.quests.structure.QuestBranch; diff --git a/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java b/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java index c4311b8b..33529adf 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java +++ b/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java @@ -1,7 +1,12 @@ package fr.skytasul.quests.utils.types; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + import org.bukkit.block.Block; +import fr.skytasul.quests.api.stages.types.Locatable; import fr.skytasul.quests.utils.MinecraftNames; import fr.skytasul.quests.utils.XBlock; import fr.skytasul.quests.utils.XMaterial; @@ -40,6 +45,28 @@ public static BQBlock fromString(String string) { return new BQBlockMaterial(XMaterial.valueOf(string)); } + public static Collection getNearbyBlocks(Locatable.MultipleLocatable.NearbyFetcher fetcher, Collection types) { + List blocks = new ArrayList<>(); + int minX = (int) (fetcher.getCenter().getX() - fetcher.getMaxDistance()); + int minY = (int) Math.max(fetcher.getCenter().getWorld().getMinHeight(), fetcher.getCenter().getY() - fetcher.getMaxDistance()); + int minZ = (int) (fetcher.getCenter().getZ() - fetcher.getMaxDistance()); + double maxX = fetcher.getCenter().getX() + fetcher.getMaxDistance(); + double maxY = Math.min(fetcher.getCenter().getWorld().getMinHeight(), fetcher.getCenter().getY() + fetcher.getMaxDistance()); + double maxZ = fetcher.getCenter().getZ() + fetcher.getMaxDistance(); + for (int x = minX; x <= maxX; x++) { + for (int z = minZ; z <= maxZ; z++) { + for (int y = minY; y <= maxY; y++) { + Block blockAt = fetcher.getCenter().getWorld().getBlockAt(x, y, z); + if (types.stream().anyMatch(type -> type.applies(blockAt))) { + blocks.add(Locatable.Located.LocatedBlock.create(blockAt)); + if (blocks.size() >= fetcher.getMaxAmount()) break; + } + } + } + } + return blocks; + } + public static class BQBlockMaterial extends BQBlock { private final XMaterial material; diff --git a/core/src/main/java/fr/skytasul/quests/utils/types/BQLocation.java b/core/src/main/java/fr/skytasul/quests/utils/types/BQLocation.java index ebb63836..ff2afd97 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/types/BQLocation.java +++ b/core/src/main/java/fr/skytasul/quests/utils/types/BQLocation.java @@ -1,6 +1,7 @@ package fr.skytasul.quests.utils.types; import java.util.Map; +import java.util.Objects; import java.util.regex.Pattern; import org.apache.commons.lang.Validate; @@ -10,7 +11,9 @@ import org.bukkit.util.NumberConversions; import org.jetbrains.annotations.NotNull; -public class BQLocation extends Location { +import fr.skytasul.quests.api.stages.types.Locatable; + +public class BQLocation extends Location implements Locatable.Located { private Pattern worldPattern; @@ -51,6 +54,11 @@ public void setWorld(World world) { throw new UnsupportedOperationException(); } + @Override + public Location getLocation() { + return this; + } + public boolean isWorld(World world) { Validate.notNull(world); if (super.getWorld() != null) return super.getWorld().equals(world); @@ -71,11 +79,25 @@ public double distanceSquared(Location o) { @Override public boolean equals(Object obj) { - if (!super.equals(obj)) return false; - BQLocation other = (BQLocation) obj; - if (worldPattern == null) return other.worldPattern == null; - if (other.worldPattern == null) return false; - return worldPattern.pattern().equals(other.worldPattern.pattern()); + if (!(obj instanceof Location)) return false; + + if (obj instanceof BQLocation) { + if (!super.equals(obj)) return false; + BQLocation other = (BQLocation) obj; + if (worldPattern == null) return other.worldPattern == null; + if (other.worldPattern == null) return false; + return worldPattern.pattern().equals(other.worldPattern.pattern()); + } + + Location other = (Location) obj; + if (!Objects.equals(other.getWorld(), getWorld())) { + if (other.getWorld() == null) return false; + if (worldPattern == null) return false; + if (!worldPattern.matcher(other.getWorld().getName()).matches()) return false; + other = other.clone(); + other.setWorld(null); + } + return super.equals(other); } @Override From e539d81f5c651a5ec016a726273e77f6baf18f02 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 15 May 2022 23:11:06 +0200 Subject: [PATCH 007/111] Change in registries --- .../fr/skytasul/quests/api/QuestsAPI.java | 4 +- .../api/objects/QuestObjectsRegistry.java | 3 +- .../serializable/SerializableRegistry.java | 11 ++++- .../quests/api/stages/AbstractStage.java | 8 ++-- .../skytasul/quests/api/stages/StageType.java | 42 +++++++++++++++---- .../quests/api/stages/StageTypeRegistry.java | 6 +-- .../api/stages/options/StageOption.java | 8 ++-- .../quests/gui/creation/stages/StagesGUI.java | 6 +-- 8 files changed, 64 insertions(+), 24 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/api/QuestsAPI.java b/core/src/main/java/fr/skytasul/quests/api/QuestsAPI.java index 33f2add3..84192ee5 100644 --- a/core/src/main/java/fr/skytasul/quests/api/QuestsAPI.java +++ b/core/src/main/java/fr/skytasul/quests/api/QuestsAPI.java @@ -32,8 +32,8 @@ public final class QuestsAPI { - private static final QuestObjectsRegistry requirements = new QuestObjectsRegistry<>(Lang.INVENTORY_REQUIREMENTS.toString()); - private static final QuestObjectsRegistry rewards = new QuestObjectsRegistry<>(Lang.INVENTORY_REWARDS.toString()); + private static final QuestObjectsRegistry requirements = new QuestObjectsRegistry<>("requirements", Lang.INVENTORY_REQUIREMENTS.toString()); + private static final QuestObjectsRegistry rewards = new QuestObjectsRegistry<>("rewards", Lang.INVENTORY_REWARDS.toString()); private static final StageTypeRegistry stages = new StageTypeRegistry(); public static final List itemComparisons = new LinkedList<>(); diff --git a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectsRegistry.java b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectsRegistry.java index e64a5e5c..34fd5943 100644 --- a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectsRegistry.java +++ b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectsRegistry.java @@ -11,7 +11,8 @@ public class QuestObjectsRegistry> implements Iterable { + protected final String id; protected final List creators = new ArrayList<>(); + public SerializableRegistry(String id) { + this.id = id; + } + + public String getID() { + return id; + } + public void register(C creator) { if (creators.stream().anyMatch(x -> x.getID().equals(creator.getID()))) throw new IllegalStateException("A creator with the same id " + creator.getID() + " has been registered."); creators.add(creator); - DebugUtils.logMessage("Quest object registered (id: " + creator.getID() + ", class: " + creator.getSerializableClass().getName() + ")"); + DebugUtils.logMessage("Quest object registered in registry " + id + " (id: " + creator.getID() + ", class: " + creator.getSerializableClass().getName() + ")"); } public SerializableCreator getByClass(Class clazz) { diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java index 7a622784..4b8be9f4 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java @@ -153,7 +153,7 @@ protected boolean canUpdate(Player p, boolean msg) { @Override public String toString() { - return "stage " + getID() + "(" + type.id + ") of quest " + branch.getQuest().getID() + ", branch " + branch.getID(); + return "stage " + getID() + "(" + type.getID() + ") of quest " + branch.getQuest().getID() + ", branch " + branch.getID(); } private void propagateStageHandlers(Consumer consumer) { @@ -230,7 +230,7 @@ public final String getDescriptionLine(PlayerAccount acc, Source source){ return descriptionLine(acc, source); }catch (Exception ex){ BeautyQuests.logger.severe("An error occurred while getting the description line for player " + acc.getName() + " in " + toString(), ex); - return "§a" + type.name; + return "§a" + type.getName(); } } @@ -308,7 +308,7 @@ protected void serialize(ConfigurationSection section) { public final void save(ConfigurationSection section) { serialize(section); - section.set("stageType", type.id); + section.set("stageType", type.getID()); section.set("customText", customText); if (startMessage != null) section.set("text", startMessage); @@ -331,7 +331,7 @@ public static AbstractStage deserialize(ConfigurationSection section, QuestBranc return null; } - AbstractStage st = stageType.loader.supply(section, branch); + AbstractStage st = stageType.getLoader().supply(section, branch); if (section.contains("text")) st.startMessage = section.getString("text"); if (section.contains("customText")) st.customText = section.getString("customText"); if (section.contains("rewards")) st.setRewards(QuestObject.deserializeList(section.getMapList("rewards"), AbstractReward::deserialize)); diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/StageType.java b/core/src/main/java/fr/skytasul/quests/api/stages/StageType.java index 2c70de41..1d4d403b 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/StageType.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/StageType.java @@ -15,15 +15,16 @@ public class StageType { - public final String id; - public final Class clazz; - public final String name; - public final StageLoader loader; - public final ItemStack item; - public final StageCreationSupplier creationSupplier; + private final String id; + private final Class clazz; + private final String name; + private final StageLoader loader; + private final ItemStack item; + private final StageCreationSupplier creationSupplier; + @Deprecated public final String[] dependencies; - private final SerializableRegistry, SerializableCreator>> optionsRegistry = new SerializableRegistry<>(); + private final SerializableRegistry, SerializableCreator>> optionsRegistry; public StageType(String id, Class clazz, String name, StageLoader loader, ItemStack item, StageCreationSupplier creationSupplier) { this(id, clazz, name, loader, item, creationSupplier, new String[0]); @@ -42,10 +43,37 @@ public StageType(String id, Class clazz, String name, StageLoader loader, this.item = item; this.loader = loader; this.creationSupplier = creationSupplier; + + this.optionsRegistry = new SerializableRegistry<>("stage-options-" + id); + this.dependencies = dependencies; if (dependencies.length != 0) BeautyQuests.logger.warning("Nag author of the " + id + " stage type about its use of the deprecated \"dependencies\" feature."); } + public String getID() { + return id; + } + + public Class getStageClass() { + return clazz; + } + + public String getName() { + return name; + } + + public StageLoader getLoader() { + return loader; + } + + public ItemStack getItem() { + return item; + } + + public StageCreationSupplier getCreationSupplier() { + return creationSupplier; + } + public SerializableRegistry, SerializableCreator>> getOptionsRegistry() { return optionsRegistry; } diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/StageTypeRegistry.java b/core/src/main/java/fr/skytasul/quests/api/stages/StageTypeRegistry.java index 4012e804..0c51cf6a 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/StageTypeRegistry.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/StageTypeRegistry.java @@ -20,7 +20,7 @@ public class StageTypeRegistry implements Iterable> { public void register(StageType type) { Validate.notNull(type); types.add(type); - DebugUtils.logMessage("Stage registered (" + type.name + ", " + (types.size() - 1) + ")"); + DebugUtils.logMessage("Stage registered (" + type.getName() + ", " + (types.size() - 1) + ")"); } public List> getTypes() { @@ -30,7 +30,7 @@ public List> getTypes() { public Optional> getType(Class stageClass) { return types .stream() - .filter(type -> type.clazz == stageClass) + .filter(type -> type.getStageClass() == stageClass) .map(type -> (StageType) type) .findAny(); } @@ -38,7 +38,7 @@ public Optional> getType(Class stageCl public Optional> getType(String id) { return types .stream() - .filter(type -> type.id.equals(id)) + .filter(type -> type.getID().equals(id)) .findAny(); } diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/options/StageOption.java b/core/src/main/java/fr/skytasul/quests/api/stages/options/StageOption.java index 7720ab9b..28b4d766 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/options/StageOption.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/options/StageOption.java @@ -1,7 +1,6 @@ package fr.skytasul.quests.api.stages.options; import fr.skytasul.quests.api.QuestsAPI; -import fr.skytasul.quests.api.serializable.SerializableCreator; import fr.skytasul.quests.api.serializable.SerializableObject; import fr.skytasul.quests.api.stages.AbstractStage; import fr.skytasul.quests.api.stages.StageCreation; @@ -9,15 +8,18 @@ public abstract class StageOption extends SerializableObject implements StageHandler { + private final Class stageClass; + protected StageOption(Class stageClass) { super(QuestsAPI.getStages() .getType(stageClass) .orElseThrow(() -> new IllegalArgumentException(stageClass.getName() + "has not been registered as a stage type via the API.")) .getOptionsRegistry()); + this.stageClass = stageClass; } - protected StageOption(SerializableCreator> creator) { - super(creator); + public Class getStageClass() { + return stageClass; } @Override diff --git a/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java b/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java index a8dbc35e..de4e23d9 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java @@ -106,7 +106,7 @@ private void setStageCreate(Line line, boolean branches){ int i = 0; for (StageType creator : QuestsAPI.getStages()) { if (creator.isValid()) { - line.setItem(++i, creator.item, (p1, item1) -> { + line.setItem(++i, creator.getItem(), (p1, item1) -> { runClick(line, creator, branches).start(p1); }, true, false); } @@ -129,13 +129,13 @@ private void updateLineManageLore(Line line) { private StageCreation runClick(Line line, StageType creator, boolean branches) { line.removeItems(); - StageCreation creation = creator.creationSupplier.supply(line, branches); + StageCreation creation = creator.getCreationSupplier().supply(line, branches); line.creation = creation; inv.setItem(SLOT_FINISH, ItemUtils.itemDone); int maxStages = branches ? 20 : 15; - ItemStack manageItem = ItemUtils.item(XMaterial.BARRIER, Lang.stageType.format(creator.name), getLineManageLore(line.getLine())); + ItemStack manageItem = ItemUtils.item(XMaterial.BARRIER, Lang.stageType.format(creator.getName()), getLineManageLore(line.getLine())); line.setItem(0, manageItem, new StageRunnableClick() { @Override public void run(Player p, ItemStack item, ClickType click) { From 481b018773ba2e1d724eef97302af9861e0839f7 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Thu, 19 May 2022 19:00:02 +0200 Subject: [PATCH 008/111] Changed visibility of save and load methods --- .../quests/api/objects/QuestObject.java | 22 +++++++++++++++++++ .../requirements/TargetNumberRequirement.java | 4 ++-- .../api/serializable/SerializableObject.java | 17 ++------------ .../serializable/SerializableRegistry.java | 4 ++-- .../quests/api/stages/AbstractStage.java | 19 +++++++++++++++- .../quests/api/stages/StageCreation.java | 4 ++++ .../api/stages/options/StageOption.java | 4 ++-- .../quests/gui/creation/stages/Line.java | 9 ++++---- .../quests/requirements/ClassRequirement.java | 4 ++-- .../requirements/FactionRequirement.java | 4 ++-- .../requirements/JobLevelRequirement.java | 4 ++-- .../requirements/McMMOSkillRequirement.java | 4 ++-- .../quests/requirements/MoneyRequirement.java | 4 ++-- .../requirements/PermissionsRequirement.java | 4 ++-- .../requirements/PlaceholderRequirement.java | 4 ++-- .../quests/requirements/QuestRequirement.java | 4 ++-- .../requirements/RegionRequirement.java | 4 ++-- .../requirements/ScoreboardRequirement.java | 4 ++-- .../logical/LogicalOrRequirement.java | 4 ++-- .../quests/rewards/CheckpointReward.java | 4 ++-- .../quests/rewards/CommandReward.java | 4 ++-- .../skytasul/quests/rewards/ItemReward.java | 4 ++-- .../quests/rewards/MessageReward.java | 4 ++-- .../skytasul/quests/rewards/MoneyReward.java | 4 ++-- .../quests/rewards/PermissionReward.java | 4 ++-- .../skytasul/quests/rewards/RandomReward.java | 4 ++-- .../quests/rewards/RemoveItemsReward.java | 4 ++-- .../rewards/RequirementDependentReward.java | 4 ++-- .../quests/rewards/TeleportationReward.java | 4 ++-- .../skytasul/quests/rewards/TitleReward.java | 4 ++-- .../skytasul/quests/rewards/WaitReward.java | 4 ++-- .../fr/skytasul/quests/rewards/XPReward.java | 4 ++-- 32 files changed, 105 insertions(+), 74 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java index d9e9f0a7..d44d2895 100644 --- a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java +++ b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java @@ -1,5 +1,9 @@ package fr.skytasul.quests.api.objects; +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; @@ -40,6 +44,24 @@ public String debugName() { @Override public abstract QuestObject clone(); + @Deprecated + protected void save(Map datas) {} + + @Deprecated + protected void load(Map savedDatas) {} + + @Override + public void save(ConfigurationSection section) { + Map datas = new HashMap<>(); + save(datas); + datas.forEach(section::set); + } + + @Override + public void load(ConfigurationSection section) { + load(section.getValues(false)); + } + public String getDescription(Player p) { // will maybe eventually be abstract (and therefore needs to be implemented) return null; } diff --git a/core/src/main/java/fr/skytasul/quests/api/requirements/TargetNumberRequirement.java b/core/src/main/java/fr/skytasul/quests/api/requirements/TargetNumberRequirement.java index 7264800d..5c7f1fe4 100644 --- a/core/src/main/java/fr/skytasul/quests/api/requirements/TargetNumberRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/api/requirements/TargetNumberRequirement.java @@ -57,13 +57,13 @@ public String[] getLore() { public abstract void sendHelpString(Player p); @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { section.set("comparison", comparison.name()); section.set("target", target); } @Override - protected void load(ConfigurationSection section) { + public void load(ConfigurationSection section) { if (section.contains("comparison")) comparison = ComparisonMethod.valueOf(section.getString("comparison")); target = section.getDouble("target"); } diff --git a/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableObject.java b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableObject.java index 535a47b7..d5998820 100644 --- a/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableObject.java +++ b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableObject.java @@ -1,7 +1,6 @@ package fr.skytasul.quests.api.serializable; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -36,22 +35,10 @@ public String getName() { @Override public abstract SerializableObject clone(); - - @Deprecated - protected void save(Map datas) {} - - @Deprecated - protected void load(Map savedDatas) {} - protected void save(ConfigurationSection section) { - Map datas = new HashMap<>(); - save(datas); - datas.forEach(section::set); - } + public abstract void save(ConfigurationSection section); - protected void load(ConfigurationSection section) { - load(section.getValues(false)); - } + public abstract void load(ConfigurationSection section); public final void serialize(ConfigurationSection section) { section.set("id", creator.getID()); diff --git a/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableRegistry.java b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableRegistry.java index aa2782b0..eca67c71 100644 --- a/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableRegistry.java +++ b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableRegistry.java @@ -26,7 +26,7 @@ public void register(C creator) { DebugUtils.logMessage("Quest object registered in registry " + id + " (id: " + creator.getID() + ", class: " + creator.getSerializableClass().getName() + ")"); } - public SerializableCreator getByClass(Class clazz) { + public C getByClass(Class clazz) { return creators .stream() .filter(creator -> creator.getSerializableClass().equals(clazz)) @@ -34,7 +34,7 @@ public SerializableCreator getByClass(Class clazz) { .orElse(null); } - public SerializableCreator getByID(String id) { + public C getByID(String id) { return creators .stream() .filter(creator -> creator.getID().equals(id)) diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java index 4b8be9f4..1c63afb0 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java @@ -7,6 +7,7 @@ import java.util.Map; import java.util.Optional; import java.util.function.Consumer; +import java.util.stream.Collectors; import org.apache.commons.lang.Validate; import org.bukkit.Bukkit; @@ -22,6 +23,7 @@ import fr.skytasul.quests.api.objects.QuestObject; import fr.skytasul.quests.api.requirements.AbstractRequirement; import fr.skytasul.quests.api.rewards.AbstractReward; +import fr.skytasul.quests.api.serializable.SerializableCreator; import fr.skytasul.quests.api.serializable.SerializableObject; import fr.skytasul.quests.api.stages.options.StageOption; import fr.skytasul.quests.players.PlayerAccount; @@ -44,7 +46,7 @@ public abstract class AbstractStage implements Listener { private List rewards = new ArrayList<>(); private List validationRequirements = new ArrayList<>(); - private List options = new ArrayList<>(); + private List options; protected AbstractStage(QuestBranch branch) { this.branch = branch; @@ -314,6 +316,8 @@ public final void save(ConfigurationSection section) { if (!rewards.isEmpty()) section.set("rewards", SerializableObject.serializeList(rewards)); if (!validationRequirements.isEmpty()) section.set("requirements", SerializableObject.serializeList(validationRequirements)); + + options.stream().filter(StageOption::shouldSave).forEach(option -> option.save(section.createSection("options." + option.getCreator().getID()))); } public static AbstractStage deserialize(ConfigurationSection section, QuestBranch branch) { @@ -337,6 +341,19 @@ public static AbstractStage deserialize(ConfigurationSection section, QuestBranc if (section.contains("rewards")) st.setRewards(QuestObject.deserializeList(section.getMapList("rewards"), AbstractReward::deserialize)); if (section.contains("requirements")) st.setValidationRequirements(QuestObject.deserializeList(section.getMapList("requirements"), AbstractRequirement::deserialize)); + st.options = stageType.getOptionsRegistry().getCreators().stream().map(SerializableCreator::newObject).collect(Collectors.toList()); + if (section.contains("options")) { + ConfigurationSection optionsSection = section.getConfigurationSection("options"); + optionsSection.getKeys(false).forEach(optionID -> { + st.options + .stream() + .filter(option -> option.getCreator().getID().equals(optionID)) + .findAny() + .ifPresent(option -> option.load(optionsSection.getConfigurationSection(optionID))); + }); + + } + return st; } } diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/StageCreation.java b/core/src/main/java/fr/skytasul/quests/api/stages/StageCreation.java index ea716d3d..536165ad 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/StageCreation.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/StageCreation.java @@ -68,6 +68,10 @@ public StageCreation(Line line, boolean ending) { }); } + public StageType getType() { + return type; + } + public Line getLine() { return line; } diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/options/StageOption.java b/core/src/main/java/fr/skytasul/quests/api/stages/options/StageOption.java index 28b4d766..c132cd48 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/options/StageOption.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/options/StageOption.java @@ -25,8 +25,8 @@ public Class getStageClass() { @Override public abstract StageOption clone(); - public abstract boolean shouldSave(); - public abstract void startEdition(StageCreation creation); + public abstract boolean shouldSave(); + } diff --git a/core/src/main/java/fr/skytasul/quests/gui/creation/stages/Line.java b/core/src/main/java/fr/skytasul/quests/gui/creation/stages/Line.java index d0766821..d3a803ca 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/creation/stages/Line.java +++ b/core/src/main/java/fr/skytasul/quests/gui/creation/stages/Line.java @@ -37,8 +37,8 @@ public boolean isEmpty() { * @param is the item * @param click the action when a click on the item (if null nothing happens) (runnable parameter is the player) */ - public void setItem(int slot, ItemStack is, StageRunnable click){ - setItem(slot, is, click, false, true); + public int setItem(int slot, ItemStack is, StageRunnable click) { + return setItem(slot, is, click, false, true); } /** @@ -49,13 +49,13 @@ public void setItem(int slot, ItemStack is, StageRunnable click){ * @param override override if any other item is on the selected slot deprecated * @param refresh refresh all items (display this item) */ - public void setItem(int slot, ItemStack is, StageRunnable click, boolean override, boolean refresh){ + public int setItem(int slot, ItemStack is, StageRunnable click, boolean override, boolean refresh) { Pair en = new Pair<>(is, click); if (override){ items.set(slot, en); }else { if (items.get(slot) != null){ - items.add(en); + slot = items.add(en); }else items.set(slot, en); } if (items.getLast() <= 8) { @@ -65,6 +65,7 @@ public void setItem(int slot, ItemStack is, StageRunnable click, boolean overrid activePage = 0; setItems(activePage); } + return slot; } /** diff --git a/core/src/main/java/fr/skytasul/quests/requirements/ClassRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/ClassRequirement.java index 86547926..f8416d19 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/ClassRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/ClassRequirement.java @@ -106,13 +106,13 @@ public AbstractRequirement clone() { } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { if (!classes.isEmpty()) section.set("classes", classes.stream().map(RPGClass::getName).collect(Collectors.toList())); } @Override - protected void load(ConfigurationSection section) { + public void load(ConfigurationSection section) { if (!section.contains("classes")) return; for (String s : section.getStringList("classes")) { RPGClass classe = com.sucy.skill.SkillAPI.getClasses().get(s.toLowerCase()); diff --git a/core/src/main/java/fr/skytasul/quests/requirements/FactionRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/FactionRequirement.java index 70ef5e8d..1b856292 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/FactionRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/FactionRequirement.java @@ -102,12 +102,12 @@ public AbstractRequirement clone() { } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { section.set("factions", factions.stream().map(Faction::getId).collect(Collectors.toList())); } @Override - protected void load(ConfigurationSection section) { + public void load(ConfigurationSection section) { for (String s : section.getStringList("factions")) { if (!FactionColl.get().containsId(s)) { BeautyQuests.getInstance().getLogger().warning("Faction with ID " + s + " no longer exists."); diff --git a/core/src/main/java/fr/skytasul/quests/requirements/JobLevelRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/JobLevelRequirement.java index e8311036..bbb71d88 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/JobLevelRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/JobLevelRequirement.java @@ -73,13 +73,13 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { super.save(section); section.set("jobName", jobName); } @Override - protected void load(ConfigurationSection section) { + public void load(ConfigurationSection section) { super.load(section); jobName = section.getString("jobName"); } diff --git a/core/src/main/java/fr/skytasul/quests/requirements/McMMOSkillRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/McMMOSkillRequirement.java index bd35b216..78f04261 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/McMMOSkillRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/McMMOSkillRequirement.java @@ -66,13 +66,13 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { super.save(section); section.set("skillName", skillName); } @Override - protected void load(ConfigurationSection section) { + public void load(ConfigurationSection section) { super.load(section); skillName = section.getString("skillName"); } diff --git a/core/src/main/java/fr/skytasul/quests/requirements/MoneyRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/MoneyRequirement.java index 2c57d053..8f02e7bb 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/MoneyRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/MoneyRequirement.java @@ -65,12 +65,12 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { section.set("money", money); } @Override - protected void load(ConfigurationSection section) { + public void load(ConfigurationSection section) { money = section.getDouble("money"); } diff --git a/core/src/main/java/fr/skytasul/quests/requirements/PermissionsRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/PermissionsRequirement.java index 4ee19bb7..dbedccb7 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/PermissionsRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/PermissionsRequirement.java @@ -88,13 +88,13 @@ public AbstractRequirement clone() { } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { section.set("permissions", permissions.stream().map(Permission::toString).collect(Collectors.toList())); if (message != null) section.set("message", message); } @Override - protected void load(ConfigurationSection section) { + public void load(ConfigurationSection section) { permissions = section.getStringList("permissions").stream().map(Permission::fromString).collect(Collectors.toList()); if (section.contains("message")) message = section.getString("message"); } diff --git a/core/src/main/java/fr/skytasul/quests/requirements/PlaceholderRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/PlaceholderRequirement.java index d51620fd..dfd2a1ae 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/PlaceholderRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/PlaceholderRequirement.java @@ -101,7 +101,7 @@ public String getValue(){ } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { section.set("placeholder", rawPlaceholder); section.set("value", value); section.set("comparison", comparison.name()); @@ -109,7 +109,7 @@ protected void save(ConfigurationSection section) { } @Override - protected void load(ConfigurationSection section){ + public void load(ConfigurationSection section){ setPlaceholder(section.getString("placeholder")); this.value = section.getString("value"); if (section.contains("comparison")) this.comparison = ComparisonMethod.valueOf(section.getString("comparison")); diff --git a/core/src/main/java/fr/skytasul/quests/requirements/QuestRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/QuestRequirement.java index 69db15e1..4974f278 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/QuestRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/QuestRequirement.java @@ -81,12 +81,12 @@ public AbstractRequirement clone() { } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { section.set("questID", questId); } @Override - protected void load(ConfigurationSection section) { + public void load(ConfigurationSection section) { questId = section.getInt("questID"); } diff --git a/core/src/main/java/fr/skytasul/quests/requirements/RegionRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/RegionRequirement.java index 4260016d..8465f76b 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/RegionRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/RegionRequirement.java @@ -81,13 +81,13 @@ public AbstractRequirement clone() { } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { section.set("world", worldName); section.set("region", regionName); } @Override - protected void load(ConfigurationSection section) { + public void load(ConfigurationSection section) { worldName = section.getString("world"); setRegionName(section.getString("region")); } diff --git a/core/src/main/java/fr/skytasul/quests/requirements/ScoreboardRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/ScoreboardRequirement.java index 6c91f6ff..e0ee7a89 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/ScoreboardRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/ScoreboardRequirement.java @@ -69,13 +69,13 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { super.save(section); section.set("objective", objectiveName); } @Override - protected void load(ConfigurationSection section) { + public void load(ConfigurationSection section) { super.load(section); setObjectiveName(section.getString("objective")); } diff --git a/core/src/main/java/fr/skytasul/quests/requirements/logical/LogicalOrRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/logical/LogicalOrRequirement.java index dcd47ba3..cb875f32 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/logical/LogicalOrRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/logical/LogicalOrRequirement.java @@ -63,12 +63,12 @@ public AbstractRequirement clone() { } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { section.set("requirements", SerializableObject.serializeList(requirements)); } @Override - protected void load(ConfigurationSection section) { + public void load(ConfigurationSection section) { requirements = SerializableObject.deserializeList(section.getMapList("requirements"), AbstractRequirement::deserialize); } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/CheckpointReward.java b/core/src/main/java/fr/skytasul/quests/rewards/CheckpointReward.java index 76d11572..e897d6f0 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/CheckpointReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/CheckpointReward.java @@ -70,12 +70,12 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { section.set("actions", SerializableObject.serializeList(actions)); } @Override - protected void load(ConfigurationSection section) { + public void load(ConfigurationSection section) { actions = SerializableObject.deserializeList(section.getMapList("actions"), AbstractReward::deserialize); } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/CommandReward.java b/core/src/main/java/fr/skytasul/quests/rewards/CommandReward.java index af8c35fa..5ce2931f 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/CommandReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/CommandReward.java @@ -83,12 +83,12 @@ public void finish(List objects) { } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { section.set("commands", Utils.serializeList(commands, Command::serialize)); } @Override - protected void load(ConfigurationSection section){ + public void load(ConfigurationSection section){ commands.addAll(Utils.deserializeList(section.getMapList("commands"), Command::deserialize)); } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/ItemReward.java b/core/src/main/java/fr/skytasul/quests/rewards/ItemReward.java index 40bd1a0d..54f29d97 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/ItemReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/ItemReward.java @@ -58,12 +58,12 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { section.set("items", Utils.serializeList(items, ItemStack::serialize)); } @Override - protected void load(ConfigurationSection section){ + public void load(ConfigurationSection section){ items.addAll(Utils.deserializeList(section.getMapList("items"), ItemStack::deserialize)); } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/MessageReward.java b/core/src/main/java/fr/skytasul/quests/rewards/MessageReward.java index b35b336d..3014306e 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/MessageReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/MessageReward.java @@ -51,12 +51,12 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { section.set("text", text); } @Override - protected void load(ConfigurationSection section) { + public void load(ConfigurationSection section) { text = section.getString("text"); } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/MoneyReward.java b/core/src/main/java/fr/skytasul/quests/rewards/MoneyReward.java index abc94649..093b56b9 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/MoneyReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/MoneyReward.java @@ -60,12 +60,12 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { section.set("money", money); } @Override - protected void load(ConfigurationSection section) { + public void load(ConfigurationSection section) { money = section.getDouble("money"); } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/PermissionReward.java b/core/src/main/java/fr/skytasul/quests/rewards/PermissionReward.java index 52601f7e..6c75f2a9 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/PermissionReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/PermissionReward.java @@ -53,12 +53,12 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { section.set("perms", Utils.serializeList(permissions, Permission::serialize)); } @Override - protected void load(ConfigurationSection section){ + public void load(ConfigurationSection section){ permissions.addAll(Utils.deserializeList(section.getMapList("perms"), Permission::deserialize)); } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/RandomReward.java b/core/src/main/java/fr/skytasul/quests/rewards/RandomReward.java index 867ec87d..ab3c0b95 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/RandomReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/RandomReward.java @@ -116,14 +116,14 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { section.set("rewards", SerializableObject.serializeList(rewards)); section.set("min", min); section.set("max", max); } @Override - protected void load(ConfigurationSection section) { + public void load(ConfigurationSection section) { rewards = SerializableObject.deserializeList(section.getMapList("rewards"), AbstractReward::deserialize); setMinMax(section.getInt("min"), section.getInt("max")); } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/RemoveItemsReward.java b/core/src/main/java/fr/skytasul/quests/rewards/RemoveItemsReward.java index 4c1f2198..bac384ff 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/RemoveItemsReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/RemoveItemsReward.java @@ -80,13 +80,13 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { section.set("items", Utils.serializeList(items, ItemStack::serialize)); if (!comparisons.getNotDefault().isEmpty()) section.createSection("comparisons", comparisons.getNotDefault()); } @Override - protected void load(ConfigurationSection section){ + public void load(ConfigurationSection section){ items.addAll(Utils.deserializeList(section.getMapList("items"), ItemStack::deserialize)); if (section.contains("comparisons")) comparisons.setNotDefaultComparisons(section.getConfigurationSection("comparisons")); } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/RequirementDependentReward.java b/core/src/main/java/fr/skytasul/quests/rewards/RequirementDependentReward.java index cafbf7fd..2a43f023 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/RequirementDependentReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/RequirementDependentReward.java @@ -139,13 +139,13 @@ public boolean onClick(Player p, Inventory inv, ItemStack current, int slot, Cli } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { section.set("requirements", SerializableObject.serializeList(requirements)); section.set("rewards", SerializableObject.serializeList(rewards)); } @Override - protected void load(ConfigurationSection section) { + public void load(ConfigurationSection section) { requirements = SerializableObject.deserializeList(section.getMapList("requirements"), AbstractRequirement::deserialize); rewards = SerializableObject.deserializeList(section.getMapList("rewards"), AbstractReward::deserialize); } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/TeleportationReward.java b/core/src/main/java/fr/skytasul/quests/rewards/TeleportationReward.java index bd49e130..9f32284f 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/TeleportationReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/TeleportationReward.java @@ -53,12 +53,12 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { section.set("tp", teleportation.serialize()); } @Override - protected void load(ConfigurationSection section) { + public void load(ConfigurationSection section) { teleportation = Location.deserialize(section.getConfigurationSection("tp").getValues(false)); } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/TitleReward.java b/core/src/main/java/fr/skytasul/quests/rewards/TitleReward.java index efbeaa39..0182fb3b 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/TitleReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/TitleReward.java @@ -49,12 +49,12 @@ public AbstractReward clone() { } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { if (title != null) title.serialize(section.createSection("title")); } @Override - protected void load(ConfigurationSection section) { + public void load(ConfigurationSection section) { title = section.contains("title") ? Title.deserialize(section.getConfigurationSection("title")) : null; } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/WaitReward.java b/core/src/main/java/fr/skytasul/quests/rewards/WaitReward.java index b82141a6..21aae546 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/WaitReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/WaitReward.java @@ -62,12 +62,12 @@ public AbstractReward clone() { } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { section.set("delay", delay); } @Override - protected void load(ConfigurationSection section) { + public void load(ConfigurationSection section) { delay = section.getInt("delay"); } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/XPReward.java b/core/src/main/java/fr/skytasul/quests/rewards/XPReward.java index 2ff6d25b..cd8f6b36 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/XPReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/XPReward.java @@ -64,12 +64,12 @@ public void itemClick(QuestObjectClickEvent event) { } @Override - protected void save(ConfigurationSection section) { + public void save(ConfigurationSection section) { section.set("xp", exp); } @Override - protected void load(ConfigurationSection section) { + public void load(ConfigurationSection section) { exp = section.getInt("xp"); } From f2b8cfa3aaf8b72b441f6595870d585c3fcd9a42 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Thu, 19 May 2022 21:46:36 +0200 Subject: [PATCH 009/111] Locatable hashcode/equals --- .../quests/api/stages/StageCreation.java | 2 +- .../quests/api/stages/types/Locatable.java | 62 +++++++++++++++++++ .../skytasul/quests/rewards/RandomReward.java | 5 +- .../skytasul/quests/utils/types/BQBlock.java | 1 + 4 files changed, 67 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/StageCreation.java b/core/src/main/java/fr/skytasul/quests/api/stages/StageCreation.java index 536165ad..45eaba72 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/StageCreation.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/StageCreation.java @@ -167,7 +167,7 @@ public void edit(T stage) { setStartMessage(stage.getStartMessage()); setCustomDescription(stage.getCustomText()); - options = stage.getOptions().stream().map(StageOption::clone).collect(Collectors.toList()); + options = stage.getOptions().stream().map(StageOption::clone).map(x -> (StageOption) x).collect(Collectors.toList()); options.forEach(option -> option.startEdition(this)); } diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java index 7444a340..e2f62506 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java @@ -12,6 +12,10 @@ default boolean isShown() { return true; } + default boolean canBeFetchedAsynchronously() { + return true; + } + interface PreciseLocatable extends Locatable { Located getLocated(); @@ -30,6 +34,38 @@ interface NearbyFetcher { int getMaxAmount(); + static NearbyFetcher create(Location location, double maxDistance, int maxAmount) { + return new NearbyFetcherImpl(location, maxDistance, maxAmount); + } + + class NearbyFetcherImpl implements NearbyFetcher { + private Location center; + private double maxDistance; + private int maxAmount; + + public NearbyFetcherImpl(Location center, double maxDistance, int maxAmount) { + this.center = center; + this.maxDistance = maxDistance; + this.maxAmount = maxAmount; + } + + @Override + public Location getCenter() { + return center; + } + + @Override + public double getMaxDistance() { + return maxDistance; + } + + @Override + public int getMaxAmount() { + return maxAmount; + } + + } + } } @@ -53,6 +89,19 @@ public LocatedImpl(Location location) { public Location getLocation() { return location; } + + @Override + public int hashCode() { + return location.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof LocatedImpl)) return false; + LocatedImpl other = (LocatedImpl) obj; + return other.location.equals(location); + } + } interface LocatedEntity extends Located { @@ -80,6 +129,19 @@ public LocatedEntityImpl(Entity entity) { public Entity getEntity() { return entity; } + + @Override + public int hashCode() { + return entity.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof LocatedEntityImpl)) return false; + LocatedEntityImpl other = (LocatedEntityImpl) obj; + return other.entity.equals(entity); + } + } } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/RandomReward.java b/core/src/main/java/fr/skytasul/quests/rewards/RandomReward.java index ab3c0b95..03755812 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/RandomReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/RandomReward.java @@ -31,7 +31,8 @@ public RandomReward() { public RandomReward(List rewards, int min, int max) { this.rewards = rewards; - setMinMax(min, max); + this.min = min; + this.max = max; } public void setMinMax(int min, int max) { @@ -39,7 +40,7 @@ public void setMinMax(int min, int max) { this.max = Math.max(min, max); if (max > rewards.size()) - BeautyQuests.logger.warning("Random reward with max amount (" + max + ") greater than amount of rewards available (" + rewards.size() + ")"); + BeautyQuests.logger.warning("Random reward with max amount (" + max + ") greater than amount of rewards available (" + rewards.size() + ") in " + debugName()); } @Override diff --git a/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java b/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java index 33529adf..b96047b5 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java +++ b/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java @@ -55,6 +55,7 @@ public static Collection getNearbyBlocks(Locatable.MultipleLo double maxZ = fetcher.getCenter().getZ() + fetcher.getMaxDistance(); for (int x = minX; x <= maxX; x++) { for (int z = minZ; z <= maxZ; z++) { + if (!fetcher.getCenter().getWorld().isChunkLoaded(x >> 4, z >> 4)) continue; for (int y = minY; y <= maxY; y++) { Block blockAt = fetcher.getCenter().getWorld().getBlockAt(x, y, z); if (types.stream().anyMatch(type -> type.applies(blockAt))) { From c05b0fe7cfb522eea2d802c0a1c2222bf92695ca Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Fri, 20 May 2022 15:26:58 +0200 Subject: [PATCH 010/111] Fixed several issues --- .../api/comparison/ItemComparisonMap.java | 2 ++ .../api/stages/types/AbstractEntityStage.java | 5 ++++ .../gui/creation/QuestCreationSession.java | 2 +- .../quests/gui/creation/stages/StagesGUI.java | 15 ++++++----- .../skytasul/quests/stages/StageInteract.java | 15 ++++++++--- .../skytasul/quests/utils/ParticleEffect.java | 4 +-- .../fr/skytasul/quests/utils/nms/NMS.java | 4 +-- .../fr/skytasul/quests/utils/nms/NullNMS.java | 5 ++-- .../skytasul/quests/utils/types/BQBlock.java | 2 +- .../quests/utils/types/BQLocation.java | 26 +++++++++++-------- .../skytasul/quests/utils/nms/v1_10_R1.java | 8 +++--- .../skytasul/quests/utils/nms/v1_11_R1.java | 8 +++--- .../skytasul/quests/utils/nms/v1_12_R1.java | 4 +-- .../skytasul/quests/utils/nms/v1_13_R2.java | 4 +-- .../skytasul/quests/utils/nms/v1_14_R1.java | 4 +-- .../skytasul/quests/utils/nms/v1_15_R1.java | 4 +-- .../skytasul/quests/utils/nms/v1_16_R1.java | 4 +-- .../skytasul/quests/utils/nms/v1_16_R2.java | 4 +-- .../skytasul/quests/utils/nms/v1_16_R3.java | 4 +-- .../skytasul/quests/utils/nms/v1_17_R1.java | 4 +-- .../skytasul/quests/utils/nms/v1_18_R1.java | 4 +-- .../skytasul/quests/utils/nms/v1_18_R2.java | 4 +-- .../fr/skytasul/quests/utils/nms/v1_9_R1.java | 8 +++--- .../fr/skytasul/quests/utils/nms/v1_9_R2.java | 8 +++--- 24 files changed, 86 insertions(+), 66 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/api/comparison/ItemComparisonMap.java b/core/src/main/java/fr/skytasul/quests/api/comparison/ItemComparisonMap.java index e38a15ff..ac6ca4ef 100644 --- a/core/src/main/java/fr/skytasul/quests/api/comparison/ItemComparisonMap.java +++ b/core/src/main/java/fr/skytasul/quests/api/comparison/ItemComparisonMap.java @@ -30,6 +30,8 @@ public ItemComparisonMap(Map notDefault) { } public void setNotDefaultComparisons(ConfigurationSection section) { + this.notDefault = (Map) section.getValues(false); + effective = new ArrayList<>(); for (ItemComparison comp : QuestsAPI.itemComparisons) { if (section.getBoolean(comp.getID(), comp.isEnabledByDefault())) effective.add(comp); diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java index 8fd46fed..dfe7039c 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java @@ -86,6 +86,11 @@ protected Supplier[] descriptionFormat(PlayerAccount acc, Source source) return new Supplier[] { () -> getMobsLeft(acc) }; } + @Override + public boolean canBeFetchedAsynchronously() { + return false; + } + @Override public Collection getNearbyLocated(NearbyFetcher fetcher) { double distanceSquared = fetcher.getMaxDistance() * fetcher.getMaxDistance(); diff --git a/core/src/main/java/fr/skytasul/quests/gui/creation/QuestCreationSession.java b/core/src/main/java/fr/skytasul/quests/gui/creation/QuestCreationSession.java index bb28ec34..78414bf8 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/creation/QuestCreationSession.java +++ b/core/src/main/java/fr/skytasul/quests/gui/creation/QuestCreationSession.java @@ -49,7 +49,7 @@ public void setStagesEdited() { } public boolean areStagesEdited() { - return stagesEdited; + return isEdition() && stagesEdited; } public StagesGUI getMainGUI() { diff --git a/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java b/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java index de4e23d9..45916272 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java @@ -104,10 +104,10 @@ private void setStageCreate(Line line, boolean branches){ line.setItem(0, stageCreate.clone(), (p, item) -> { line.setItem(0, null, null, true, false); int i = 0; - for (StageType creator : QuestsAPI.getStages()) { - if (creator.isValid()) { - line.setItem(++i, creator.getItem(), (p1, item1) -> { - runClick(line, creator, branches).start(p1); + for (StageType type : QuestsAPI.getStages()) { + if (type.isValid()) { + line.setItem(++i, type.getItem(), (p1, item1) -> { + runClick(line, type, branches).start(p1); }, true, false); } } @@ -127,15 +127,16 @@ private void updateLineManageLore(Line line) { line.editItem(0, ItemUtils.lore(line.getItem(0), getLineManageLore(line.getLine()))); } - private StageCreation runClick(Line line, StageType creator, boolean branches) { + private StageCreation runClick(Line line, StageType type, boolean branches) { line.removeItems(); - StageCreation creation = creator.getCreationSupplier().supply(line, branches); + StageCreation creation = type.getCreationSupplier().supply(line, branches); line.creation = creation; + creation.setup((StageType) type); inv.setItem(SLOT_FINISH, ItemUtils.itemDone); int maxStages = branches ? 20 : 15; - ItemStack manageItem = ItemUtils.item(XMaterial.BARRIER, Lang.stageType.format(creator.getName()), getLineManageLore(line.getLine())); + ItemStack manageItem = ItemUtils.item(XMaterial.BARRIER, Lang.stageType.format(type.getName()), getLineManageLore(line.getLine())); line.setItem(0, manageItem, new StageRunnableClick() { @Override public void run(Player p, ItemStack item, ClickType click) { diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java b/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java index 78c6fd06..99fe7642 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java @@ -35,20 +35,27 @@ public class StageInteract extends AbstractStage implements Locatable.MultipleLocatable, Locatable.PreciseLocatable { - private boolean left; - private BQLocation lc; - private BQBlock block; + private final boolean left; + private final BQLocation lc; + private final Located.LocatedBlock locatedBlock; + private final BQBlock block; public StageInteract(QuestBranch branch, boolean leftClick, BQLocation location) { super(branch); this.left = leftClick; this.lc = new BQLocation(location.getBlock().getLocation()); + this.locatedBlock = Located.LocatedBlock.create(lc); + + this.block = null; } public StageInteract(QuestBranch branch, boolean leftClick, BQBlock block) { super(branch); this.left = leftClick; this.block = block; + + this.lc = null; + this.locatedBlock = null; } public BQLocation getLocation() { @@ -65,7 +72,7 @@ public boolean needLeftClick(){ @Override public Located getLocated() { - return lc; + return locatedBlock; } @Override diff --git a/core/src/main/java/fr/skytasul/quests/utils/ParticleEffect.java b/core/src/main/java/fr/skytasul/quests/utils/ParticleEffect.java index 3c3052d4..5cb171ab 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/ParticleEffect.java +++ b/core/src/main/java/fr/skytasul/quests/utils/ParticleEffect.java @@ -7,7 +7,7 @@ import org.bukkit.Location; import org.bukkit.Particle; import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import fr.skytasul.quests.utils.compatibility.Post1_13; @@ -44,7 +44,7 @@ public String toString() { return type.particle.name() + " in shape " + shape.name() + (type.colored ? " with color \"R" + (type.particle != Particle.NOTE ? color.getRed() + " G" + color.getGreen() + " B" + color.getBlue() : "random") + "\"" : ""); } - public void send(LivingEntity entity, List players) { + public void send(Entity entity, List players) { send(entity.getLocation(), NMS.getNMS().entityNameplateHeight(entity), players); } diff --git a/core/src/main/java/fr/skytasul/quests/utils/nms/NMS.java b/core/src/main/java/fr/skytasul/quests/utils/nms/NMS.java index e6abbf6c..3c312c82 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/nms/NMS.java +++ b/core/src/main/java/fr/skytasul/quests/utils/nms/NMS.java @@ -7,7 +7,7 @@ import org.bukkit.Bukkit; import org.bukkit.Material; -import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.inventory.meta.ItemMeta; @@ -40,7 +40,7 @@ public NMS() { public abstract Object bookPacket(ByteBuf buf); - public abstract double entityNameplateHeight(LivingEntity en); // can be remplaced by Entity.getHeight from 1.11 + public abstract double entityNameplateHeight(Entity en); // can be remplaced by Entity.getHeight from 1.11 public List getAvailableBlockProperties(Material material){ throw new UnsupportedOperationException(); diff --git a/core/src/main/java/fr/skytasul/quests/utils/nms/NullNMS.java b/core/src/main/java/fr/skytasul/quests/utils/nms/NullNMS.java index c18bea22..7ff8d9c1 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/nms/NullNMS.java +++ b/core/src/main/java/fr/skytasul/quests/utils/nms/NullNMS.java @@ -1,5 +1,6 @@ package fr.skytasul.quests.utils.nms; +import org.bukkit.entity.Entity; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; import org.bukkit.inventory.meta.ItemMeta; @@ -30,8 +31,8 @@ public ReflectUtils getReflect(){ } @Override - public double entityNameplateHeight(LivingEntity en){ - return en.getEyeHeight() + 1; + public double entityNameplateHeight(Entity en){ + return en instanceof LivingEntity ? ((LivingEntity) en).getEyeHeight() + 1 : 1; } public Object getIChatBaseComponent(String text){ diff --git a/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java b/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java index b96047b5..0f37e778 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java +++ b/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java @@ -51,7 +51,7 @@ public static Collection getNearbyBlocks(Locatable.MultipleLo int minY = (int) Math.max(fetcher.getCenter().getWorld().getMinHeight(), fetcher.getCenter().getY() - fetcher.getMaxDistance()); int minZ = (int) (fetcher.getCenter().getZ() - fetcher.getMaxDistance()); double maxX = fetcher.getCenter().getX() + fetcher.getMaxDistance(); - double maxY = Math.min(fetcher.getCenter().getWorld().getMinHeight(), fetcher.getCenter().getY() + fetcher.getMaxDistance()); + double maxY = Math.min(fetcher.getCenter().getWorld().getMaxHeight(), fetcher.getCenter().getY() + fetcher.getMaxDistance()); double maxZ = fetcher.getCenter().getZ() + fetcher.getMaxDistance(); for (int x = minX; x <= maxX; x++) { for (int z = minZ; z <= maxZ; z++) { diff --git a/core/src/main/java/fr/skytasul/quests/utils/types/BQLocation.java b/core/src/main/java/fr/skytasul/quests/utils/types/BQLocation.java index ff2afd97..3adbee85 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/types/BQLocation.java +++ b/core/src/main/java/fr/skytasul/quests/utils/types/BQLocation.java @@ -56,7 +56,7 @@ public void setWorld(World world) { @Override public Location getLocation() { - return this; + return new Location(getWorld(), getX(), getY(), getZ()); } public boolean isWorld(World world) { @@ -80,24 +80,28 @@ public double distanceSquared(Location o) { @Override public boolean equals(Object obj) { if (!(obj instanceof Location)) return false; + Location other = (Location) obj; + + if (Double.doubleToLongBits(this.getX()) != Double.doubleToLongBits(other.getX())) return false; + if (Double.doubleToLongBits(this.getY()) != Double.doubleToLongBits(other.getY())) return false; + if (Double.doubleToLongBits(this.getZ()) != Double.doubleToLongBits(other.getZ())) return false; + if (Float.floatToIntBits(this.getPitch()) != Float.floatToIntBits(other.getPitch())) return false; + if (Float.floatToIntBits(this.getYaw()) != Float.floatToIntBits(other.getYaw())) return false; if (obj instanceof BQLocation) { - if (!super.equals(obj)) return false; - BQLocation other = (BQLocation) obj; - if (worldPattern == null) return other.worldPattern == null; - if (other.worldPattern == null) return false; - return worldPattern.pattern().equals(other.worldPattern.pattern()); + BQLocation otherBQ = (BQLocation) obj; + if (worldPattern == null) return otherBQ.worldPattern == null; + if (otherBQ.worldPattern == null) return false; + return worldPattern.pattern().equals(otherBQ.worldPattern.pattern()); } - Location other = (Location) obj; if (!Objects.equals(other.getWorld(), getWorld())) { if (other.getWorld() == null) return false; if (worldPattern == null) return false; - if (!worldPattern.matcher(other.getWorld().getName()).matches()) return false; - other = other.clone(); - other.setWorld(null); + return worldPattern.matcher(other.getWorld().getName()).matches(); } - return super.equals(other); + + return true; } @Override diff --git a/v1_10_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_10_R1.java b/v1_10_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_10_R1.java index 0b1f2092..d2ee06c2 100644 --- a/v1_10_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_10_R1.java +++ b/v1_10_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_10_R1.java @@ -1,9 +1,9 @@ package fr.skytasul.quests.utils.nms; import org.apache.commons.lang.Validate; -import org.bukkit.craftbukkit.v1_10_R1.entity.CraftLivingEntity; +import org.bukkit.craftbukkit.v1_10_R1.entity.CraftEntity; import org.bukkit.craftbukkit.v1_10_R1.entity.CraftPlayer; -import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import net.minecraft.server.v1_10_R1.EnumChatFormat; @@ -28,8 +28,8 @@ public void sendPacket(Player p, Object packet){ } @Override - public double entityNameplateHeight(LivingEntity en){ - return ((CraftLivingEntity) en).getHandle().length; + public double entityNameplateHeight(Entity en){ + return ((CraftEntity) en).getHandle().length; } public Object getIChatBaseComponent(String text){ diff --git a/v1_11_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_11_R1.java b/v1_11_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_11_R1.java index 4d8e6950..eb6746a0 100644 --- a/v1_11_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_11_R1.java +++ b/v1_11_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_11_R1.java @@ -1,9 +1,9 @@ package fr.skytasul.quests.utils.nms; import org.apache.commons.lang.Validate; -import org.bukkit.craftbukkit.v1_11_R1.entity.CraftLivingEntity; +import org.bukkit.craftbukkit.v1_11_R1.entity.CraftEntity; import org.bukkit.craftbukkit.v1_11_R1.entity.CraftPlayer; -import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import net.minecraft.server.v1_11_R1.EnumChatFormat; @@ -28,8 +28,8 @@ public void sendPacket(Player p, Object packet){ } @Override - public double entityNameplateHeight(LivingEntity en){ - return ((CraftLivingEntity) en).getHandle().length; + public double entityNameplateHeight(Entity en){ + return ((CraftEntity) en).getHandle().length; } public Object getIChatBaseComponent(String text){ diff --git a/v1_12_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_12_R1.java b/v1_12_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_12_R1.java index 6baf3abb..4fef5f08 100644 --- a/v1_12_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_12_R1.java +++ b/v1_12_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_12_R1.java @@ -2,7 +2,7 @@ import org.apache.commons.lang.Validate; import org.bukkit.craftbukkit.v1_12_R1.entity.CraftPlayer; -import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import net.minecraft.server.v1_12_R1.EnumChatFormat; @@ -27,7 +27,7 @@ public void sendPacket(Player p, Object packet){ } @Override - public double entityNameplateHeight(LivingEntity en){ + public double entityNameplateHeight(Entity en){ return en.getHeight(); } diff --git a/v1_13_R2/src/main/java/fr/skytasul/quests/utils/nms/v1_13_R2.java b/v1_13_R2/src/main/java/fr/skytasul/quests/utils/nms/v1_13_R2.java index acaf0d76..d9bdd2ca 100644 --- a/v1_13_R2/src/main/java/fr/skytasul/quests/utils/nms/v1_13_R2.java +++ b/v1_13_R2/src/main/java/fr/skytasul/quests/utils/nms/v1_13_R2.java @@ -6,7 +6,7 @@ import org.apache.commons.lang.Validate; import org.bukkit.Material; import org.bukkit.craftbukkit.v1_13_R2.entity.CraftPlayer; -import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import net.minecraft.server.v1_13_R2.*; @@ -27,7 +27,7 @@ public void sendPacket(Player p, Object packet){ } @Override - public double entityNameplateHeight(LivingEntity en){ + public double entityNameplateHeight(Entity en){ return en.getHeight(); } diff --git a/v1_14_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_14_R1.java b/v1_14_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_14_R1.java index 6d33ee24..d70d3ba8 100644 --- a/v1_14_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_14_R1.java +++ b/v1_14_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_14_R1.java @@ -6,7 +6,7 @@ import org.apache.commons.lang.Validate; import org.bukkit.Material; import org.bukkit.craftbukkit.v1_14_R1.entity.CraftPlayer; -import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import net.minecraft.server.v1_14_R1.*; @@ -27,7 +27,7 @@ public void sendPacket(Player p, Object packet){ } @Override - public double entityNameplateHeight(LivingEntity en){ + public double entityNameplateHeight(Entity en){ return en.getHeight(); } diff --git a/v1_15_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_15_R1.java b/v1_15_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_15_R1.java index 7887e0f7..d6dcfd66 100644 --- a/v1_15_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_15_R1.java +++ b/v1_15_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_15_R1.java @@ -6,7 +6,7 @@ import org.apache.commons.lang.Validate; import org.bukkit.Material; import org.bukkit.craftbukkit.v1_15_R1.entity.CraftPlayer; -import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import net.minecraft.server.v1_15_R1.*; @@ -27,7 +27,7 @@ public void sendPacket(Player p, Object packet){ } @Override - public double entityNameplateHeight(LivingEntity en){ + public double entityNameplateHeight(Entity en){ return en.getHeight(); } diff --git a/v1_16_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_16_R1.java b/v1_16_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_16_R1.java index e79b1659..b8985f5e 100644 --- a/v1_16_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_16_R1.java +++ b/v1_16_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_16_R1.java @@ -6,7 +6,7 @@ import org.apache.commons.lang.Validate; import org.bukkit.Material; import org.bukkit.craftbukkit.v1_16_R1.entity.CraftPlayer; -import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import net.minecraft.server.v1_16_R1.*; @@ -27,7 +27,7 @@ public void sendPacket(Player p, Object packet){ } @Override - public double entityNameplateHeight(LivingEntity en){ + public double entityNameplateHeight(Entity en){ return en.getHeight(); } diff --git a/v1_16_R2/src/main/java/fr/skytasul/quests/utils/nms/v1_16_R2.java b/v1_16_R2/src/main/java/fr/skytasul/quests/utils/nms/v1_16_R2.java index 7a5d77f2..f14d7985 100644 --- a/v1_16_R2/src/main/java/fr/skytasul/quests/utils/nms/v1_16_R2.java +++ b/v1_16_R2/src/main/java/fr/skytasul/quests/utils/nms/v1_16_R2.java @@ -6,7 +6,7 @@ import org.apache.commons.lang.Validate; import org.bukkit.Material; import org.bukkit.craftbukkit.v1_16_R2.entity.CraftPlayer; -import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import net.minecraft.server.v1_16_R2.*; @@ -27,7 +27,7 @@ public void sendPacket(Player p, Object packet){ } @Override - public double entityNameplateHeight(LivingEntity en){ + public double entityNameplateHeight(Entity en){ return en.getHeight(); } diff --git a/v1_16_R3/src/main/java/fr/skytasul/quests/utils/nms/v1_16_R3.java b/v1_16_R3/src/main/java/fr/skytasul/quests/utils/nms/v1_16_R3.java index 59e26127..5a97c2bb 100644 --- a/v1_16_R3/src/main/java/fr/skytasul/quests/utils/nms/v1_16_R3.java +++ b/v1_16_R3/src/main/java/fr/skytasul/quests/utils/nms/v1_16_R3.java @@ -6,7 +6,7 @@ import org.apache.commons.lang.Validate; import org.bukkit.Material; import org.bukkit.craftbukkit.v1_16_R3.entity.CraftPlayer; -import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import net.minecraft.server.v1_16_R3.*; @@ -27,7 +27,7 @@ public void sendPacket(Player p, Object packet){ } @Override - public double entityNameplateHeight(LivingEntity en){ + public double entityNameplateHeight(Entity en){ return en.getHeight(); } diff --git a/v1_17_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_17_R1.java b/v1_17_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_17_R1.java index 276d27ec..821d122e 100644 --- a/v1_17_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_17_R1.java +++ b/v1_17_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_17_R1.java @@ -5,7 +5,7 @@ import org.apache.commons.lang.Validate; import org.bukkit.Material; import org.bukkit.craftbukkit.v1_17_R1.entity.CraftPlayer; -import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import net.minecraft.core.Registry; @@ -35,7 +35,7 @@ public void sendPacket(Player p, Object packet){ } @Override - public double entityNameplateHeight(LivingEntity en){ + public double entityNameplateHeight(Entity en){ return en.getHeight(); } diff --git a/v1_18_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_18_R1.java b/v1_18_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_18_R1.java index b8138333..c46b41cc 100644 --- a/v1_18_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_18_R1.java +++ b/v1_18_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_18_R1.java @@ -5,7 +5,7 @@ import org.apache.commons.lang.Validate; import org.bukkit.Material; import org.bukkit.craftbukkit.v1_18_R1.entity.CraftPlayer; -import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import net.minecraft.core.Registry; @@ -35,7 +35,7 @@ public void sendPacket(Player p, Object packet){ } @Override - public double entityNameplateHeight(LivingEntity en){ + public double entityNameplateHeight(Entity en){ return en.getHeight(); } diff --git a/v1_18_R2/src/main/java/fr/skytasul/quests/utils/nms/v1_18_R2.java b/v1_18_R2/src/main/java/fr/skytasul/quests/utils/nms/v1_18_R2.java index 256c8f6f..1d196111 100644 --- a/v1_18_R2/src/main/java/fr/skytasul/quests/utils/nms/v1_18_R2.java +++ b/v1_18_R2/src/main/java/fr/skytasul/quests/utils/nms/v1_18_R2.java @@ -5,7 +5,7 @@ import org.apache.commons.lang.Validate; import org.bukkit.Material; import org.bukkit.craftbukkit.v1_18_R2.entity.CraftPlayer; -import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import net.minecraft.core.Registry; @@ -34,7 +34,7 @@ public void sendPacket(Player p, Object packet){ } @Override - public double entityNameplateHeight(LivingEntity en){ + public double entityNameplateHeight(Entity en){ return en.getHeight(); } diff --git a/v1_9_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_9_R1.java b/v1_9_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_9_R1.java index 418e92cb..bdddae93 100644 --- a/v1_9_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_9_R1.java +++ b/v1_9_R1/src/main/java/fr/skytasul/quests/utils/nms/v1_9_R1.java @@ -1,9 +1,9 @@ package fr.skytasul.quests.utils.nms; import org.apache.commons.lang.Validate; -import org.bukkit.craftbukkit.v1_9_R1.entity.CraftLivingEntity; +import org.bukkit.craftbukkit.v1_9_R1.entity.CraftEntity; import org.bukkit.craftbukkit.v1_9_R1.entity.CraftPlayer; -import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import net.minecraft.server.v1_9_R1.EnumChatFormat; @@ -28,8 +28,8 @@ public void sendPacket(Player p, Object packet){ } @Override - public double entityNameplateHeight(LivingEntity en){ - return ((CraftLivingEntity) en).getHandle().length; + public double entityNameplateHeight(Entity en){ + return ((CraftEntity) en).getHandle().length; } public Object getIChatBaseComponent(String text){ diff --git a/v1_9_R2/src/main/java/fr/skytasul/quests/utils/nms/v1_9_R2.java b/v1_9_R2/src/main/java/fr/skytasul/quests/utils/nms/v1_9_R2.java index b90ef2fb..fe858742 100644 --- a/v1_9_R2/src/main/java/fr/skytasul/quests/utils/nms/v1_9_R2.java +++ b/v1_9_R2/src/main/java/fr/skytasul/quests/utils/nms/v1_9_R2.java @@ -1,9 +1,9 @@ package fr.skytasul.quests.utils.nms; import org.apache.commons.lang.Validate; -import org.bukkit.craftbukkit.v1_9_R2.entity.CraftLivingEntity; +import org.bukkit.craftbukkit.v1_9_R2.entity.CraftEntity; import org.bukkit.craftbukkit.v1_9_R2.entity.CraftPlayer; -import org.bukkit.entity.LivingEntity; +import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import net.minecraft.server.v1_9_R2.EnumChatFormat; @@ -28,8 +28,8 @@ public void sendPacket(Player p, Object packet){ } @Override - public double entityNameplateHeight(LivingEntity en){ - return ((CraftLivingEntity) en).getHandle().length; + public double entityNameplateHeight(Entity en){ + return ((CraftEntity) en).getHandle().length; } public Object getIChatBaseComponent(String text){ From b84f60266cbe56d14a345e270456c8b00aec8491 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 22 May 2022 10:58:57 +0200 Subject: [PATCH 011/111] Reworked locales * Extracted an interface from the Lang enum, which manages the way locales are stored in YAML files for future use --- .../java/fr/skytasul/quests/BeautyQuests.java | 57 +++-------- .../java/fr/skytasul/quests/api/Locale.java | 94 +++++++++++++++++++ .../api/stages/types/AbstractEntityStage.java | 2 +- .../java/fr/skytasul/quests/utils/Lang.java | 56 +++++------ .../quests/utils/types/DialogRunner.java | 2 +- core/src/main/resources/locales/en_US.yml | 11 +++ 6 files changed, 139 insertions(+), 83 deletions(-) create mode 100644 core/src/main/java/fr/skytasul/quests/api/Locale.java diff --git a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java index bbd5d13f..1f762dc7 100644 --- a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java +++ b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java @@ -2,9 +2,6 @@ import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -31,9 +28,9 @@ import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitRunnable; -import com.google.common.base.Charsets; import com.tchristofferson.configupdater.ConfigUpdater; +import fr.skytasul.quests.api.Locale; import fr.skytasul.quests.api.QuestsAPI; import fr.skytasul.quests.api.bossbar.BQBossBarImplementation; import fr.skytasul.quests.commands.Commands; @@ -328,7 +325,7 @@ private void loadConfigParameters(boolean init) throws LoadingException { config.getConfig().save(configFile); logger.info("Updated config."); } - if (init && loadLang() == null) return; + if (init) loadLang(); ConfigUpdater.update(this, "config.yml", configFile); config.init(); @@ -365,47 +362,10 @@ private void loadConfigParameters(boolean init) throws LoadingException { private YamlConfiguration loadLang() throws LoadingException { try { - for (String language : new String[] { "en_US", "fr_FR", "zh_CN", "zh_HK", "de_DE", "pt_PT", "it_IT", "es_ES", "sv_SE", "hu_HU", "ru_RU", "pl_PL", "th_TH", "lt_LT", "vi_VN" }) { - File file = new File(getDataFolder(), "locales/" + language + ".yml"); - if (!file.exists()) saveResource("locales/" + language + ".yml", false); - } - - long lastMillis = System.currentTimeMillis(); loadedLanguage = config.getConfig().getString("lang", "en_US"); - String language = "locales/" + loadedLanguage + ".yml"; - File file = new File(getDataFolder(), language); - InputStream res = getResource(language); - boolean created = false; - if (!file.exists()){ - logger.warning("Language file " + language + " does not exist. Using default english strings."); - file.createNewFile(); - res = getResource("locales/en_US.yml"); - created = true; - } - YamlConfiguration conf = YamlConfiguration.loadConfiguration(file); - boolean changes = false; - if (res != null){ // if it's a local resource - YamlConfiguration def = YamlConfiguration.loadConfiguration(new InputStreamReader(res, StandardCharsets.UTF_8)); - for (String key : def.getKeys(true)){ // get all keys in resource - if (!def.isConfigurationSection(key)){ // if not a block - if (!conf.contains(key)){ // if string does not exist in the file - conf.set(key, def.get(key)); // copy string - if (!created) DebugUtils.logMessage("String copied from source file to " + language + ". Key: " + key); - changes = true; - } - } - } - } - Lang.loadStrings(YamlConfiguration.loadConfiguration(new InputStreamReader(getResource("locales/en_US.yml"), Charsets.UTF_8)), conf); - - if (changes) { - getLogger().info("Copied new strings into " + language + " language file."); - conf.save(file); // if there has been changes before, save the edited file - } - getLogger().info("Loaded language file " + language + " (" + (((double) System.currentTimeMillis() - lastMillis) / 1000D) + "s)!"); - return conf; - } catch(Exception e) { - throw new LoadingException("Couldn't create language file.", e); + return Locale.loadLang(this, Lang.values(), loadedLanguage, "en_US", "fr_FR", "zh_CN", "zh_HK", "de_DE", "pt_PT", "it_IT", "es_ES", "sv_SE", "hu_HU", "ru_RU", "pl_PL", "th_TH", "lt_LT", "vi_VN"); + }catch (Exception ex) { + throw new LoadingException("Couldn't load language file.", ex); } } @@ -657,7 +617,7 @@ public static BeautyQuests getInstance(){ return instance; } - class LoadingException extends Exception { + public static class LoadingException extends Exception { private static final long serialVersionUID = -2811265488885752109L; private String loggerMessage; @@ -670,6 +630,11 @@ public LoadingException(String loggerMessage, Throwable cause) { super(cause); this.loggerMessage = loggerMessage; } + + public String getLoggerMessage() { + return loggerMessage; + } + } } diff --git a/core/src/main/java/fr/skytasul/quests/api/Locale.java b/core/src/main/java/fr/skytasul/quests/api/Locale.java new file mode 100644 index 00000000..77bbc064 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/api/Locale.java @@ -0,0 +1,94 @@ +package fr.skytasul.quests.api; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.function.Supplier; + +import org.bukkit.ChatColor; +import org.bukkit.command.CommandSender; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.Plugin; + +import fr.skytasul.quests.utils.ChatUtils; +import fr.skytasul.quests.utils.DebugUtils; +import fr.skytasul.quests.utils.Utils; + +public interface Locale { + + String getPath(); + + String getValue(); + + void setValue(String value); + + default String format(Object... replace) { + return Utils.format(getValue(), replace); + } + + default String format(Supplier... replace) { + return Utils.format(getValue(), replace); + } + + default void send(CommandSender sender, Object... args) { + Utils.sendMessage(sender, getValue(), args); + } + + default void sendWP(CommandSender p, Object... args) { + Utils.sendMessageWP(p, getValue(), args); + } + + public static void loadStrings(Locale[] locales, YamlConfiguration defaultConfig, YamlConfiguration config) { + for (Locale l : locales) { + String value = config.getString(l.getPath(), null); + if (value == null) value = defaultConfig.getString(l.getPath(), null); + if (value == null) DebugUtils.logMessage("Unavailable string in config for key " + l.getPath()); + l.setValue(ChatUtils.translateHexColorCodes(ChatColor.translateAlternateColorCodes('&', value == null ? "§cunknown string" : value))); + } + } + + public static YamlConfiguration loadLang(Plugin plugin, Locale[] locales, String loadedLanguage, String... languages) throws IOException { + long lastMillis = System.currentTimeMillis(); + for (String language : languages) { + File file = new File(plugin.getDataFolder(), "locales/" + language + ".yml"); + if (!file.exists()) plugin.saveResource("locales/" + language + ".yml", false); + } + + String language = "locales/" + loadedLanguage + ".yml"; + File file = new File(plugin.getDataFolder(), language); + InputStream res = plugin.getResource(language); + boolean created = false; + if (!file.exists()) { + plugin.getLogger().warning("Language file " + language + " does not exist. Using default english strings."); + file.createNewFile(); + res = plugin.getResource("locales/en_US.yml"); + created = true; + } + YamlConfiguration conf = YamlConfiguration.loadConfiguration(file); + boolean changes = false; + if (res != null) { // if it's a local resource + YamlConfiguration def = YamlConfiguration.loadConfiguration(new InputStreamReader(res, StandardCharsets.UTF_8)); + for (String key : def.getKeys(true)) { // get all keys in resource + if (!def.isConfigurationSection(key)) { // if not a block + if (!conf.contains(key)) { // if string does not exist in the file + conf.set(key, def.get(key)); // copy string + if (!created) DebugUtils.logMessage("String copied from source file to " + language + ". Key: " + key); + changes = true; + } + } + } + } + loadStrings(locales, YamlConfiguration.loadConfiguration(new InputStreamReader(plugin.getResource("locales/en_US.yml"), StandardCharsets.UTF_8)), conf); + + if (changes) { + plugin.getLogger().info("Copied new strings into " + language + " language file."); + conf.save(file); // if there has been changes before, save the edited file + } + + plugin.getLogger().info("Loaded language " + loadedLanguage + " (" + (((double) System.currentTimeMillis() - lastMillis) / 1000D) + "s)!"); + return conf; + } + +} \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java index dfe7039c..d05431b7 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java @@ -136,7 +136,7 @@ protected AbstractCreator(Line line, boolean ending) { public void setEntity(EntityType entity) { this.entity = entity; - line.editItem(6, ItemUtils.lore(line.getItem(6), entity == null ? Lang.EntityTypeAny.toString() : entity.name())); + line.editItem(6, ItemUtils.lore(line.getItem(6), Lang.optionValue.format(entity == null ? Lang.EntityTypeAny.toString() : entity.name()))); } public void setAmount(int amount) { diff --git a/core/src/main/java/fr/skytasul/quests/utils/Lang.java b/core/src/main/java/fr/skytasul/quests/utils/Lang.java index cd7dac3f..320cb7ce 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Lang.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Lang.java @@ -1,16 +1,11 @@ package fr.skytasul.quests.utils; -import java.util.function.Supplier; - -import org.bukkit.ChatColor; -import org.bukkit.command.CommandSender; -import org.bukkit.configuration.file.YamlConfiguration; - +import fr.skytasul.quests.api.Locale; /** * Stores all string paths and methods to format and send them to players. */ -public enum Lang{ +public enum Lang implements Locale { /* Formats (must be first to be used after) */ Prefix("misc.format.prefix"), @@ -100,6 +95,7 @@ public enum Lang{ NUMBER_NEGATIVE("msg.number.negative"), NUMBER_ZERO("msg.number.zero"), NUMBER_INVALID("msg.number.invalid"), + NUMBER_NOT_IN_BOUNDS("msg.number.notInBounds"), // 0: min, 1: max ERROR_OCCURED("msg.errorOccurred"), CANT_COMMAND("msg.commandsDisabled"), OUT_OF_BOUNDS("msg.indexOutOfBounds"), @@ -236,6 +232,9 @@ public enum Lang{ TITLE_STAY("msg.editor.title.stay"), TITLE_FADEOUT("msg.editor.title.fadeOut"), + COLOR_EDITOR("msg.editor.color"), + INVALID_COLOR("msg.editor.invalidColor"), + FIREWORK_INVALID("msg.editor.firework.invalid", ErrorPrefix), FIREWORK_INVALID_HAND("msg.editor.firework.invalidHand", ErrorPrefix), FIREWORK_EDITED("msg.editor.firework.edited", SuccessPrefix), @@ -652,6 +651,14 @@ public enum Lang{ title_stay("inv.editTitle.stay"), title_fadeOut("inv.editTitle.fadeOut"), + INVENTORY_PARTICLE_EFFECT("inv.particleEffect.name"), + particle_shape("inv.particleEffect.shape"), + particle_type("inv.particleEffect.type"), + particle_color("inv.particleEffect.color"), + + INVENTORY_PARTICLE_LIST("inv.particleList.name"), + particle_colored("inv.particleList.colored"), + BOOK_NAME("inv.listBook.questName"), BOOK_STARTER("inv.listBook.questStarter"), BOOK_REWARDS("inv.listBook.questRewards"), @@ -806,45 +813,24 @@ private Lang(String path, Lang prefix) { this.prefix = prefix; } + @Override public String getPath(){ return path; } - private void setValue(String value){ + @Override + public void setValue(String value) { this.value = value; } @Override - public String toString(){ + public String getValue() { return prefix == null ? value : (prefix.toString() + value); } - public String format(Object... replace){ - return Utils.format(toString(), replace); - } - - public String format(Supplier... replace) { - return Utils.format(toString(), replace); - } - - public void send(CommandSender sender, Object... args){ - Utils.sendMessage(sender, toString(), args); - } - - public void sendWP(CommandSender p, Object... args){ - Utils.sendMessageWP(p, toString(), args); - } - - - public static void loadStrings(YamlConfiguration defaultConfig, YamlConfiguration config) { - for (Lang l : values()){ - String value = config.getString(l.path, null); - if (value == null) value = defaultConfig.getString(l.path, null); - if (value == null) DebugUtils.logMessage("Unavailable string in config for key " + l.path); - l.setValue(ChatUtils.translateHexColorCodes(ChatColor.translateAlternateColorCodes('&', value == null ? "§cunknown string" : value))); - } + @Override + public String toString() { + return getValue(); } - - } diff --git a/core/src/main/java/fr/skytasul/quests/utils/types/DialogRunner.java b/core/src/main/java/fr/skytasul/quests/utils/types/DialogRunner.java index 5f9daa77..e7ede0d8 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/types/DialogRunner.java +++ b/core/src/main/java/fr/skytasul/quests/utils/types/DialogRunner.java @@ -213,7 +213,7 @@ private void handlePlayerChanges() { } public void unload() { - players.values().forEach(PlayerStatus::cancel); + if (!players.isEmpty()) players.values().forEach(PlayerStatus::cancel); players.clear(); handlePlayerChanges(); } diff --git a/core/src/main/resources/locales/en_US.yml b/core/src/main/resources/locales/en_US.yml index 6ba729b7..48312491 100644 --- a/core/src/main/resources/locales/en_US.yml +++ b/core/src/main/resources/locales/en_US.yml @@ -88,6 +88,7 @@ msg: negative: §cYou must enter a positive number! zero: §cYou must enter a number other than 0! invalid: §c{0} isn't a valid number. + notInBounds: §cYour number must be between {0} and {1}. errorOccurred: '§cAn error occurred, contact an adminstrator! §4§lError code: {0}' commandsDisabled: §cCurrently you aren't allowed to execute commands! indexOutOfBounds: §cThe number {0} is out of bounds! It has to be between {1} and @@ -322,6 +323,8 @@ msg: fadeIn: 'Write the "fade-in" duration, in ticks (20 ticks = 1 second).' stay: 'Write the "stay" duration, in ticks (20 ticks = 1 second).' fadeOut: 'Write the "fade-out" duration, in ticks (20 ticks = 1 second).' + color: 'Enter a color in the hexadecimal format (#XXXXX) or RGB format (RED GRE BLU).' + invalidColor: 'The color you entered is invalid. It must be either hexadecimal or RGB.' firework: invalid: 'This item is not a valid firework.' invalidHand: 'You must hold a firework in your main hand.' @@ -687,6 +690,14 @@ inv: fadeIn: §aFade-in duration stay: §bStay duration fadeOut: §aFade-out duration + particleEffect: + name: Create particle effect + shape: §dParticle shape + type: §eParticle type + color: §bParticle color + particleList: + name: Particles list + colored: Colored particle scoreboard: name: §6§lQuests From 7023c5b459c48e0fb10f907560d2333489ace85d Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 22 May 2022 10:59:35 +0200 Subject: [PATCH 012/111] Particle effect GUI * created a GUI to edit a particle effect * fixed note particles not being correctly randomized --- .../quests/editors/checkers/ColorParser.java | 44 +++++ .../quests/editors/checkers/NumberParser.java | 16 ++ .../quests/gui/misc/ParticleEffectGUI.java | 166 ++++++++++++++++++ .../skytasul/quests/utils/ParticleEffect.java | 43 ++++- .../quests/utils/compatibility/Post1_13.java | 2 +- 5 files changed, 263 insertions(+), 8 deletions(-) create mode 100644 core/src/main/java/fr/skytasul/quests/editors/checkers/ColorParser.java create mode 100644 core/src/main/java/fr/skytasul/quests/gui/misc/ParticleEffectGUI.java diff --git a/core/src/main/java/fr/skytasul/quests/editors/checkers/ColorParser.java b/core/src/main/java/fr/skytasul/quests/editors/checkers/ColorParser.java new file mode 100644 index 00000000..81a1ca31 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/editors/checkers/ColorParser.java @@ -0,0 +1,44 @@ +package fr.skytasul.quests.editors.checkers; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.bukkit.Color; +import org.bukkit.entity.Player; + +import fr.skytasul.quests.utils.Lang; + +public class ColorParser implements AbstractParser { + + public static final ColorParser PARSER = new ColorParser(); + + private final Pattern hexPattern = Pattern.compile("^#([a-fA-F0-9]{6})$"); + private final Pattern rgbPattern = Pattern.compile("^\\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\b,? ?\\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\b,? ?\\b([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])\\b$"); + + private ColorParser() {} + + @Override + public Color parse(Player p, String msg) throws Throwable { + int red, green, blue; + + Matcher hexMatcher = hexPattern.matcher(msg); + if (hexMatcher.matches()) { + String hex = hexMatcher.group(1); + red = Integer.parseInt(hex.substring(0, 2), 16); + green = Integer.parseInt(hex.substring(2, 4), 16); + blue = Integer.parseInt(hex.substring(4, 6), 16); + }else { + Matcher rgbMatcher = rgbPattern.matcher(msg); + if (rgbMatcher.matches()) { + red = Integer.parseInt(rgbMatcher.group(1)); + green = Integer.parseInt(rgbMatcher.group(2)); + blue = Integer.parseInt(rgbMatcher.group(3)); + }else { + Lang.INVALID_COLOR.send(p); + return null; + } + } + return Color.fromRGB(red, green, blue); + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/editors/checkers/NumberParser.java b/core/src/main/java/fr/skytasul/quests/editors/checkers/NumberParser.java index 1844b649..7cf75fc6 100644 --- a/core/src/main/java/fr/skytasul/quests/editors/checkers/NumberParser.java +++ b/core/src/main/java/fr/skytasul/quests/editors/checkers/NumberParser.java @@ -15,6 +15,8 @@ public class NumberParser implements AbstractParser { private Class numberType; private boolean positive; private boolean noZero; + private BigDecimal min; + private BigDecimal max; public NumberParser(Class numberType, boolean positive) { this(numberType, positive, false); @@ -26,6 +28,13 @@ public NumberParser(Class numberType, boolean positive, boolean noZero) { this.noZero = noZero; } + public NumberParser(Class numberType, T min, T max) { + this.numberType = numberType; + this.min = new BigDecimal(min.doubleValue()); + this.max = new BigDecimal(max.doubleValue()); + } + + @Override public T parse(Player p, String msg) { try{ String tname = numberType != Integer.class ? numberType.getSimpleName() : "Int"; @@ -40,6 +49,13 @@ public T parse(Player p, String msg) { return null; } } + if (min != null || max != null) { + BigDecimal bd = new BigDecimal(msg); + if ((min != null && bd.compareTo(min) < 0) || (max != null && bd.compareTo(max) > 0)) { + Lang.NUMBER_NOT_IN_BOUNDS.send(p, min, max); + return null; + } + } return number; }catch (Exception ex) {} Lang.NUMBER_INVALID.send(p, msg); diff --git a/core/src/main/java/fr/skytasul/quests/gui/misc/ParticleEffectGUI.java b/core/src/main/java/fr/skytasul/quests/gui/misc/ParticleEffectGUI.java new file mode 100644 index 00000000..afbfa1c3 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/gui/misc/ParticleEffectGUI.java @@ -0,0 +1,166 @@ +package fr.skytasul.quests.gui.misc; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import org.bukkit.Bukkit; +import org.bukkit.Color; +import org.bukkit.DyeColor; +import org.bukkit.Particle; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import fr.skytasul.quests.api.options.QuestOption; +import fr.skytasul.quests.editors.TextEditor; +import fr.skytasul.quests.editors.checkers.ColorParser; +import fr.skytasul.quests.gui.CustomInventory; +import fr.skytasul.quests.gui.ItemUtils; +import fr.skytasul.quests.gui.templates.PagedGUI; +import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.ParticleEffect; +import fr.skytasul.quests.utils.ParticleEffect.ParticleShape; +import fr.skytasul.quests.utils.Utils; +import fr.skytasul.quests.utils.XMaterial; +import fr.skytasul.quests.utils.compatibility.Post1_13; +import fr.skytasul.quests.utils.nms.NMS; + +public class ParticleEffectGUI implements CustomInventory { + + private static final int SLOT_SHAPE = 1; + private static final int SLOT_PARTICLE = 3; + private static final int SLOT_COLOR = 4; + private static final int SLOT_CANCEL = 7; + private static final int SLOT_FINISH = 8; + + private static final List PARTICLES = Arrays.stream(Particle.values()).filter(particle -> { + if (particle.getDataType() == Void.class) return true; + if (NMS.getMCVersion() >= 13) return particle.getDataType() == Post1_13.getDustOptionClass(); + return false; + }).collect(Collectors.toList()); + + private final Consumer end; + + private Particle particle; + private ParticleShape shape; + private Color color; + + private Inventory inv; + + public ParticleEffectGUI(Consumer end) { + this(end, Particle.FLAME, ParticleShape.POINT, Color.AQUA); + } + + public ParticleEffectGUI(Consumer end, ParticleEffect effect) { + this(end, effect.getParticle(), effect.getShape(), effect.getColor()); + } + + public ParticleEffectGUI(Consumer end, Particle particle, ParticleShape shape, Color color) { + this.end = end; + this.particle = particle; + this.shape = shape; + this.color = color; + } + + @Override + public Inventory open(Player p) { + if (inv == null) { + inv = Bukkit.createInventory(null, 9, Lang.INVENTORY_PARTICLE_EFFECT.toString()); + + inv.setItem(SLOT_SHAPE, ItemUtils.item(XMaterial.FIREWORK_STAR, Lang.particle_shape.toString(), Lang.optionValue.format(shape))); + inv.setItem(SLOT_PARTICLE, ItemUtils.item(XMaterial.PAPER, Lang.particle_type.toString(), Lang.optionValue.format(particle))); + if (canHaveColor(particle)) setColorItem(); + + inv.setItem(SLOT_CANCEL, ItemUtils.itemCancel); + inv.setItem(SLOT_FINISH, ItemUtils.itemDone); + } + return inv = p.openInventory(inv).getTopInventory(); + } + + @Override + public CloseBehavior onClose(Player p, Inventory inv) { + Utils.runSync(() -> end.accept(null)); + return CloseBehavior.REMOVE; + } + + @Override + public boolean onClick(Player p, Inventory inv, ItemStack current, int slot, ClickType click) { + switch (slot) { + + case SLOT_SHAPE: + List shapes = Arrays.asList(ParticleShape.values()); + int index = shapes.indexOf(shape); + shape = shapes.get(index == shapes.size() - 1 ? 0 : (index + 1)); + ItemUtils.lore(current, Lang.optionValue.format(shape)); + break; + + case SLOT_PARTICLE: + new PagedGUI(Lang.INVENTORY_PARTICLE_LIST.toString(), DyeColor.MAGENTA, PARTICLES) { + + @Override + public ItemStack getItemStack(Particle object) { + String[] lore = canHaveColor(object) ? new String[] { QuestOption.formatDescription(Lang.particle_colored.toString()) } : new String[0]; + return ItemUtils.item(XMaterial.PAPER, "§e" + object.name(), lore); + } + + @Override + public void click(Particle existing, ItemStack item, ClickType clickType) { + particle = existing; + ItemUtils.lore(current, Lang.optionValue.format(particle)); + if (canHaveColor(existing)) { + setColorItem(); + }else { + inv.setItem(SLOT_COLOR, null); + } + ParticleEffectGUI.this.create(p); + } + + @Override + public CloseBehavior onClose(Player p, Inventory inv) { + Utils.runSync(() -> ParticleEffectGUI.this.create(p)); + return CloseBehavior.REMOVE; + } + }.create(p); + break; + + case SLOT_COLOR: + if (canHaveColor(particle)) { + Runnable reopen = () -> open(p); + Lang.COLOR_EDITOR.send(p); + new TextEditor<>(p, reopen, newColor -> { + color = newColor; + ItemUtils.lore(current, getColorLore()); + reopen.run(); + }, ColorParser.PARSER).enter(); + } + break; + + case SLOT_CANCEL: + end.accept(null); + break; + + case SLOT_FINISH: + end.accept(new ParticleEffect(particle, shape, color)); + break; + } + return true; + } + + private boolean canHaveColor(Particle particle) { + if (NMS.getMCVersion() >= 13) return particle.getDataType() == Post1_13.getDustOptionClass(); + return particle == Particle.REDSTONE || particle == Particle.SPELL_MOB || particle == Particle.SPELL_MOB_AMBIENT; + } + + private void setColorItem() { + if (color == null) color = Color.RED; + inv.setItem(SLOT_COLOR, ItemUtils.item(XMaterial.MAGENTA_DYE, Lang.particle_color.toString(), getColorLore())); + } + + private String[] getColorLore() { + return new String[] { Lang.optionValue.format("RGB: " + color.getRed() + " " + color.getGreen() + " " + color.getBlue()) }; + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/utils/ParticleEffect.java b/core/src/main/java/fr/skytasul/quests/utils/ParticleEffect.java index 5cb171ab..827f0bee 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/ParticleEffect.java +++ b/core/src/main/java/fr/skytasul/quests/utils/ParticleEffect.java @@ -3,6 +3,7 @@ import java.util.List; import java.util.Random; +import org.apache.commons.lang.Validate; import org.bukkit.Color; import org.bukkit.Location; import org.bukkit.Particle; @@ -17,31 +18,51 @@ public class ParticleEffect { private static Random random = new Random(); - private ParticleType type; - private ParticleShape shape; + private final ParticleType type; + private final ParticleShape shape; + private final Color color; - private Color color; - private double[] colors; - private Object dustColor; + private final double[] colors; + private final Object dustColor; public ParticleEffect(Particle bukkitType, ParticleShape shape, Color color) { + Validate.notNull(bukkitType); + Validate.notNull(shape); this.type = new ParticleType(bukkitType); this.shape = shape; this.color = color; if (type.dustColored) { dustColor = Post1_13.getDustColor(color, 1); + colors = null; }else if (type.colored && bukkitType != Particle.NOTE) { + dustColor = null; colors = new double[3]; colors[0] = color.getRed() == 0 ? Float.MIN_NORMAL : color.getRed() / 255D; colors[1] = color.getGreen() / 255D; colors[2] = color.getBlue() / 255D; + }else { + dustColor = null; + colors = null; + color = null; } } + public Particle getParticle() { + return type.particle; + } + + public ParticleShape getShape() { + return shape; + } + + public Color getColor() { + return color; + } + @Override public String toString() { - return type.particle.name() + " in shape " + shape.name() + (type.colored ? " with color \"R" + (type.particle != Particle.NOTE ? color.getRed() + " G" + color.getGreen() + " B" + color.getBlue() : "random") + "\"" : ""); + return type.particle.name() + " in shape " + shape.name() + (type.colored ? " with color " + (type.particle != Particle.NOTE ? "R" + color.getRed() + " G" + color.getGreen() + " B" + color.getBlue() : "random") : ""); } public void send(Entity entity, List players) { @@ -77,6 +98,8 @@ public void sendParticle(Location lc, List players, double offX, double offX = random.nextInt(24) / 24D; // this offset contains the note number offY = 0; offZ = 0; + amount = 0; // amount must be 0 for note color to be enabled + extra = 1; // speed must be to 1 for the same reason }else if (dustColor != null) { data = dustColor; }else if (colors != null) { @@ -93,11 +116,17 @@ public void sendParticle(Location lc, List players, double offX, double } } + public void serialize(ConfigurationSection section) { + section.set("particleEffect", type.particle.name()); + section.set("particleShape", shape.name()); + if (color != null) section.set("particleColor", color.serialize()); + } + public static ParticleEffect deserialize(ConfigurationSection data) { return new ParticleEffect( Particle.valueOf(data.getString("particleEffect").toUpperCase()), ParticleShape.valueOf(data.getString("particleShape").toUpperCase()), - Color.deserialize(data.getConfigurationSection("particleColor").getValues(false))); + data.contains("particleColor") ? Color.deserialize(data.getConfigurationSection("particleColor").getValues(false)) : null); } public enum ParticleShape { diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/Post1_13.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/Post1_13.java index 674ff87a..762249b5 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/compatibility/Post1_13.java +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/Post1_13.java @@ -29,7 +29,7 @@ public static Class getDustOptionClass() { } public static Object getDustColor(Color color, int size) { - return new Particle.DustOptions(Color.fromBGR(color.getBlue(), color.getGreen(), color.getRed()), size); + return new Particle.DustOptions(color, size); } public static boolean isBlockData(Object data) { From 993c8edea7433bb553c0b9591c141d303c91a8ce Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 22 May 2022 16:58:07 +0200 Subject: [PATCH 013/111] Added locatable type filter --- .../api/stages/types/AbstractEntityStage.java | 2 + .../quests/api/stages/types/Locatable.java | 19 +++++++- .../ParticleEffectGUI.java | 40 ++++------------- .../quests/gui/particles/ParticleListGUI.java | 44 +++++++++++++++++++ .../skytasul/quests/utils/ParticleEffect.java | 10 +++-- .../skytasul/quests/utils/types/BQBlock.java | 35 +++++++++------ 6 files changed, 101 insertions(+), 49 deletions(-) rename core/src/main/java/fr/skytasul/quests/gui/{misc => particles}/ParticleEffectGUI.java (75%) create mode 100644 core/src/main/java/fr/skytasul/quests/gui/particles/ParticleListGUI.java diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java index d05431b7..81393012 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java @@ -2,6 +2,7 @@ import java.util.AbstractMap; import java.util.Collection; +import java.util.Collections; import java.util.Comparator; import java.util.Map; import java.util.Map.Entry; @@ -93,6 +94,7 @@ public boolean canBeFetchedAsynchronously() { @Override public Collection getNearbyLocated(NearbyFetcher fetcher) { + if (!fetcher.getTargetClass().isAssignableFrom(Located.LocatedEntity.class)) return Collections.emptyList(); double distanceSquared = fetcher.getMaxDistance() * fetcher.getMaxDistance(); return fetcher.getCenter().getWorld() .getEntitiesByClass(entity.getEntityClass()) diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java index e2f62506..a8ab008e 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java @@ -34,19 +34,29 @@ interface NearbyFetcher { int getMaxAmount(); + default Class getTargetClass() { + return Located.class; + } + static NearbyFetcher create(Location location, double maxDistance, int maxAmount) { - return new NearbyFetcherImpl(location, maxDistance, maxAmount); + return new NearbyFetcherImpl(location, maxDistance, maxAmount, Located.class); + } + + static NearbyFetcher create(Location location, double maxDistance, int maxAmount, Class targetClass) { + return new NearbyFetcherImpl(location, maxDistance, maxAmount, targetClass); } class NearbyFetcherImpl implements NearbyFetcher { private Location center; private double maxDistance; private int maxAmount; + private Class targetClass; - public NearbyFetcherImpl(Location center, double maxDistance, int maxAmount) { + public NearbyFetcherImpl(Location center, double maxDistance, int maxAmount, Class targetClass) { this.center = center; this.maxDistance = maxDistance; this.maxAmount = maxAmount; + this.targetClass = targetClass; } @Override @@ -64,6 +74,11 @@ public int getMaxAmount() { return maxAmount; } + @Override + public Class getTargetClass() { + return targetClass; + } + } } diff --git a/core/src/main/java/fr/skytasul/quests/gui/misc/ParticleEffectGUI.java b/core/src/main/java/fr/skytasul/quests/gui/particles/ParticleEffectGUI.java similarity index 75% rename from core/src/main/java/fr/skytasul/quests/gui/misc/ParticleEffectGUI.java rename to core/src/main/java/fr/skytasul/quests/gui/particles/ParticleEffectGUI.java index afbfa1c3..63dbb673 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/misc/ParticleEffectGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/particles/ParticleEffectGUI.java @@ -1,4 +1,4 @@ -package fr.skytasul.quests.gui.misc; +package fr.skytasul.quests.gui.particles; import java.util.Arrays; import java.util.List; @@ -7,19 +7,16 @@ import org.bukkit.Bukkit; import org.bukkit.Color; -import org.bukkit.DyeColor; import org.bukkit.Particle; import org.bukkit.entity.Player; import org.bukkit.event.inventory.ClickType; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; -import fr.skytasul.quests.api.options.QuestOption; import fr.skytasul.quests.editors.TextEditor; import fr.skytasul.quests.editors.checkers.ColorParser; import fr.skytasul.quests.gui.CustomInventory; import fr.skytasul.quests.gui.ItemUtils; -import fr.skytasul.quests.gui.templates.PagedGUI; import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.ParticleEffect; import fr.skytasul.quests.utils.ParticleEffect.ParticleShape; @@ -36,7 +33,7 @@ public class ParticleEffectGUI implements CustomInventory { private static final int SLOT_CANCEL = 7; private static final int SLOT_FINISH = 8; - private static final List PARTICLES = Arrays.stream(Particle.values()).filter(particle -> { + static final List PARTICLES = Arrays.stream(Particle.values()).filter(particle -> { if (particle.getDataType() == Void.class) return true; if (NMS.getMCVersion() >= 13) return particle.getDataType() == Post1_13.getDustOptionClass(); return false; @@ -72,7 +69,7 @@ public Inventory open(Player p) { inv.setItem(SLOT_SHAPE, ItemUtils.item(XMaterial.FIREWORK_STAR, Lang.particle_shape.toString(), Lang.optionValue.format(shape))); inv.setItem(SLOT_PARTICLE, ItemUtils.item(XMaterial.PAPER, Lang.particle_type.toString(), Lang.optionValue.format(particle))); - if (canHaveColor(particle)) setColorItem(); + if (ParticleEffect.canHaveColor(particle)) setColorItem(); inv.setItem(SLOT_CANCEL, ItemUtils.itemCancel); inv.setItem(SLOT_FINISH, ItemUtils.itemDone); @@ -98,36 +95,22 @@ public boolean onClick(Player p, Inventory inv, ItemStack current, int slot, Cli break; case SLOT_PARTICLE: - new PagedGUI(Lang.INVENTORY_PARTICLE_LIST.toString(), DyeColor.MAGENTA, PARTICLES) { - - @Override - public ItemStack getItemStack(Particle object) { - String[] lore = canHaveColor(object) ? new String[] { QuestOption.formatDescription(Lang.particle_colored.toString()) } : new String[0]; - return ItemUtils.item(XMaterial.PAPER, "§e" + object.name(), lore); - } - - @Override - public void click(Particle existing, ItemStack item, ClickType clickType) { + new ParticleListGUI(existing -> { + if (existing != null) { particle = existing; ItemUtils.lore(current, Lang.optionValue.format(particle)); - if (canHaveColor(existing)) { + if (ParticleEffect.canHaveColor(existing)) { setColorItem(); }else { inv.setItem(SLOT_COLOR, null); } - ParticleEffectGUI.this.create(p); - } - - @Override - public CloseBehavior onClose(Player p, Inventory inv) { - Utils.runSync(() -> ParticleEffectGUI.this.create(p)); - return CloseBehavior.REMOVE; } - }.create(p); + ParticleEffectGUI.this.create(p); + }).create(p); break; case SLOT_COLOR: - if (canHaveColor(particle)) { + if (ParticleEffect.canHaveColor(particle)) { Runnable reopen = () -> open(p); Lang.COLOR_EDITOR.send(p); new TextEditor<>(p, reopen, newColor -> { @@ -149,11 +132,6 @@ public CloseBehavior onClose(Player p, Inventory inv) { return true; } - private boolean canHaveColor(Particle particle) { - if (NMS.getMCVersion() >= 13) return particle.getDataType() == Post1_13.getDustOptionClass(); - return particle == Particle.REDSTONE || particle == Particle.SPELL_MOB || particle == Particle.SPELL_MOB_AMBIENT; - } - private void setColorItem() { if (color == null) color = Color.RED; inv.setItem(SLOT_COLOR, ItemUtils.item(XMaterial.MAGENTA_DYE, Lang.particle_color.toString(), getColorLore())); diff --git a/core/src/main/java/fr/skytasul/quests/gui/particles/ParticleListGUI.java b/core/src/main/java/fr/skytasul/quests/gui/particles/ParticleListGUI.java new file mode 100644 index 00000000..dcd07eb4 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/gui/particles/ParticleListGUI.java @@ -0,0 +1,44 @@ +package fr.skytasul.quests.gui.particles; + +import java.util.function.Consumer; + +import org.bukkit.DyeColor; +import org.bukkit.Particle; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import fr.skytasul.quests.api.options.QuestOption; +import fr.skytasul.quests.gui.ItemUtils; +import fr.skytasul.quests.gui.templates.PagedGUI; +import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.ParticleEffect; +import fr.skytasul.quests.utils.Utils; +import fr.skytasul.quests.utils.XMaterial; + +public class ParticleListGUI extends PagedGUI { + private Consumer end; + + public ParticleListGUI(Consumer end) { + super(Lang.INVENTORY_PARTICLE_LIST.toString(), DyeColor.MAGENTA, ParticleEffectGUI.PARTICLES); + this.end = end; + } + + @Override + public ItemStack getItemStack(Particle object) { + String[] lore = ParticleEffect.canHaveColor(object) ? new String[] { QuestOption.formatDescription(Lang.particle_colored.toString()) } : new String[0]; + return ItemUtils.item(XMaterial.PAPER, "§e" + object.name(), lore); + } + + @Override + public void click(Particle existing, ItemStack item, ClickType clickType) { + end.accept(existing); + } + + @Override + public CloseBehavior onClose(Player p, Inventory inv) { + Utils.runSync(() -> end.accept(null)); + return CloseBehavior.REMOVE; + } +} \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/utils/ParticleEffect.java b/core/src/main/java/fr/skytasul/quests/utils/ParticleEffect.java index 827f0bee..8bd6fcd1 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/ParticleEffect.java +++ b/core/src/main/java/fr/skytasul/quests/utils/ParticleEffect.java @@ -27,7 +27,6 @@ public class ParticleEffect { public ParticleEffect(Particle bukkitType, ParticleShape shape, Color color) { Validate.notNull(bukkitType); - Validate.notNull(shape); this.type = new ParticleType(bukkitType); this.shape = shape; this.color = color; @@ -62,7 +61,7 @@ public Color getColor() { @Override public String toString() { - return type.particle.name() + " in shape " + shape.name() + (type.colored ? " with color " + (type.particle != Particle.NOTE ? "R" + color.getRed() + " G" + color.getGreen() + " B" + color.getBlue() : "random") : ""); + return type.particle.name() + (shape == null ? "" : " in shape " + shape.name()) + (type.colored ? " with color " + (type.particle != Particle.NOTE ? "R" + color.getRed() + " G" + color.getGreen() + " B" + color.getBlue() : "random") : ""); } public void send(Entity entity, List players) { @@ -121,7 +120,7 @@ public void serialize(ConfigurationSection section) { section.set("particleShape", shape.name()); if (color != null) section.set("particleColor", color.serialize()); } - + public static ParticleEffect deserialize(ConfigurationSection data) { return new ParticleEffect( Particle.valueOf(data.getString("particleEffect").toUpperCase()), @@ -129,6 +128,11 @@ public static ParticleEffect deserialize(ConfigurationSection data) { data.contains("particleColor") ? Color.deserialize(data.getConfigurationSection("particleColor").getValues(false)) : null); } + public static boolean canHaveColor(Particle particle) { + if (NMS.getMCVersion() >= 13) return particle.getDataType() == Post1_13.getDustOptionClass(); + return particle == Particle.REDSTONE || particle == Particle.SPELL_MOB || particle == Particle.SPELL_MOB_AMBIENT; + } + public enum ParticleShape { POINT, NEAR, BAR, EXCLAMATION, SPOT; } diff --git a/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java b/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java index 0f37e778..11cac7c8 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java +++ b/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java @@ -2,11 +2,13 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import org.bukkit.block.Block; import fr.skytasul.quests.api.stages.types.Locatable; +import fr.skytasul.quests.api.stages.types.Locatable.Located; import fr.skytasul.quests.utils.MinecraftNames; import fr.skytasul.quests.utils.XBlock; import fr.skytasul.quests.utils.XMaterial; @@ -46,25 +48,32 @@ public static BQBlock fromString(String string) { } public static Collection getNearbyBlocks(Locatable.MultipleLocatable.NearbyFetcher fetcher, Collection types) { + if (!fetcher.getTargetClass().isAssignableFrom(Located.LocatedBlock.class)) return Collections.emptyList(); List blocks = new ArrayList<>(); - int minX = (int) (fetcher.getCenter().getX() - fetcher.getMaxDistance()); + int minY = (int) Math.max(fetcher.getCenter().getWorld().getMinHeight(), fetcher.getCenter().getY() - fetcher.getMaxDistance()); - int minZ = (int) (fetcher.getCenter().getZ() - fetcher.getMaxDistance()); - double maxX = fetcher.getCenter().getX() + fetcher.getMaxDistance(); double maxY = Math.min(fetcher.getCenter().getWorld().getMaxHeight(), fetcher.getCenter().getY() + fetcher.getMaxDistance()); - double maxZ = fetcher.getCenter().getZ() + fetcher.getMaxDistance(); - for (int x = minX; x <= maxX; x++) { - for (int z = minZ; z <= maxZ; z++) { - if (!fetcher.getCenter().getWorld().isChunkLoaded(x >> 4, z >> 4)) continue; - for (int y = minY; y <= maxY; y++) { - Block blockAt = fetcher.getCenter().getWorld().getBlockAt(x, y, z); - if (types.stream().anyMatch(type -> type.applies(blockAt))) { - blocks.add(Locatable.Located.LocatedBlock.create(blockAt)); - if (blocks.size() >= fetcher.getMaxAmount()) break; - } + + int centerX = fetcher.getCenter().getBlockX(); + int centerZ = fetcher.getCenter().getBlockZ(); + int x = centerX; + int z = centerZ; + spiral: for (int i = 0; i < fetcher.getMaxDistance() * fetcher.getMaxDistance(); i++) { + if (!fetcher.getCenter().getWorld().isChunkLoaded(x >> 4, z >> 4)) continue; + for (int y = minY; y <= maxY; y++) { + Block blockAt = fetcher.getCenter().getWorld().getBlockAt(x, y, z); + if (types.stream().anyMatch(type -> type.applies(blockAt))) { + blocks.add(Locatable.Located.LocatedBlock.create(blockAt)); + if (blocks.size() >= fetcher.getMaxAmount()) break spiral; } } + if (Math.abs(x - centerX) <= Math.abs(z - centerZ) && ((x - centerX) != (z - centerZ) || x >= centerX)) + x += ((z >= centerZ) ? 1 : -1); + else + z += ((x >= centerX) ? -1 : 1); } + + // used the N spiral algorithm from here: https://stackoverflow.com/a/31864777 return blocks; } From 93c03baac3a462ef55781539d600e1757ec8cccc Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Wed, 25 May 2022 22:19:33 +0200 Subject: [PATCH 014/111] Moved AbstractCountableStage, updated XBlock --- .../api/stages/AbstractCountableStage.java | 311 +-------------- .../types/AbstractCountableBlockStage.java | 1 - .../stages/types/AbstractCountableStage.java | 296 ++++++++++++++ .../api/stages/types/AbstractItemStage.java | 2 +- .../quests/api/stages/types/Locatable.java | 9 +- .../fr/skytasul/quests/stages/StageMobs.java | 2 +- .../java/fr/skytasul/quests/utils/XBlock.java | 362 ++++++++++++++---- 7 files changed, 610 insertions(+), 373 deletions(-) create mode 100644 core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableStage.java diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractCountableStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractCountableStage.java index ca6e5728..6fcbceb2 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractCountableStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractCountableStage.java @@ -1,295 +1,16 @@ -package fr.skytasul.quests.api.stages; - -import java.util.AbstractMap; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.function.Supplier; - -import org.bukkit.Bukkit; -import org.bukkit.boss.BarColor; -import org.bukkit.boss.BarStyle; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.MemoryConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitTask; - -import fr.skytasul.quests.BeautyQuests; -import fr.skytasul.quests.QuestsConfiguration; -import fr.skytasul.quests.api.QuestsAPI; -import fr.skytasul.quests.api.bossbar.BQBossBarManager.BQBossBar; -import fr.skytasul.quests.players.PlayerAccount; -import fr.skytasul.quests.structure.QuestBranch; -import fr.skytasul.quests.structure.QuestBranch.Source; -import fr.skytasul.quests.utils.Lang; -import fr.skytasul.quests.utils.Utils; - -public abstract class AbstractCountableStage extends AbstractStage { - - protected Map> objects; - - protected Map bars = new HashMap<>(); - private boolean barsEnabled = false; - private int cachedSize = 0; - - public AbstractCountableStage(QuestBranch branch, Map> objects) { - super(branch); - this.objects = objects; - calculateSize(); - } - - public Map> getObjects() { - return objects; - } - - public Map> cloneObjects() { - Map> map = new HashMap<>(); - for (Entry> entry : objects.entrySet()) { - map.put(entry.getKey(), new AbstractMap.SimpleEntry<>(cloneObject(entry.getValue().getKey()), entry.getValue().getValue())); - } - return map; - } - - public Map getPlayerRemainings(PlayerAccount acc) { - return getData(acc, "remaining"); - } - - protected void calculateSize() { - cachedSize = 0; - for (Entry objectsEntry : objects.values()) { - cachedSize += objectsEntry.getValue(); - } - barsEnabled = QuestsConfiguration.showMobsProgressBar() && cachedSize > 0; - } - - @Override - protected String descriptionLine(PlayerAccount acc, Source source){ - return Utils.descriptionLines(source, buildRemainingArray(acc, source)); - } - - @Override - protected Supplier[] descriptionFormat(PlayerAccount acc, Source source) { - return new Supplier[] { () -> Utils.descriptionLines(source, buildRemainingArray(acc, source)) }; - } - - private String[] buildRemainingArray(PlayerAccount acc, Source source) { - Map playerAmounts = getPlayerRemainings(acc); - if (playerAmounts == null) { - BeautyQuests.logger.severe("The plugin has been unable to retrieve stage datas for account " + acc.debugName() + " on " + super.toString()); - return new String[] { "§4§lerror" }; - } - String[] elements = new String[playerAmounts.size()]; - int i = 0; - for (Entry obj : playerAmounts.entrySet()) { - Entry object = objects.get(obj.getKey()); - elements[i] = object == null ? "no object " + obj.getKey() : QuestsConfiguration.getItemNameColor() + Utils.getStringFromNameAndAmount(getName(object.getKey()), QuestsConfiguration.getItemAmountColor(), obj.getValue(), object.getValue(), QuestsConfiguration.showDescriptionItemsXOne(source)); - i++; - } - return elements; - } - - @Override - protected void initPlayerDatas(PlayerAccount acc, Map datas) { - super.initPlayerDatas(acc, datas); - Map amounts = new HashMap<>(); - for (Entry> entry : objects.entrySet()) { - amounts.put(entry.getKey(), entry.getValue().getValue()); - } - datas.put("remaining", amounts); - } - - /** - * When called, this will test the player datas for the passed object. - * If found, the remaining amount will be lowered. - * If no remaining items are found, the stage will complete. - * @param acc player account - * @param p player - * @param object object of the event - * @param amount amount completed - * @return false if there is no need to call this method again in the same game tick. - */ - public boolean event(PlayerAccount acc, Player p, Object object, int amount) { - if (amount < 0) throw new IllegalArgumentException("Event amount must be positive (" + amount + ")"); - if (!canUpdate(p)) return true; - for (Entry> entry : objects.entrySet()) { - int id = entry.getKey(); - if (objectApplies(entry.getValue().getKey(), object)) { - Map playerAmounts = getPlayerRemainings(acc); - if (playerAmounts == null) { - BeautyQuests.logger.warning(p.getName() + " does not have object datas for stage " + toString() + ". This is a bug!"); - return true; - } - if (playerAmounts.containsKey(id)) { - int playerAmount = playerAmounts.get(id); - if (playerAmount <= amount) { - playerAmounts.remove(id); - }else playerAmounts.put(id, playerAmount -= amount); - } - - if (playerAmounts.isEmpty()) { - finishStage(p); - return true; - }else { - if (barsEnabled) { - BossBar bar = bars.get(p); - if (bar == null) { - BeautyQuests.logger.warning(p.getName() + " does not have boss bar for stage " + toString() + ". This is a bug!"); - }else bar.update(playerAmounts.values().stream().mapToInt(Integer::intValue).sum()); - } - updateObjective(acc, p, "remaining", playerAmounts); - return false; - } - } - } - return true; - } - - @Override - public void start(PlayerAccount acc) { - super.start(acc); - if (acc.isCurrent()) createBar(acc.getPlayer(), cachedSize); - } - - @Override - public void end(PlayerAccount acc) { - super.end(acc); - if (acc.isCurrent()) removeBar(acc.getPlayer()); - } - - @Override - public void unload() { - super.unload(); - bars.values().forEach(BossBar::remove); - } - - @Override - public void joins(PlayerAccount acc, Player p) { - super.joins(acc, p); - Map remainings = getPlayerRemainings(acc); - if (remainings == null) { - BeautyQuests.logger.severe(p.getName() + " does not have remaining datas for stage " + toString() + ". This is a bug!"); - return; - } - createBar(p, remainings.values().stream().mapToInt(Integer::intValue).sum()); - } - - @Override - public void leaves(PlayerAccount acc, Player p) { - super.leaves(acc, p); - removeBar(p); - } - - protected void createBar(Player p, int amount) { - if (barsEnabled) bars.put(p, new BossBar(p, amount)); - } - - protected void removeBar(Player p) { - if (bars.containsKey(p)) bars.remove(p).remove(); - } - - protected boolean objectApplies(T object, Object other) { - return object.equals(other); - } - - protected T cloneObject(T object) { - return object; - } - - protected abstract String getName(T object); - - protected abstract Object serialize(T object); - - protected abstract T deserialize(Object object); - - /** - * @deprecated for removal, {@link #serialize(ConfigurationSection)} should be used instead. - */ - @Override - @Deprecated - protected void serialize(Map map) {} - - @Override - protected void serialize(ConfigurationSection section) { - ConfigurationSection objectsSection = section.createSection("objects"); - for (Entry> obj : objects.entrySet()) { - ConfigurationSection objectSection = objectsSection.createSection(Integer.toString(obj.getKey())); - objectSection.set("amount", obj.getValue().getValue()); - objectSection.set("object", serialize(obj.getValue().getKey())); - } - Map serialized = new HashMap<>(); - serialize(serialized); - serialized.forEach(section::set); - } - - /** - * @deprecated for removal, {@link #deserialize(ConfigurationSection)} should be used instead. - */ - @Deprecated - protected void deserialize(Map serializedDatas) { - MemoryConfiguration configuration = new MemoryConfiguration(); - serializedDatas.forEach(configuration::set); - deserialize(configuration); - } - - protected void deserialize(ConfigurationSection section) { - ConfigurationSection objectsSection = section.getConfigurationSection("objects"); - if (objectsSection != null) { - for (String key : objectsSection.getKeys(false)) { - ConfigurationSection object = objectsSection.getConfigurationSection(key); - Object serialized = object.get("object"); - if (serialized instanceof ConfigurationSection) serialized = ((ConfigurationSection) serialized).getValues(false); - objects.put(Integer.parseInt(key), new AbstractMap.SimpleEntry<>(deserialize(serialized), object.getInt("amount"))); - } - } - - if (objects.isEmpty()) BeautyQuests.logger.warning("Stage with no content: " + toString()); - calculateSize(); - } - - class BossBar { - private Player p; - private BQBossBar bar; - private BukkitTask timer; - - public BossBar(Player p, int amount) { - this.p = p; - - BarStyle style = null; - if (cachedSize % 20 == 0) { - style = BarStyle.SEGMENTED_20; - }else if (cachedSize % 10 == 0) { - style = BarStyle.SEGMENTED_10; - }else if (cachedSize % 12 == 0) { - style = BarStyle.SEGMENTED_12; - }else if (cachedSize % 6 == 0) { - style = BarStyle.SEGMENTED_6; - }else style = BarStyle.SOLID; - bar = QuestsAPI.getBossBarManager().buildBossBar(Lang.MobsProgression.format(branch.getQuest().getName(), 100, 100), BarColor.YELLOW, style); - update(amount); - } - - public void remove() { - bar.removeAll(); - if (timer != null) timer.cancel(); - } - - public void update(int amount) { - if (amount >= 0 && amount <= cachedSize) { - bar.setProgress((double) (cachedSize - amount) / (double) cachedSize); - }else BeautyQuests.logger.warning("Amount of objects superior to max objects in " + AbstractCountableStage.this.toString() + " for player " + p.getName() + ": " + amount + " > " + cachedSize); - bar.setTitle(Lang.MobsProgression.format(branch.getQuest().getName(), cachedSize - amount, cachedSize)); - bar.addPlayer(p); - timer(); - } - - private void timer() { - if (QuestsConfiguration.getProgressBarTimeout() <= 0) return; - if (timer != null) timer.cancel(); - timer = Bukkit.getScheduler().runTaskLater(BeautyQuests.getInstance(), () -> { - bar.removePlayer(p); - timer = null; - }, QuestsConfiguration.getProgressBarTimeout() * 20L); - } - } - -} +package fr.skytasul.quests.api.stages; + +import java.util.Map; +import java.util.Map.Entry; + +import fr.skytasul.quests.structure.QuestBranch; + +@Deprecated +public abstract class AbstractCountableStage extends fr.skytasul.quests.api.stages.types.AbstractCountableStage { + + @Deprecated + protected AbstractCountableStage(QuestBranch branch, Map> objects) { + super(branch, objects); + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableBlockStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableBlockStage.java index e2825462..8b27e2cc 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableBlockStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableBlockStage.java @@ -9,7 +9,6 @@ import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; -import fr.skytasul.quests.api.stages.AbstractCountableStage; import fr.skytasul.quests.api.stages.StageCreation; import fr.skytasul.quests.gui.Inventories; import fr.skytasul.quests.gui.ItemUtils; diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableStage.java new file mode 100644 index 00000000..68885c39 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableStage.java @@ -0,0 +1,296 @@ +package fr.skytasul.quests.api.stages.types; + +import java.util.AbstractMap; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Supplier; + +import org.bukkit.Bukkit; +import org.bukkit.boss.BarColor; +import org.bukkit.boss.BarStyle; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.MemoryConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitTask; + +import fr.skytasul.quests.BeautyQuests; +import fr.skytasul.quests.QuestsConfiguration; +import fr.skytasul.quests.api.QuestsAPI; +import fr.skytasul.quests.api.bossbar.BQBossBarManager.BQBossBar; +import fr.skytasul.quests.api.stages.AbstractStage; +import fr.skytasul.quests.players.PlayerAccount; +import fr.skytasul.quests.structure.QuestBranch; +import fr.skytasul.quests.structure.QuestBranch.Source; +import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.Utils; + +public abstract class AbstractCountableStage extends AbstractStage { + + protected Map> objects; + + protected Map bars = new HashMap<>(); + private boolean barsEnabled = false; + private int cachedSize = 0; + + public AbstractCountableStage(QuestBranch branch, Map> objects) { + super(branch); + this.objects = objects; + calculateSize(); + } + + public Map> getObjects() { + return objects; + } + + public Map> cloneObjects() { + Map> map = new HashMap<>(); + for (Entry> entry : objects.entrySet()) { + map.put(entry.getKey(), new AbstractMap.SimpleEntry<>(cloneObject(entry.getValue().getKey()), entry.getValue().getValue())); + } + return map; + } + + public Map getPlayerRemainings(PlayerAccount acc) { + return getData(acc, "remaining"); + } + + protected void calculateSize() { + cachedSize = 0; + for (Entry objectsEntry : objects.values()) { + cachedSize += objectsEntry.getValue(); + } + barsEnabled = QuestsConfiguration.showMobsProgressBar() && cachedSize > 0; + } + + @Override + protected String descriptionLine(PlayerAccount acc, Source source){ + return Utils.descriptionLines(source, buildRemainingArray(acc, source)); + } + + @Override + protected Supplier[] descriptionFormat(PlayerAccount acc, Source source) { + return new Supplier[] { () -> Utils.descriptionLines(source, buildRemainingArray(acc, source)) }; + } + + private String[] buildRemainingArray(PlayerAccount acc, Source source) { + Map playerAmounts = getPlayerRemainings(acc); + if (playerAmounts == null) { + BeautyQuests.logger.severe("The plugin has been unable to retrieve stage datas for account " + acc.debugName() + " on " + super.toString()); + return new String[] { "§4§lerror" }; + } + String[] elements = new String[playerAmounts.size()]; + int i = 0; + for (Entry obj : playerAmounts.entrySet()) { + Entry object = objects.get(obj.getKey()); + elements[i] = object == null ? "no object " + obj.getKey() : QuestsConfiguration.getItemNameColor() + Utils.getStringFromNameAndAmount(getName(object.getKey()), QuestsConfiguration.getItemAmountColor(), obj.getValue(), object.getValue(), QuestsConfiguration.showDescriptionItemsXOne(source)); + i++; + } + return elements; + } + + @Override + protected void initPlayerDatas(PlayerAccount acc, Map datas) { + super.initPlayerDatas(acc, datas); + Map amounts = new HashMap<>(); + for (Entry> entry : objects.entrySet()) { + amounts.put(entry.getKey(), entry.getValue().getValue()); + } + datas.put("remaining", amounts); + } + + /** + * When called, this will test the player datas for the passed object. + * If found, the remaining amount will be lowered. + * If no remaining items are found, the stage will complete. + * @param acc player account + * @param p player + * @param object object of the event + * @param amount amount completed + * @return false if there is no need to call this method again in the same game tick. + */ + public boolean event(PlayerAccount acc, Player p, Object object, int amount) { + if (amount < 0) throw new IllegalArgumentException("Event amount must be positive (" + amount + ")"); + if (!canUpdate(p)) return true; + for (Entry> entry : objects.entrySet()) { + int id = entry.getKey(); + if (objectApplies(entry.getValue().getKey(), object)) { + Map playerAmounts = getPlayerRemainings(acc); + if (playerAmounts == null) { + BeautyQuests.logger.warning(p.getName() + " does not have object datas for stage " + toString() + ". This is a bug!"); + return true; + } + if (playerAmounts.containsKey(id)) { + int playerAmount = playerAmounts.get(id); + if (playerAmount <= amount) { + playerAmounts.remove(id); + }else playerAmounts.put(id, playerAmount -= amount); + } + + if (playerAmounts.isEmpty()) { + finishStage(p); + return true; + }else { + if (barsEnabled) { + BossBar bar = bars.get(p); + if (bar == null) { + BeautyQuests.logger.warning(p.getName() + " does not have boss bar for stage " + toString() + ". This is a bug!"); + }else bar.update(playerAmounts.values().stream().mapToInt(Integer::intValue).sum()); + } + updateObjective(acc, p, "remaining", playerAmounts); + return false; + } + } + } + return true; + } + + @Override + public void start(PlayerAccount acc) { + super.start(acc); + if (acc.isCurrent()) createBar(acc.getPlayer(), cachedSize); + } + + @Override + public void end(PlayerAccount acc) { + super.end(acc); + if (acc.isCurrent()) removeBar(acc.getPlayer()); + } + + @Override + public void unload() { + super.unload(); + bars.values().forEach(BossBar::remove); + } + + @Override + public void joins(PlayerAccount acc, Player p) { + super.joins(acc, p); + Map remainings = getPlayerRemainings(acc); + if (remainings == null) { + BeautyQuests.logger.severe(p.getName() + " does not have remaining datas for stage " + toString() + ". This is a bug!"); + return; + } + createBar(p, remainings.values().stream().mapToInt(Integer::intValue).sum()); + } + + @Override + public void leaves(PlayerAccount acc, Player p) { + super.leaves(acc, p); + removeBar(p); + } + + protected void createBar(Player p, int amount) { + if (barsEnabled) bars.put(p, new BossBar(p, amount)); + } + + protected void removeBar(Player p) { + if (bars.containsKey(p)) bars.remove(p).remove(); + } + + protected boolean objectApplies(T object, Object other) { + return object.equals(other); + } + + protected T cloneObject(T object) { + return object; + } + + protected abstract String getName(T object); + + protected abstract Object serialize(T object); + + protected abstract T deserialize(Object object); + + /** + * @deprecated for removal, {@link #serialize(ConfigurationSection)} should be used instead. + */ + @Override + @Deprecated + protected void serialize(Map map) {} + + @Override + protected void serialize(ConfigurationSection section) { + ConfigurationSection objectsSection = section.createSection("objects"); + for (Entry> obj : objects.entrySet()) { + ConfigurationSection objectSection = objectsSection.createSection(Integer.toString(obj.getKey())); + objectSection.set("amount", obj.getValue().getValue()); + objectSection.set("object", serialize(obj.getValue().getKey())); + } + Map serialized = new HashMap<>(); + serialize(serialized); + serialized.forEach(section::set); + } + + /** + * @deprecated for removal, {@link #deserialize(ConfigurationSection)} should be used instead. + */ + @Deprecated + protected void deserialize(Map serializedDatas) { + MemoryConfiguration configuration = new MemoryConfiguration(); + serializedDatas.forEach(configuration::set); + deserialize(configuration); + } + + protected void deserialize(ConfigurationSection section) { + ConfigurationSection objectsSection = section.getConfigurationSection("objects"); + if (objectsSection != null) { + for (String key : objectsSection.getKeys(false)) { + ConfigurationSection object = objectsSection.getConfigurationSection(key); + Object serialized = object.get("object"); + if (serialized instanceof ConfigurationSection) serialized = ((ConfigurationSection) serialized).getValues(false); + objects.put(Integer.parseInt(key), new AbstractMap.SimpleEntry<>(deserialize(serialized), object.getInt("amount"))); + } + } + + if (objects.isEmpty()) BeautyQuests.logger.warning("Stage with no content: " + toString()); + calculateSize(); + } + + class BossBar { + private Player p; + private BQBossBar bar; + private BukkitTask timer; + + public BossBar(Player p, int amount) { + this.p = p; + + BarStyle style = null; + if (cachedSize % 20 == 0) { + style = BarStyle.SEGMENTED_20; + }else if (cachedSize % 10 == 0) { + style = BarStyle.SEGMENTED_10; + }else if (cachedSize % 12 == 0) { + style = BarStyle.SEGMENTED_12; + }else if (cachedSize % 6 == 0) { + style = BarStyle.SEGMENTED_6; + }else style = BarStyle.SOLID; + bar = QuestsAPI.getBossBarManager().buildBossBar(Lang.MobsProgression.format(branch.getQuest().getName(), 100, 100), BarColor.YELLOW, style); + update(amount); + } + + public void remove() { + bar.removeAll(); + if (timer != null) timer.cancel(); + } + + public void update(int amount) { + if (amount >= 0 && amount <= cachedSize) { + bar.setProgress((double) (cachedSize - amount) / (double) cachedSize); + }else BeautyQuests.logger.warning("Amount of objects superior to max objects in " + AbstractCountableStage.this.toString() + " for player " + p.getName() + ": " + amount + " > " + cachedSize); + bar.setTitle(Lang.MobsProgression.format(branch.getQuest().getName(), cachedSize - amount, cachedSize)); + bar.addPlayer(p); + timer(); + } + + private void timer() { + if (QuestsConfiguration.getProgressBarTimeout() <= 0) return; + if (timer != null) timer.cancel(); + timer = Bukkit.getScheduler().runTaskLater(BeautyQuests.getInstance(), () -> { + bar.removePlayer(p); + timer = null; + }, QuestsConfiguration.getProgressBarTimeout() * 20L); + } + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractItemStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractItemStage.java index c01a32bf..dccac855 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractItemStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractItemStage.java @@ -13,7 +13,7 @@ import org.bukkit.inventory.ItemStack; import fr.skytasul.quests.api.comparison.ItemComparisonMap; -import fr.skytasul.quests.api.stages.AbstractCountableStage; +import fr.skytasul.quests.api.stages.types.AbstractCountableStage; import fr.skytasul.quests.api.stages.StageCreation; import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.gui.creation.ItemsGUI; diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java index a8ab008e..2dc1431f 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java @@ -94,7 +94,7 @@ static Located create(Location location) { } class LocatedImpl implements Located { - private Location location; + protected Location location; public LocatedImpl(Location location) { this.location = location; @@ -102,7 +102,7 @@ public LocatedImpl(Location location) { @Override public Location getLocation() { - return location; + return location.clone(); } @Override @@ -179,6 +179,11 @@ class LocatedBlockImpl extends LocatedImpl implements LocatedBlock { public LocatedBlockImpl(Location location) { super(location); } + + @Override + public Block getBlock() { + return location.getBlock(); + } } } diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageMobs.java b/core/src/main/java/fr/skytasul/quests/stages/StageMobs.java index 4cdd031f..b2d26819 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageMobs.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageMobs.java @@ -10,7 +10,7 @@ import org.bukkit.event.entity.EntityDamageEvent.DamageCause; import fr.skytasul.quests.api.mobs.Mob; -import fr.skytasul.quests.api.stages.AbstractCountableStage; +import fr.skytasul.quests.api.stages.types.AbstractCountableStage; import fr.skytasul.quests.api.stages.StageCreation; import fr.skytasul.quests.gui.Inventories; import fr.skytasul.quests.gui.ItemUtils; diff --git a/core/src/main/java/fr/skytasul/quests/utils/XBlock.java b/core/src/main/java/fr/skytasul/quests/utils/XBlock.java index 91859efa..2261123d 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/XBlock.java +++ b/core/src/main/java/fr/skytasul/quests/utils/XBlock.java @@ -1,7 +1,7 @@ /* * The MIT License (MIT) * - * Copyright (c) 2020 Crypto Morin + * Copyright (c) 2022 Crypto Morin * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -21,22 +21,20 @@ */ package fr.skytasul.quests.utils; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.Locale; -import java.util.Optional; -import java.util.Set; +import java.util.*; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.commons.lang.Validate; import org.bukkit.DyeColor; import org.bukkit.Material; import org.bukkit.TreeSpecies; +import org.bukkit.block.Banner; import org.bukkit.block.Block; import org.bukkit.block.BlockFace; import org.bukkit.block.BlockState; +import org.bukkit.block.data.BlockData; import org.bukkit.inventory.InventoryHolder; import org.bukkit.material.Cake; import org.bukkit.material.Colorable; @@ -56,7 +54,7 @@ * This class doesn't and shouldn't support materials that are {@link Material#isLegacy()}. * * @author Crypto Morin - * @version 2.0.0 + * @version 2.2.0 * @see Block * @see BlockState * @see MaterialData @@ -71,10 +69,27 @@ public final class XBlock { )); public static final Set DANGEROUS = Collections.unmodifiableSet(EnumSet.of(XMaterial.MAGMA_BLOCK, XMaterial.LAVA, XMaterial.CAMPFIRE, XMaterial.FIRE, XMaterial.SOUL_FIRE)); public static final byte CAKE_SLICES = 6; - private static final boolean ISFLAT = XMaterial.isNewVersion(); - - private XBlock() {} + private static final boolean ISFLAT = XMaterial.supports(13); + private static final Map ITEM_TO_BLOCK = new EnumMap<>(XMaterial.class); + + static { + ITEM_TO_BLOCK.put(XMaterial.MELON_SLICE, XMaterial.MELON_STEM); + ITEM_TO_BLOCK.put(XMaterial.MELON_SEEDS, XMaterial.MELON_STEM); + + ITEM_TO_BLOCK.put(XMaterial.CARROT_ON_A_STICK, XMaterial.CARROTS); + ITEM_TO_BLOCK.put(XMaterial.GOLDEN_CARROT, XMaterial.CARROTS); + ITEM_TO_BLOCK.put(XMaterial.CARROT, XMaterial.CARROTS); + + ITEM_TO_BLOCK.put(XMaterial.POTATO, XMaterial.POTATOES); + ITEM_TO_BLOCK.put(XMaterial.BAKED_POTATO, XMaterial.POTATOES); + ITEM_TO_BLOCK.put(XMaterial.POISONOUS_POTATO, XMaterial.POTATOES); + + ITEM_TO_BLOCK.put(XMaterial.PUMPKIN_SEEDS, XMaterial.PUMPKIN_STEM); + ITEM_TO_BLOCK.put(XMaterial.PUMPKIN_PIE, XMaterial.PUMPKIN); + } + private XBlock() {} + public static boolean isLit(Block block) { if (ISFLAT) { if (!(block.getBlockData() instanceof org.bukkit.block.data.Lightable)) return false; @@ -86,13 +101,14 @@ public static boolean isLit(Block block) { } /** - * Checks if the block is a container. - * Containers are chests, hoppers, enderchests and everything that - * has an inventory. - * - * @param block the block to check. - * @return true if the block is a container, otherwise false. - */ + * Checks if the block is a container. + * Containers are chests, hoppers, enderchests and everything that + * has an inventory. + * + * @param block the block to check. + * + * @return true if the block is a container, otherwise false. + */ public static boolean isContainer(@Nullable Block block) { return block != null && block.getState() instanceof InventoryHolder; } @@ -106,8 +122,10 @@ public static boolean isContainer(@Nullable Block block) { public static void setLit(Block block, boolean lit) { if (ISFLAT) { if (!(block.getBlockData() instanceof org.bukkit.block.data.Lightable)) return; - org.bukkit.block.data.Lightable lightable = (org.bukkit.block.data.Lightable) block.getBlockData(); + BlockData data = block.getBlockData(); + org.bukkit.block.data.Lightable lightable = (org.bukkit.block.data.Lightable) data; lightable.setLit(lit); + block.setBlockData(data, false); return; } @@ -123,6 +141,7 @@ else if (name.startsWith("REDSTONE_LAMP")) * Any material that can be planted which is from {@link #CROPS} * * @param material the material to check. + * * @return true if this material is a crop, otherwise false. */ public static boolean isCrop(XMaterial material) { @@ -133,6 +152,7 @@ public static boolean isCrop(XMaterial material) { * Any material that can damage players, usually by interacting with the block. * * @param material the material to check. + * * @return true if this material is dangerous, otherwise false. */ public static boolean isDangerous(XMaterial material) { @@ -192,24 +212,24 @@ public static boolean isPotato(@Nullable Material material) { public static BlockFace getDirection(Block block) { if (ISFLAT) { - if (!(block.getBlockData() instanceof Directional)) return BlockFace.SELF; - Directional direction = (Directional) block.getBlockData(); + if (!(block.getBlockData() instanceof org.bukkit.block.data.Directional)) return BlockFace.SELF; + org.bukkit.block.data.Directional direction = (org.bukkit.block.data.Directional) block.getBlockData(); return direction.getFacing(); } BlockState state = block.getState(); MaterialData data = state.getData(); - if (data instanceof org.bukkit.material.Directional) { - return ((org.bukkit.material.Directional) data).getFacing(); - } - return null; + if (data instanceof org.bukkit.material.Directional) return ((org.bukkit.material.Directional) data).getFacing(); + return BlockFace.SELF; } public static boolean setDirection(Block block, BlockFace facing) { if (ISFLAT) { if (!(block.getBlockData() instanceof org.bukkit.block.data.Directional)) return false; - org.bukkit.block.data.Directional direction = (org.bukkit.block.data.Directional) block.getBlockData(); + BlockData data = block.getBlockData(); + org.bukkit.block.data.Directional direction = (org.bukkit.block.data.Directional) data; direction.setFacing(facing); + block.setBlockData(data, false); return true; } @@ -223,6 +243,124 @@ public static boolean setDirection(Block block, BlockFace facing) { return false; } + public static boolean setType(@Nonnull Block block, @Nullable XMaterial material, boolean applyPhysics) { + Objects.requireNonNull(block, "Cannot set type of null block"); + if (material == null) material = XMaterial.AIR; + XMaterial smartConversion = ITEM_TO_BLOCK.get(material); + if (smartConversion != null) material = smartConversion; + if (material.parseMaterial() == null) return false; + + block.setType(material.parseMaterial(), applyPhysics); + if (XMaterial.supports(13)) return false; + + String parsedName = material.parseMaterial().name(); + if (parsedName.endsWith("_ITEM")) { + String blockName = parsedName.substring(0, parsedName.length() - "_ITEM".length()); + Material blockMaterial = Objects.requireNonNull(Material.getMaterial(blockName), () -> "Could not find block material for item '" + parsedName + "' as '" + blockName + '\''); + block.setType(blockMaterial, applyPhysics); + }else if (parsedName.contains("CAKE")) { + Material blockMaterial = Material.getMaterial("CAKE_BLOCK"); + block.setType(blockMaterial, applyPhysics); + } + + LegacyMaterial legacyMaterial = LegacyMaterial.getMaterial(parsedName); + if (legacyMaterial == LegacyMaterial.BANNER) block.setType(LegacyMaterial.STANDING_BANNER.material, applyPhysics); + LegacyMaterial.Handling handling = legacyMaterial == null ? null : legacyMaterial.handling; + + BlockState state = block.getState(); + boolean update = false; + + if (handling == LegacyMaterial.Handling.COLORABLE) { + if (state instanceof Banner) { + Banner banner = (Banner) state; + String xName = material.name(); + int colorIndex = xName.indexOf('_'); + String color = xName.substring(0, colorIndex); + if (color.equals("LIGHT")) color = xName.substring(0, "LIGHT_".length() + 4); + + banner.setBaseColor(DyeColor.valueOf(color)); + }else state.setRawData(material.getData()); + update = true; + }else if (handling == LegacyMaterial.Handling.WOOD_SPECIES) { + // Wood doesn't exist in 1.8 + // https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/browse/src/main/java/org/bukkit/material/Wood.java?until=7d83cba0f2575112577ed7a091ed8a193bfc261a&untilPath=src%2Fmain%2Fjava%2Forg%2Fbukkit%2Fmaterial%2FWood.java + // https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/browse/src/main/java/org/bukkit/TreeSpecies.java + + String name = material.name(); + int firstIndicator = name.indexOf('_'); + if (firstIndicator < 0) return false; + String woodType = name.substring(0, firstIndicator); + + TreeSpecies species; + switch (woodType) { + case "OAK": + species = TreeSpecies.GENERIC; + break; + case "DARK": + species = TreeSpecies.DARK_OAK; + break; + case "SPRUCE": + species = TreeSpecies.REDWOOD; + break; + default: { + try { + species = TreeSpecies.valueOf(woodType); + }catch (IllegalArgumentException ex) { + throw new AssertionError("Unknown material " + legacyMaterial + " for wood species"); + } + } + } + + // Doesn't handle stairs, slabs, fence and fence gates as they had their own separate materials. + boolean firstType = false; + switch (legacyMaterial) { + case WOOD: + case WOOD_DOUBLE_STEP: + state.setRawData(species.getData()); + update = true; + break; + case LOG: + case LEAVES: + firstType = true; + // fall through to next switch statement below + case LOG_2: + case LEAVES_2: + switch (species) { + case GENERIC: + case REDWOOD: + case BIRCH: + case JUNGLE: + if (!firstType) throw new AssertionError("Invalid tree species " + species + " for block type" + legacyMaterial + ", use block type 2 instead"); + break; + case ACACIA: + case DARK_OAK: + if (firstType) throw new AssertionError("Invalid tree species " + species + " for block type 2 " + legacyMaterial + ", use block type instead"); + break; + } + state.setRawData((byte) ((state.getRawData() & 0xC) | (species.getData() & 0x3))); + update = true; + break; + case SAPLING: + case WOOD_STEP: + state.setRawData((byte) ((state.getRawData() & 0x8) | species.getData())); + update = true; + break; + default: + throw new AssertionError("Unknown block type " + legacyMaterial + " for tree species: " + species); + } + }else if (material.getData() != 0) { + state.setRawData(material.getData()); + update = true; + } + + if (update) state.update(false, applyPhysics); + return update; + } + + public static boolean setType(@Nonnull Block block, @Nullable XMaterial material) { + return setType(block, material, true); + } + public static int getAge(Block block) { if (ISFLAT) { if (!(block.getBlockData() instanceof org.bukkit.block.data.Ageable)) return 0; @@ -238,8 +376,10 @@ public static int getAge(Block block) { public static void setAge(Block block, int age) { if (ISFLAT) { if (!(block.getBlockData() instanceof org.bukkit.block.data.Ageable)) return; - org.bukkit.block.data.Ageable ageable = (org.bukkit.block.data.Ageable) block.getBlockData(); + BlockData data = block.getBlockData(); + org.bukkit.block.data.Ageable ageable = (org.bukkit.block.data.Ageable) data; ageable.setAge(age); + block.setBlockData(data, false); } BlockState state = block.getState(); @@ -249,12 +389,13 @@ public static void setAge(Block block, int age) { } /** - * Sets the type of any block that can be colored. - * - * @param block the block to color. - * @param color the color to use. - * @return true if the block can be colored, otherwise false. - */ + * Sets the type of any block that can be colored. + * + * @param block the block to color. + * @param color the color to use. + * + * @return true if the block can be colored, otherwise false. + */ public static boolean setColor(Block block, DyeColor color) { if (ISFLAT) { String type = block.getType().name(); @@ -279,13 +420,16 @@ public static boolean setColor(Block block, DyeColor color) { * * @param block the block to set the fluid level of. * @param level the level of fluid. + * * @return true if this block can have a fluid level, otherwise false. */ public static boolean setFluidLevel(Block block, int level) { if (ISFLAT) { if (!(block.getBlockData() instanceof org.bukkit.block.data.Levelled)) return false; - org.bukkit.block.data.Levelled levelled = (org.bukkit.block.data.Levelled) block.getBlockData(); + BlockData data = block.getBlockData(); + org.bukkit.block.data.Levelled levelled = (org.bukkit.block.data.Levelled) data; levelled.setLevel(level); + block.setBlockData(data, false); return true; } @@ -350,12 +494,12 @@ public static boolean isOneOf(Block block, Collection blocks) { public static void setCakeSlices(Block block, int amount) { Validate.isTrue(isCake(block.getType()), "Block is not a cake: " + block.getType()); if (ISFLAT) { - org.bukkit.block.data.BlockData bd = block.getBlockData(); - org.bukkit.block.data.type.Cake cake = (org.bukkit.block.data.type.Cake) bd; + BlockData data = block.getBlockData(); + org.bukkit.block.data.type.Cake cake = (org.bukkit.block.data.type.Cake) data; int remaining = cake.getMaximumBites() - (cake.getBites() + amount); if (remaining > 0) { cake.setBites(remaining); - block.setBlockData(bd); + block.setBlockData(data); }else { block.breakNaturally(); } @@ -376,14 +520,14 @@ public static void setCakeSlices(Block block, int amount) { public static int addCakeSlices(Block block, int slices) { Validate.isTrue(isCake(block.getType()), "Block is not a cake: " + block.getType()); if (ISFLAT) { - org.bukkit.block.data.BlockData bd = block.getBlockData(); - org.bukkit.block.data.type.Cake cake = (org.bukkit.block.data.type.Cake) bd; + BlockData data = block.getBlockData(); + org.bukkit.block.data.type.Cake cake = (org.bukkit.block.data.type.Cake) data; int bites = cake.getBites() - slices; int remaining = cake.getMaximumBites() - bites; if (remaining > 0) { cake.setBites(bites); - block.setBlockData(bd); + block.setBlockData(data); return remaining; }else { block.breakNaturally(); @@ -403,19 +547,6 @@ public static int addCakeSlices(Block block, int slices) { block.breakNaturally(); return 0; } - } - - public static boolean setWooden(Block block, XMaterial species) { - block.setType(species.parseMaterial()); - if (ISFLAT) return true; - - TreeSpecies type = species == XMaterial.SPRUCE_LOG ? TreeSpecies.REDWOOD : TreeSpecies.valueOf(species.name().substring(0, species.name().indexOf('_'))); - - BlockState state = block.getState(); - MaterialData data = state.getData(); - ((Wood) data).setSpecies(type); - state.update(true); - return true; } public static void setEnderPearlOnFrame(Block endPortalFrame, boolean eye) { @@ -433,6 +564,7 @@ public static void setEnderPearlOnFrame(Block endPortalFrame, boolean eye) { /** * @param block the block to get its XMaterial type. + * * @return the XMaterial of the block. * @deprecated Not stable, use {@link #isType(Block, XMaterial)} or {@link #isSimilar(Block, XMaterial)} instead. * If you want to save a block material somewhere, you need to use {@link XMaterial#matchXMaterial(Material)} @@ -443,34 +575,37 @@ public static XMaterial getType(Block block) { String type = block.getType().name(); BlockState state = block.getState(); MaterialData data = state.getData(); + byte dataValue; if (data instanceof Wood) { TreeSpecies species = ((Wood) data).getSpecies(); - return XMaterial.matchXMaterial(species.name() + block.getType().name()) - .orElseThrow(() -> new IllegalArgumentException("Unsupported material from tree species " + species.name() + ": " + block.getType().name())); - } - if (data instanceof Colorable) { - Colorable color = (Colorable) data; - return XMaterial.matchXMaterial(color.getColor().name() + '_' + type).orElseThrow(() -> new IllegalArgumentException("Unsupported colored material")); + dataValue = species.getData(); + }else if (data instanceof Colorable) { + DyeColor color = ((Colorable) data).getColor(); + dataValue = color.getDyeData(); + }else { + dataValue = data.getData(); } - return XMaterial.matchXMaterial(block.getType()); - } - - /** + + return XMaterial.matchDefinedXMaterial(type, dataValue).orElseThrow(() -> new IllegalArgumentException("Unsupported material for block " + dataValue + ": " + block.getType().name())); + } + + /** * Same as {@link #isType(Block, XMaterial)} except it also does a simple {@link XMaterial#matchXMaterial(Material)} * comparison with the given block and material. * * @param block the block to compare. * @param material the material to compare with. + * * @return true if block type is similar to the given material. * @see #isType(Block, XMaterial) * @since 1.3.0 */ public static boolean isSimilar(Block block, XMaterial material) { return material == XMaterial.matchXMaterial(block.getType()) || isType(block, material); - } - - /** + } + + /** * Universal Method *

* Check if the block type matches the specified XMaterial. @@ -479,6 +614,7 @@ public static boolean isSimilar(Block block, XMaterial material) { * * @param block the block to check. * @param material the XMaterial similar to this block type. + * * @return true if the raw block type matches with the material. * @see #isSimilar(Block, XMaterial) */ @@ -515,14 +651,23 @@ public static boolean isType(Block block, XMaterial material) { case CAVE_AIR: case VOID_AIR: return isAir(mat); - default: - return mat.equals(material.parseMaterial()); // EDITED FOR BEAUTYQUESTS } + return false; } public static boolean isAir(@Nullable Material material) { - if (material == Material.AIR) return true; - return ISFLAT && (material == Material.CAVE_AIR || material == Material.VOID_AIR); + if (ISFLAT) { + // material.isAir() doesn't exist for 1.13 + switch (material) { + case AIR: + case CAVE_AIR: + case VOID_AIR: + return true; + default: + return false; + } + } + return material == Material.AIR; } public static boolean isPowered(Block block) { @@ -540,8 +685,10 @@ public static boolean isPowered(Block block) { public static void setPowered(Block block, boolean powered) { if (ISFLAT) { if (!(block.getBlockData() instanceof org.bukkit.block.data.Powerable)) return; - org.bukkit.block.data.Powerable powerable = (org.bukkit.block.data.Powerable) block.getBlockData(); + BlockData data = block.getBlockData(); + org.bukkit.block.data.Powerable powerable = (org.bukkit.block.data.Powerable) data; powerable.setPowered(powered); + block.setBlockData(data, false); return; } @@ -565,8 +712,12 @@ public static boolean isOpen(Block block) { public static void setOpened(Block block, boolean opened) { if (ISFLAT) { if (!(block.getBlockData() instanceof org.bukkit.block.data.Openable)) return; - org.bukkit.block.data.Openable openable = (org.bukkit.block.data.Openable) block.getBlockData(); + // These useless "data" variables are used because JVM doesn't like upcasts/downcasts for + // non-existing classes even if unused. + BlockData data = block.getBlockData(); + org.bukkit.block.data.Openable openable = (org.bukkit.block.data.Openable) data; openable.setOpen(opened); + block.setBlockData(data, false); return; } @@ -591,8 +742,10 @@ public static BlockFace getRotation(Block block) { public static void setRotation(Block block, BlockFace facing) { if (ISFLAT) { if (!(block.getBlockData() instanceof org.bukkit.block.data.Rotatable)) return; - org.bukkit.block.data.Rotatable rotatable = (org.bukkit.block.data.Rotatable) block.getBlockData(); + BlockData data = block.getBlockData(); + org.bukkit.block.data.Rotatable rotatable = (org.bukkit.block.data.Rotatable) data; rotatable.setRotation(facing); + block.setBlockData(data, false); } } @@ -604,6 +757,69 @@ private static boolean isMaterial(Block block, BlockMaterial... materials) { return false; } + private enum LegacyMaterial { + // Colorable + STANDING_BANNER( + Handling.COLORABLE), + WALL_BANNER( + Handling.COLORABLE), + BANNER( + Handling.COLORABLE), + CARPET( + Handling.COLORABLE), + WOOL( + Handling.COLORABLE), + STAINED_CLAY( + Handling.COLORABLE), + STAINED_GLASS( + Handling.COLORABLE), + STAINED_GLASS_PANE( + Handling.COLORABLE), + THIN_GLASS( + Handling.COLORABLE), + + // Wood Species + WOOD( + Handling.WOOD_SPECIES), + WOOD_STEP( + Handling.WOOD_SPECIES), + WOOD_DOUBLE_STEP( + Handling.WOOD_SPECIES), + LEAVES( + Handling.WOOD_SPECIES), + LEAVES_2( + Handling.WOOD_SPECIES), + LOG( + Handling.WOOD_SPECIES), + LOG_2( + Handling.WOOD_SPECIES), + SAPLING( + Handling.WOOD_SPECIES); + + private static final Map LOOKUP = new HashMap<>(); + + static { + for (LegacyMaterial legacyMaterial : values()) { + LOOKUP.put(legacyMaterial.name(), legacyMaterial); + } + } + + private final Material material = Material.getMaterial(name()); + private final Handling handling; + + LegacyMaterial(Handling handling) { + this.handling = handling; + } + + private static LegacyMaterial getMaterial(String name) { + return LOOKUP.get(name); + } + + private enum Handling { + COLORABLE, WOOD_SPECIES; + } + } + /** * An enum with cached legacy materials which can be used when comparing blocks with blocks and blocks with items. * From e38f198e9849e0669af78f8e36ca57552f7fb1d1 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Fri, 27 May 2022 21:11:02 +0200 Subject: [PATCH 015/111] Added death stage --- .../quests/api/stages/AbstractStage.java | 2 +- .../quests/editors/TextListEditor.java | 11 +- .../quests/gui/creation/stages/StagesGUI.java | 34 +++--- .../quests/gui/misc/DamageCausesGUI.java | 76 ++++++++++++ .../fr/skytasul/quests/stages/StageDeath.java | 109 ++++++++++++++++++ .../java/fr/skytasul/quests/utils/Lang.java | 16 ++- .../skytasul/quests/utils/types/BQBlock.java | 2 +- core/src/main/resources/locales/en_US.yml | 11 ++ 8 files changed, 236 insertions(+), 25 deletions(-) create mode 100644 core/src/main/java/fr/skytasul/quests/gui/misc/DamageCausesGUI.java create mode 100644 core/src/main/java/fr/skytasul/quests/stages/StageDeath.java diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java index 1c63afb0..889b2488 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java @@ -175,7 +175,7 @@ private void propagateStageHandlers(Consumer consumer) { * @param p Player who finish the stage */ protected final void finishStage(Player p) { - branch.finishStage(p, this); + Utils.runSync(() -> branch.finishStage(p, this)); } /** diff --git a/core/src/main/java/fr/skytasul/quests/editors/TextListEditor.java b/core/src/main/java/fr/skytasul/quests/editors/TextListEditor.java index 75ee65ff..82e99e45 100644 --- a/core/src/main/java/fr/skytasul/quests/editors/TextListEditor.java +++ b/core/src/main/java/fr/skytasul/quests/editors/TextListEditor.java @@ -1,9 +1,9 @@ package fr.skytasul.quests.editors; import java.util.List; +import java.util.StringJoiner; import java.util.function.Consumer; import java.util.function.Predicate; -import java.util.stream.Collectors; import org.apache.commons.lang.Validate; import org.bukkit.entity.Player; @@ -74,10 +74,11 @@ public boolean chat(String coloredMessage, String strippedMessage){ break; case LIST: - p.sendMessage(texts - .stream() - .map(text -> "§7- §r" + text) - .collect(Collectors.joining("\n", "§6§lList:\n", ""))); + StringJoiner joiner = new StringJoiner("\n", "§6§lList:\n", ""); + for (int i = 0; i < texts.size(); i++) { + joiner.add("§7" + i + "- §r" + texts.get(i)); + } + p.sendMessage(joiner.toString()); break; case HELP: diff --git a/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java b/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java index 45916272..6fd1e156 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java @@ -326,25 +326,27 @@ private void editBranch(QuestBranch branch){ private static final ItemStack stagePlayTime = ItemUtils.item(XMaterial.CLOCK, Lang.stagePlayTime.toString()); private static final ItemStack stageBreed = ItemUtils.item(XMaterial.WHEAT, Lang.stageBreedAnimals.toString()); private static final ItemStack stageTame = ItemUtils.item(XMaterial.CARROT, Lang.stageTameAnimals.toString()); + private static final ItemStack stageDeath = ItemUtils.item(XMaterial.SKELETON_SKULL, Lang.stageDeath.toString()); public static void initialize(){ DebugUtils.logMessage("Initlializing default stage types."); - QuestsAPI.registerStage(new StageType<>("NPC", StageNPC.class, Lang.Talk.name(), StageNPC::deserialize, stageNPC, StageNPC.Creator::new)); - QuestsAPI.registerStage(new StageType<>("ITEMS", StageBringBack.class, Lang.Items.name(), StageBringBack::deserialize, stageItems, StageBringBack.Creator::new)); - QuestsAPI.registerStage(new StageType<>("MOBS", StageMobs.class, Lang.Mobs.name(), StageMobs::deserialize, stageMobs, StageMobs.Creator::new)); - QuestsAPI.registerStage(new StageType<>("MINE", StageMine.class, Lang.Mine.name(), StageMine::deserialize, stageMine, StageMine.Creator::new)); - QuestsAPI.registerStage(new StageType<>("PLACE_BLOCKS", StagePlaceBlocks.class, Lang.Place.name(), StagePlaceBlocks::deserialize, stagePlace, StagePlaceBlocks.Creator::new)); - QuestsAPI.registerStage(new StageType<>("CHAT", StageChat.class, Lang.Chat.name(), StageChat::deserialize, stageChat, StageChat.Creator::new)); - QuestsAPI.registerStage(new StageType<>("INTERACT", StageInteract.class, Lang.Interact.name(), StageInteract::deserialize, stageInteract, StageInteract.Creator::new)); - QuestsAPI.registerStage(new StageType<>("FISH", StageFish.class, Lang.Fish.name(), StageFish::deserialize, stageFish, StageFish.Creator::new)); - QuestsAPI.registerStage(new StageType<>("MELT", StageMelt.class, Lang.Melt.name(), StageMelt::deserialize, stageMelt, StageMelt.Creator::new)); - QuestsAPI.registerStage(new StageType<>("ENCHANT", StageEnchant.class, Lang.Enchant.name(), StageEnchant::deserialize, stageEnchant, StageEnchant.Creator::new)); - QuestsAPI.registerStage(new StageType<>("CRAFT", StageCraft.class, Lang.Craft.name(), StageCraft::deserialize, stageCraft, StageCraft.Creator::new)); - QuestsAPI.registerStage(new StageType<>("BUCKET", StageBucket.class, Lang.Bucket.name(), StageBucket::deserialize, stageBucket, StageBucket.Creator::new)); - QuestsAPI.registerStage(new StageType<>("LOCATION", StageLocation.class, Lang.Location.name(), StageLocation::deserialize, stageLocation, StageLocation.Creator::new)); - QuestsAPI.registerStage(new StageType<>("PLAY_TIME", StagePlayTime.class, Lang.PlayTime.name(), StagePlayTime::deserialize, stagePlayTime, StagePlayTime.Creator::new)); - QuestsAPI.registerStage(new StageType<>("BREED", StageBreed.class, Lang.Breed.name(), StageBreed::deserialize, stageBreed, StageBreed.Creator::new)); - QuestsAPI.registerStage(new StageType<>("TAME", StageTame.class, Lang.Tame.name(), StageTame::deserialize, stageTame, StageTame.Creator::new)); + QuestsAPI.getStages().register(new StageType<>("NPC", StageNPC.class, Lang.Talk.name(), StageNPC::deserialize, stageNPC, StageNPC.Creator::new)); + QuestsAPI.getStages().register(new StageType<>("ITEMS", StageBringBack.class, Lang.Items.name(), StageBringBack::deserialize, stageItems, StageBringBack.Creator::new)); + QuestsAPI.getStages().register(new StageType<>("MOBS", StageMobs.class, Lang.Mobs.name(), StageMobs::deserialize, stageMobs, StageMobs.Creator::new)); + QuestsAPI.getStages().register(new StageType<>("MINE", StageMine.class, Lang.Mine.name(), StageMine::deserialize, stageMine, StageMine.Creator::new)); + QuestsAPI.getStages().register(new StageType<>("PLACE_BLOCKS", StagePlaceBlocks.class, Lang.Place.name(), StagePlaceBlocks::deserialize, stagePlace, StagePlaceBlocks.Creator::new)); + QuestsAPI.getStages().register(new StageType<>("CHAT", StageChat.class, Lang.Chat.name(), StageChat::deserialize, stageChat, StageChat.Creator::new)); + QuestsAPI.getStages().register(new StageType<>("INTERACT", StageInteract.class, Lang.Interact.name(), StageInteract::deserialize, stageInteract, StageInteract.Creator::new)); + QuestsAPI.getStages().register(new StageType<>("FISH", StageFish.class, Lang.Fish.name(), StageFish::deserialize, stageFish, StageFish.Creator::new)); + QuestsAPI.getStages().register(new StageType<>("MELT", StageMelt.class, Lang.Melt.name(), StageMelt::deserialize, stageMelt, StageMelt.Creator::new)); + QuestsAPI.getStages().register(new StageType<>("ENCHANT", StageEnchant.class, Lang.Enchant.name(), StageEnchant::deserialize, stageEnchant, StageEnchant.Creator::new)); + QuestsAPI.getStages().register(new StageType<>("CRAFT", StageCraft.class, Lang.Craft.name(), StageCraft::deserialize, stageCraft, StageCraft.Creator::new)); + QuestsAPI.getStages().register(new StageType<>("BUCKET", StageBucket.class, Lang.Bucket.name(), StageBucket::deserialize, stageBucket, StageBucket.Creator::new)); + QuestsAPI.getStages().register(new StageType<>("LOCATION", StageLocation.class, Lang.Location.name(), StageLocation::deserialize, stageLocation, StageLocation.Creator::new)); + QuestsAPI.getStages().register(new StageType<>("PLAY_TIME", StagePlayTime.class, Lang.PlayTime.name(), StagePlayTime::deserialize, stagePlayTime, StagePlayTime.Creator::new)); + QuestsAPI.getStages().register(new StageType<>("BREED", StageBreed.class, Lang.Breed.name(), StageBreed::deserialize, stageBreed, StageBreed.Creator::new)); + QuestsAPI.getStages().register(new StageType<>("TAME", StageTame.class, Lang.Tame.name(), StageTame::deserialize, stageTame, StageTame.Creator::new)); + QuestsAPI.getStages().register(new StageType<>("DEATH", StageDeath.class, Lang.Death.name(), StageDeath::deserialize, stageDeath, StageDeath.Creator::new)); } } \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/gui/misc/DamageCausesGUI.java b/core/src/main/java/fr/skytasul/quests/gui/misc/DamageCausesGUI.java new file mode 100644 index 00000000..f2abf747 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/gui/misc/DamageCausesGUI.java @@ -0,0 +1,76 @@ +package fr.skytasul.quests.gui.misc; + +import java.util.EnumMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.bukkit.DyeColor; +import org.bukkit.event.entity.EntityDamageEvent.DamageCause; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.ItemStack; + +import fr.skytasul.quests.gui.ItemUtils; +import fr.skytasul.quests.gui.templates.ListGUI; +import fr.skytasul.quests.gui.templates.PagedGUI; +import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.XMaterial; + +public class DamageCausesGUI extends ListGUI { + + private static final Map MAPPED_ITEMS; + + static { + MAPPED_ITEMS = new EnumMap<>(DamageCause.class); + for (DamageCause cause : DamageCause.values()) { + XMaterial type; + if (cause.name().length() <= 6) { + type = XMaterial.CREEPER_HEAD; + }else if (cause.name().length() < 10) { + type = XMaterial.ZOMBIE_HEAD; + }else if (cause.name().length() < 14) { + type = XMaterial.SKELETON_SKULL; + }else { + type = XMaterial.WITHER_SKELETON_SKULL; + } + // this is simply not to have a fully uniform list + MAPPED_ITEMS.put(cause, ItemUtils.item(type, "§7" + cause.name())); + } + } + + private final Consumer> end; + + public DamageCausesGUI(List causes, Consumer> end) { + super(Lang.INVENTORY_DAMAGE_CAUSE.toString(), DyeColor.RED, causes); + this.end = end; + } + + @Override + public void finish(List objects) { + end.accept(objects); + } + + @Override + public ItemStack getObjectItemStack(DamageCause object) { + return MAPPED_ITEMS.get(object); + } + + @Override + public void createObject(Function callback) { + new PagedGUI(Lang.INVENTORY_DAMAGE_CAUSES_LIST.toString(), DyeColor.ORANGE, MAPPED_ITEMS.keySet()) { + + @Override + public ItemStack getItemStack(DamageCause object) { + return MAPPED_ITEMS.get(object); + } + + @Override + public void click(DamageCause existing, ItemStack item, ClickType clickType) { + callback.apply(existing); + } + + }.create(p); + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageDeath.java b/core/src/main/java/fr/skytasul/quests/stages/StageDeath.java new file mode 100644 index 00000000..95d63aca --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/stages/StageDeath.java @@ -0,0 +1,109 @@ +package fr.skytasul.quests.stages; + +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.entity.EntityDamageEvent; +import org.bukkit.event.entity.EntityDamageEvent.DamageCause; +import org.bukkit.event.entity.PlayerDeathEvent; + +import fr.skytasul.quests.api.stages.AbstractStage; +import fr.skytasul.quests.api.stages.StageCreation; +import fr.skytasul.quests.gui.ItemUtils; +import fr.skytasul.quests.gui.creation.stages.Line; +import fr.skytasul.quests.gui.misc.DamageCausesGUI; +import fr.skytasul.quests.players.PlayerAccount; +import fr.skytasul.quests.structure.QuestBranch; +import fr.skytasul.quests.structure.QuestBranch.Source; +import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.XMaterial; + +public class StageDeath extends AbstractStage { + + private List causes; + + public StageDeath(QuestBranch branch, List causes) { + super(branch); + this.causes = causes; + } + + @EventHandler + public void onPlayerDeath(PlayerDeathEvent event) { + Player p = event.getEntity(); + if (!hasStarted(p)) return; + + if (!causes.isEmpty()) { + EntityDamageEvent lastDamage = p.getLastDamageCause(); + if (lastDamage == null) return; + if (!causes.contains(lastDamage.getCause())) return; + } + + if (canUpdate(p, true)) finishStage(p); + } + + @Override + protected String descriptionLine(PlayerAccount acc, Source source) { + return Lang.SCOREBOARD_DIE.toString(); + } + + @Override + protected void serialize(ConfigurationSection section) { + if (!causes.isEmpty()) section.set("causes", causes.stream().map(DamageCause::name).collect(Collectors.toList())); + } + + public static StageDeath deserialize(ConfigurationSection section, QuestBranch branch) { + List causes; + if (section.contains("causes")) { + causes = section.getStringList("causes").stream().map(DamageCause::valueOf).collect(Collectors.toList()); + }else { + causes = Collections.emptyList(); + } + return new StageDeath(branch, causes); + } + + public static class Creator extends StageCreation { + + private static final int CAUSES_SLOT = 7; + + private List causes; + + public Creator(Line line, boolean ending) { + super(line, ending); + + line.setItem(CAUSES_SLOT, ItemUtils.item(XMaterial.SKELETON_SKULL, Lang.stageDeathCauses.toString()), (p, item) -> { + new DamageCausesGUI(causes, newCauses -> { + setCauses(newCauses); + reopenGUI(p, true); + }).create(p); + }); + } + + public void setCauses(List causes) { + this.causes = causes; + line.editItem(CAUSES_SLOT, ItemUtils.lore(line.getItem(CAUSES_SLOT), Lang.optionValue.format(causes.isEmpty() ? Lang.stageDeathCauseAny : Lang.stageDeathCausesSet.format(causes.size())))); + } + + @Override + public void start(Player p) { + super.start(p); + setCauses(Collections.emptyList()); + } + + @Override + public void edit(StageDeath stage) { + super.edit(stage); + setCauses(stage.causes); + } + + @Override + protected StageDeath finishStage(QuestBranch branch) { + return new StageDeath(branch, causes); + } + + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/utils/Lang.java b/core/src/main/java/fr/skytasul/quests/utils/Lang.java index 320cb7ce..ccf485e3 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Lang.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Lang.java @@ -354,6 +354,7 @@ public enum Lang implements Locale { stagePlayTime("inv.create.playTime"), stageBreedAnimals("inv.create.breedAnimals"), stageTameAnimals("inv.create.tameAnimals"), + stageDeath("inv.create.death"), stageText("inv.create.NPCText"), dialogLines("inv.create.dialogLines"), // 0: lines stageNPCSelect("inv.create.NPCSelect"), @@ -383,13 +384,18 @@ public enum Lang implements Locale { editItem("inv.create.editItem"), editBucketType("inv.create.editBucketType"), editBucketAmount("inv.create.editBucketAmount"), + changeTicksRequired("inv.create.changeTicksRequired"), + changeEntityType("inv.create.changeEntityType"), + stageLocationLocation("inv.create.editLocation"), stageLocationRadius("inv.create.editRadius"), stageLocationCurrentRadius("inv.create.currentRadius"), // 0: radius stageLocationWorldPattern("inv.create.stage.location.worldPattern"), stageLocationWorldPatternLore("inv.create.stage.location.worldPatternLore"), - changeTicksRequired("inv.create.changeTicksRequired"), - changeEntityType("inv.create.changeEntityType"), + + stageDeathCauses("inv.create.stage.death.causes"), + stageDeathCauseAny("inv.create.stage.death.anyCause"), + stageDeathCausesSet("inv.create.stage.death.setCauses"), // 0: causes amount INVENTORY_STAGES("inv.stages.name"), nextPage("inv.stages.nextPage"), @@ -659,6 +665,10 @@ public enum Lang implements Locale { INVENTORY_PARTICLE_LIST("inv.particleList.name"), particle_colored("inv.particleList.colored"), + INVENTORY_DAMAGE_CAUSE("inv.damageCause.name"), + + INVENTORY_DAMAGE_CAUSES_LIST("inv.damageCausesList.name"), + BOOK_NAME("inv.listBook.questName"), BOOK_STARTER("inv.listBook.questStarter"), BOOK_REWARDS("inv.listBook.questRewards"), @@ -693,6 +703,7 @@ public enum Lang implements Locale { SCOREBOARD_TAME("scoreboard.stage.tame"), // 0: animals to breed SCOREBOARD_LOCATION("scoreboard.stage.location"), // 0: x, 1: y, 2: z, 3: world SCOREBOARD_PLAY_TIME("scoreboard.stage.playTimeFormatted"), // 0: remaining time + SCOREBOARD_DIE("scoreboard.stage.die"), /* Indications */ @@ -739,6 +750,7 @@ public enum Lang implements Locale { PlayTime("misc.stageType.playTime"), Breed("misc.stageType.breedAnimals"), Tame("misc.stageType.tameAnimals"), + Death("misc.stageType.die"), ComparisonEquals("misc.comparison.equals"), ComparisonDifferent("misc.comparison.different"), diff --git a/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java b/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java index 11cac7c8..cd2e48aa 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java +++ b/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java @@ -92,7 +92,7 @@ public XMaterial retrieveMaterial() { @Override public boolean applies(Block block) { - return XBlock.isType(block, material); + return XBlock.isSimilar(block, material); } @Override diff --git a/core/src/main/resources/locales/en_US.yml b/core/src/main/resources/locales/en_US.yml index 48312491..cf78e697 100644 --- a/core/src/main/resources/locales/en_US.yml +++ b/core/src/main/resources/locales/en_US.yml @@ -367,6 +367,7 @@ inv: playTime: §ePlay time breedAnimals: §aBreed animals tameAnimals: §aTame animals + death: §cDie NPCText: §eEdit dialog dialogLines: '{0} lines' NPCSelect: §eChoose or create NPC @@ -405,6 +406,10 @@ inv: location: worldPattern: '§aSet world name pattern §d(ADVANCED)' worldPatternLore: 'If you want the stage to be completed for any world matching a specific pattern, enter a regex (regular expression) here.' + death: + causes: '§aSet death causes §d(ADVANCED)' + anyCause: Any death cause + setCauses: '{0} death cause(s)' stages: name: Create stages nextPage: §eNext page @@ -698,6 +703,10 @@ inv: particleList: name: Particles list colored: Colored particle + damageCause: + name: Damage cause + damageCausesList: + name: Damage causes list scoreboard: name: §6§lQuests @@ -725,6 +734,7 @@ scoreboard: playTimeFormatted: §ePlay §6{0} breed: §eBreed §6{0} tame: §eTame §6{0} + die: §cDie indication: startQuest: §7Do you want to start the quest {0}? closeInventory: §7Are you sure you want to close the GUI? @@ -776,6 +786,7 @@ misc: playTime: Play time breedAnimals: Breed animals tameAnimals: Tame animals + die: Die comparison: equals: equal to {0} different: different to {0} From 4dbffa6af3c4f3c476c50f0195e10a61cb76745a Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Fri, 27 May 2022 21:26:08 +0200 Subject: [PATCH 016/111] Updated FR locale --- .../skytasul/quests/editors/DialogEditor.java | 2 +- .../quests/editors/TextListEditor.java | 2 +- core/src/main/resources/locales/fr_FR.yml | 22 +++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/editors/DialogEditor.java b/core/src/main/java/fr/skytasul/quests/editors/DialogEditor.java index 220e772a..d35a1119 100644 --- a/core/src/main/java/fr/skytasul/quests/editors/DialogEditor.java +++ b/core/src/main/java/fr/skytasul/quests/editors/DialogEditor.java @@ -67,7 +67,7 @@ public boolean chat(String coloredMessage, String strippedMessage){ case LIST: for (int i = 0; i < d.messages.size(); i++) { Message dmsg = d.messages.get(i); - Utils.IsendMessage(p, "§6{0}: §7 \"{1}§7\"§e by §l{2}", false, i, dmsg.text, dmsg.sender.name().toLowerCase()); + Utils.IsendMessage(p, "§6{0}: §7\"{1}§7\"§e by §l{2}", false, i, dmsg.text, dmsg.sender.name().toLowerCase()); } break; diff --git a/core/src/main/java/fr/skytasul/quests/editors/TextListEditor.java b/core/src/main/java/fr/skytasul/quests/editors/TextListEditor.java index 82e99e45..a6f24252 100644 --- a/core/src/main/java/fr/skytasul/quests/editors/TextListEditor.java +++ b/core/src/main/java/fr/skytasul/quests/editors/TextListEditor.java @@ -76,7 +76,7 @@ public boolean chat(String coloredMessage, String strippedMessage){ case LIST: StringJoiner joiner = new StringJoiner("\n", "§6§lList:\n", ""); for (int i = 0; i < texts.size(); i++) { - joiner.add("§7" + i + "- §r" + texts.get(i)); + joiner.add("§6" + i + ": §r" + texts.get(i)); } p.sendMessage(joiner.toString()); break; diff --git a/core/src/main/resources/locales/fr_FR.yml b/core/src/main/resources/locales/fr_FR.yml index aa044354..1d7559fb 100644 --- a/core/src/main/resources/locales/fr_FR.yml +++ b/core/src/main/resources/locales/fr_FR.yml @@ -74,6 +74,7 @@ msg: negative: '§cTu dois entrer un nombre positif !' zero: '§cTu dois entrer un nombre autre que 0 !' invalid: '§c{0} n''est pas un nombre valide.' + notInBounds: '§cLa valeur doit être comprise entre {0} et {1}.' errorOccurred: '§cUne erreur est survenue, prévenez un administrateur ! §4§lCode d''erreur : {0}' commandsDisabled: '§cVous n''êtes actuellement pas autorisé à exécuter des commandes !' indexOutOfBounds: '§cLe numéro {0} est hors des limites ! Il doit se trouver entre {1} et {2}.' @@ -290,6 +291,8 @@ msg: fadeIn: 'Écrivez la durée du fondu d''entrée, en ticks (20 ticks = 1 seconde).' stay: 'Écrivez la durée où le titre reste affiché, en ticks (20 ticks = 1 seconde).' fadeOut: 'Écrivez la durée du fondu de sortie, en ticks (20 ticks = 1 seconde).' + color: 'Entrez une couleur au format hexadécimal (#XXXXX) ou RVB (ROUGE VERT BLEU).' + invalidColor: 'La couleur que vous avez saisie n''est pas valide. Elle doit être hexadécimale ou RVB.' firework: invalid: 'Cet objet n''est pas un feu d''artifice valide.' invalidHand: 'Vous devez tenir un feu d''artifice dans votre main principale.' @@ -332,6 +335,7 @@ inv: playTime: '§eTemps de jeu' breedAnimals: '§aFaire reproduire des animaux' tameAnimals: '§aApprivoiser des animaux' + death: '§cMourir' NPCText: '§eModifier le dialogue' dialogLines: '{0} lignes' NPCSelect: '§eChoisir ou créer un NPC' @@ -370,6 +374,10 @@ inv: location: worldPattern: '§aModifier le motif de nom de monde §d(AVANCÉ)' worldPatternLore: 'Si vous voulez que l''étape soit terminée pour un monde correspondant à un motif spécifique, entrez ici une regex (expression régulière).' + death: + causes: '§aDéfinir les causes de la mort §d(AVANCED)' + anyCause: Toute cause de mort + setCauses: '{0} cause(s) de mort' stages: name: Créer les étapes nextPage: '§ePage suivante' @@ -650,6 +658,18 @@ inv: fadeIn: '§aDurée du fondu d''entrée' stay: '§bDurée statique' fadeOut: '§aDurée du fondu de sortie' + particleEffect: + name: Créer un effet de particules + shape: '§dForme de l''effet' + type: '§eType de particules' + color: '§bCouleur des particules' + particleList: + name: Liste des particules + colored: Particule colorée + damageCause: + name: Cause de dégât + damageCausesList: + name: Liste des causes de dégât scoreboard: name: '§6§lQuêtes' noLaunched: '§cAucune quête en cours.' @@ -676,6 +696,7 @@ scoreboard: playTimeFormatted: '§eJoue §6{0}' breed: '§eFaites se reproduire §6{0}' tame: '§eApprivoisez §6{0}' + die: '§cMourez' indication: startQuest: '§7Voulez-vous commencer la quête {0} ?' closeInventory: '§7Êtes-vous sûr de vouloir fermer l''interface graphique ?' @@ -723,6 +744,7 @@ misc: playTime: Temps de jeu breedAnimals: Faire reproduire des animaux tameAnimals: Apprivoiser des animaux + die: Mourir comparison: equals: égal à {0} different: différent de {0} From 7c1e16c68f75a28dc8bb3b15dbb852107e46cd54 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sat, 4 Jun 2022 17:34:26 +0200 Subject: [PATCH 017/111] Fixed multiple block data issue --- .../java/fr/skytasul/quests/gui/blocks/SelectBlockGUI.java | 2 +- .../java/fr/skytasul/quests/options/OptionQuestItem.java | 7 +++++++ .../fr/skytasul/quests/utils/compatibility/Post1_13.java | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/gui/blocks/SelectBlockGUI.java b/core/src/main/java/fr/skytasul/quests/gui/blocks/SelectBlockGUI.java index 943ce1c3..0fa11617 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/blocks/SelectBlockGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/blocks/SelectBlockGUI.java @@ -73,7 +73,7 @@ public Inventory open(Player p){ private void updateTypeItem() { inv.setItem(TYPE_SLOT, item(type, Lang.materialName.format(type.name()))); - if (inv.getItem(TYPE_SLOT) == null || inv.getItem(3).getType() == Material.AIR) { // means that the material cannot be treated as an inventory item (ex: fire) + if (inv.getItem(TYPE_SLOT) == null || inv.getItem(TYPE_SLOT).getType() == Material.AIR) { // means that the material cannot be treated as an inventory item (ex: fire) inv.setItem(TYPE_SLOT, item(XMaterial.STONE, Lang.materialName.format(type.name()), QuestOption.formatDescription(Lang.materialNotItemLore.format(type.name())))); } if (tag == null) ItemUtils.addEnchant(inv.getItem(TYPE_SLOT), Enchantment.DURABILITY, 1); diff --git a/core/src/main/java/fr/skytasul/quests/options/OptionQuestItem.java b/core/src/main/java/fr/skytasul/quests/options/OptionQuestItem.java index 1202b6ad..95f0e2d1 100644 --- a/core/src/main/java/fr/skytasul/quests/options/OptionQuestItem.java +++ b/core/src/main/java/fr/skytasul/quests/options/OptionQuestItem.java @@ -64,6 +64,13 @@ public void click(FinishGUI gui, Player p, ItemStack item, int slot, ClickType c setValue(obj.parseItem()); } gui.inv.setItem(slot, ItemUtils.nameAndLore(getValue().clone(), Lang.customMaterial.toString(), getLore())); + ItemStack setItem = gui.inv.getItem(slot); + if (setItem == null || setItem.getType() == Material.AIR) { + // means that the material cannot be treated as an inventory item (ex: fire) + resetValue(); + Lang.INVALID_ITEM_TYPE.send(p); + gui.inv.setItem(slot, ItemUtils.nameAndLore(getValue().clone(), Lang.customMaterial.toString(), getLore())); + } gui.reopen(p); }, new MaterialParser(false, false)).passNullIntoEndConsumer().enter(); } diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/Post1_13.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/Post1_13.java index 762249b5..5b9d71f8 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/compatibility/Post1_13.java +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/Post1_13.java @@ -60,7 +60,7 @@ public XMaterial retrieveMaterial() { @Override public String getAsString() { - return BQBlock.BLOCKDATA_HEADER + data.getAsString(); + return BQBlock.BLOCKDATA_HEADER + data.getAsString(true); } } From 51dc4f197226562c2aa0c3396a1d2c34563a7a7e Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Tue, 7 Jun 2022 18:56:02 +0200 Subject: [PATCH 018/111] Locatable annotation --- .../quests/api/stages/types/Locatable.java | 40 +++++++++++++++++++ .../quests/utils/types/BQLocation.java | 6 +++ 2 files changed, 46 insertions(+) diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java index 2dc1431f..24f5dfe6 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java @@ -1,5 +1,9 @@ package fr.skytasul.quests.api.stages.types; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; import java.util.Collection; import org.bukkit.Location; @@ -85,10 +89,18 @@ public Class getTargetClass() { } + @Retention (RetentionPolicy.RUNTIME) + @Target (ElementType.TYPE) + @interface LocatableType { + LocatedType[] types() default { LocatedType.ENTITY, LocatedType.BLOCK, LocatedType.OTHER }; + } + interface Located { Location getLocation(); + LocatedType getType(); + static Located create(Location location) { return new LocatedImpl(location); } @@ -105,6 +117,11 @@ public Location getLocation() { return location.clone(); } + @Override + public LocatedType getType() { + return LocatedType.OTHER; + } + @Override public int hashCode() { return location.hashCode(); @@ -129,6 +146,11 @@ default Location getLocation() { return entity == null ? null : entity.getLocation(); } + @Override + default LocatedType getType() { + return LocatedType.ENTITY; + } + static LocatedEntity create(Entity entity) { return new LocatedEntityImpl(entity); } @@ -167,6 +189,11 @@ default Block getBlock() { return getLocation().getBlock(); } + @Override + default LocatedType getType() { + return LocatedType.BLOCK; + } + static LocatedBlock create(Block block) { return new LocatedBlockImpl(block.getLocation()); } @@ -184,10 +211,23 @@ public LocatedBlockImpl(Location location) { public Block getBlock() { return location.getBlock(); } + + @Override + public LocatedType getType() { + // As LocatedBlockImpl inherits getType() + // from both LocatedBlock AND LocatedImpl, + // we redefine the method correctly. + return LocatedType.BLOCK; + } + } } } + enum LocatedType { + BLOCK, ENTITY, OTHER; + } + } diff --git a/core/src/main/java/fr/skytasul/quests/utils/types/BQLocation.java b/core/src/main/java/fr/skytasul/quests/utils/types/BQLocation.java index 3adbee85..82324503 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/types/BQLocation.java +++ b/core/src/main/java/fr/skytasul/quests/utils/types/BQLocation.java @@ -12,6 +12,7 @@ import org.jetbrains.annotations.NotNull; import fr.skytasul.quests.api.stages.types.Locatable; +import fr.skytasul.quests.api.stages.types.Locatable.LocatedType; public class BQLocation extends Location implements Locatable.Located { @@ -59,6 +60,11 @@ public Location getLocation() { return new Location(getWorld(), getX(), getY(), getZ()); } + @Override + public LocatedType getType() { + return LocatedType.OTHER; + } + public boolean isWorld(World world) { Validate.notNull(world); if (super.getWorld() != null) return super.getWorld().equals(world); From 0d7ff79bad02656d59c689246d41f54dfffafd58 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sat, 11 Jun 2022 11:03:47 +0200 Subject: [PATCH 019/111] Updated updater --- core/pom.xml | 6 ++--- .../java/fr/skytasul/quests/BeautyQuests.java | 23 +++++++++++-------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index 9adff9db..0cfa6642 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -43,7 +43,7 @@ fr.skytasul.quests.utils.configupdater - de.jeff_media.updatechecker + com.jeff_media.updatechecker fr.skytasul.quests.utils.updatechecker @@ -229,9 +229,9 @@ provided - de.jeff_media + com.jeff_media SpigotUpdateChecker - 1.3.2 + 3.0.0 compile diff --git a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java index 1f762dc7..e3dd67e1 100644 --- a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java +++ b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java @@ -28,6 +28,8 @@ import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitRunnable; +import com.jeff_media.updatechecker.UpdateCheckSource; +import com.jeff_media.updatechecker.UpdateChecker; import com.tchristofferson.configupdater.ConfigUpdater; import fr.skytasul.quests.api.Locale; @@ -61,8 +63,6 @@ import fr.skytasul.quests.utils.logger.LoggerHandler; import fr.skytasul.quests.utils.nms.NMS; -import de.jeff_media.updatechecker.UpdateChecker; - public class BeautyQuests extends JavaPlugin { public static LoggerExpanded logger; @@ -293,26 +293,29 @@ private void launchMetrics(String pluginVersion) { private void launchUpdateChecker(String pluginVersion) { DebugUtils.logMessage("Starting Spigot updater"); + UpdateChecker checker; if (pluginVersion.contains("_")) { Matcher matcher = Pattern.compile("_BUILD(\\d+)").matcher(pluginVersion); if (matcher.find()) { String build = matcher.group(1); - UpdateChecker.init(instance, "https://ci.codemc.io/job/SkytAsul/job/BeautyQuests/lastSuccessfulBuild/buildNumber") + checker = new UpdateChecker(this, UpdateCheckSource.CUSTOM_URL, "https://ci.codemc.io/job/SkytAsul/job/BeautyQuests/lastSuccessfulBuild/buildNumber") .setUserAgent("") .setDownloadLink("https://ci.codemc.io/job/SkytAsul/job/BeautyQuests") - .setNotifyOpsOnJoin(false) .setUsedVersion(build) - .setNameFreeVersion("(dev builds)") - .checkNow(); + .setNameFreeVersion("(dev builds)"); }else { logger.warning("Unknown plugin version, cannot check for updates."); + return; } }else { - UpdateChecker.init(this, 39255) - .setDownloadLink(39255) - .setNotifyOpsOnJoin(false) - .checkNow(); + checker = new UpdateChecker(this, UpdateCheckSource.SPIGOT, "39255") + .setDownloadLink(39255); } + checker + .setDonationLink("https://paypal.me/SkytAsul") + .setSupportLink("https://discord.gg/H8fXrkD") + .setNotifyOpsOnJoin(false) + .checkNow(); } /* ---------- YAML ---------- */ From cfa8e843f5cc5e36b6f0a8abfe776b142cfeea9c Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 19 Jun 2022 15:50:02 +0200 Subject: [PATCH 020/111] Rework locatable * added javadoc to the Locatable API * changed the return type of MultipleLocatable#getNearbyLocated(...) fixed-size collection to a spliterator for better performance and flexibility * added the ability to show/hide location indications per-player --- .../types/AbstractCountableBlockStage.java | 4 +- .../api/stages/types/AbstractEntityStage.java | 14 +-- .../quests/api/stages/types/Locatable.java | 112 ++++++++++++++---- .../skytasul/quests/stages/StageInteract.java | 4 +- .../skytasul/quests/stages/StageLocation.java | 2 +- .../fr/skytasul/quests/stages/StageNPC.java | 4 +- .../skytasul/quests/utils/types/BQBlock.java | 79 ++++++++---- 7 files changed, 158 insertions(+), 61 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableBlockStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableBlockStage.java index 8b27e2cc..768740a4 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableBlockStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableBlockStage.java @@ -1,8 +1,8 @@ package fr.skytasul.quests.api.stages.types; -import java.util.Collection; import java.util.Map; import java.util.Map.Entry; +import java.util.Spliterator; import java.util.stream.Collectors; import org.bukkit.block.Block; @@ -46,7 +46,7 @@ protected BQBlock deserialize(Object object) { } @Override - public Collection getNearbyLocated(NearbyFetcher fetcher) { + public Spliterator getNearbyLocated(NearbyFetcher fetcher) { return BQBlock.getNearbyBlocks(fetcher, objects.values().stream().map(Entry::getKey).collect(Collectors.toList())); } diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java index 81393012..2004a5fe 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java @@ -1,14 +1,13 @@ package fr.skytasul.quests.api.stages.types; import java.util.AbstractMap; -import java.util.Collection; -import java.util.Collections; import java.util.Comparator; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.Spliterator; +import java.util.Spliterators; import java.util.function.Supplier; -import java.util.stream.Collectors; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.EntityType; @@ -93,8 +92,8 @@ public boolean canBeFetchedAsynchronously() { } @Override - public Collection getNearbyLocated(NearbyFetcher fetcher) { - if (!fetcher.getTargetClass().isAssignableFrom(Located.LocatedEntity.class)) return Collections.emptyList(); + public Spliterator getNearbyLocated(NearbyFetcher fetcher) { + if (!fetcher.isTargeting(LocatedType.ENTITY)) return Spliterators.emptySpliterator(); double distanceSquared = fetcher.getMaxDistance() * fetcher.getMaxDistance(); return fetcher.getCenter().getWorld() .getEntitiesByClass(entity.getEntityClass()) @@ -106,9 +105,8 @@ public Collection getNearbyLocated(NearbyFetcher fetcher) { }) .filter(Objects::nonNull) .sorted(Comparator.comparing(Entry::getValue)) - .limit(fetcher.getMaxAmount()) - .map(entry -> Located.LocatedEntity.create(entry.getKey())) - .collect(Collectors.toList()); + .map(entry -> Located.LocatedEntity.create(entry.getKey())) + .spliterator(); } public abstract static class AbstractCreator extends StageCreation { diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java index 24f5dfe6..7cbf7cf8 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java @@ -4,31 +4,70 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import java.util.Collection; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.Spliterator; import org.bukkit.Location; import org.bukkit.block.Block; import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +/** + * This interface indicates that an object can provide some locations on demand. + *

+ * Valid subinterfaces are {@link PreciseLocatable} and {@link MultipleLocatable}. + * A class must not directly implement this interface but one of those. + *

+ * Classes implementing one of the subinterfaces are also supposed to have the + * {@link LocatedType} annotation to indicate which types of located objects can be + * retrieved. If no annotation is attached, it will be assumed that all kinds of + * located objects can be retrieved. + */ public interface Locatable { - default boolean isShown() { + /** + * If no indication should be displayed to the player, + * then this method should return false. + * + * @param player Player to test for indications + * @return true if location indications should be displayed + * to the player, false otherwise. + */ + default boolean isShown(Player player) { return true; } + /** + * Indicates if the Located instances gotten from {@link PreciseLocatable#getLocated()} + * and {@link MultipleLocatable#getNearbyLocated(fr.skytasul.quests.api.stages.types.Locatable.MultipleLocatable.NearbyFetcher)} + * can be safely retrieved from an asynchronous thread. + * + * @return true only if the Located fetch operations can + * be done asynchronously, false otherwise. + */ default boolean canBeFetchedAsynchronously() { return true; } + /** + * This interface indicates that an object can provide a unique and precise location, + * no matter the player. + */ interface PreciseLocatable extends Locatable { Located getLocated(); } + /** + * This interface indicates that an object can provide multiple locations depending on + * factors such as a center and a maximum distance, detailed in {@link NearbyFetcher}. + */ interface MultipleLocatable extends Locatable { - Collection getNearbyLocated(NearbyFetcher fetcher); + Spliterator getNearbyLocated(NearbyFetcher fetcher); interface NearbyFetcher { @@ -36,31 +75,27 @@ interface NearbyFetcher { double getMaxDistance(); - int getMaxAmount(); - - default Class getTargetClass() { - return Located.class; + default boolean isTargeting(LocatedType type) { + return true; } - static NearbyFetcher create(Location location, double maxDistance, int maxAmount) { - return new NearbyFetcherImpl(location, maxDistance, maxAmount, Located.class); + static NearbyFetcher create(Location location, double maxDistance) { + return new NearbyFetcherImpl(location, maxDistance, null); } - static NearbyFetcher create(Location location, double maxDistance, int maxAmount, Class targetClass) { - return new NearbyFetcherImpl(location, maxDistance, maxAmount, targetClass); + static NearbyFetcher create(Location location, double maxDistance, LocatedType targetType) { + return new NearbyFetcherImpl(location, maxDistance, targetType); } class NearbyFetcherImpl implements NearbyFetcher { private Location center; private double maxDistance; - private int maxAmount; - private Class targetClass; + private LocatedType targetType; - public NearbyFetcherImpl(Location center, double maxDistance, int maxAmount, Class targetClass) { + public NearbyFetcherImpl(Location center, double maxDistance, LocatedType targetType) { this.center = center; this.maxDistance = maxDistance; - this.maxAmount = maxAmount; - this.targetClass = targetClass; + this.targetType = targetType; } @Override @@ -74,13 +109,8 @@ public double getMaxDistance() { } @Override - public int getMaxAmount() { - return maxAmount; - } - - @Override - public Class getTargetClass() { - return targetClass; + public boolean isTargeting(LocatedType type) { + return targetType == null || targetType == type; } } @@ -89,9 +119,18 @@ public Class getTargetClass() { } + /** + * This annotation indicates which types of {@link Located} objects can be retrieved from the attached class. + *

+ * It should only be attached to {@link Locatable} implementing classes. + */ @Retention (RetentionPolicy.RUNTIME) @Target (ElementType.TYPE) @interface LocatableType { + /** + * @return an array of {@link LocatedType} that can be retrieved + * from the class attached to this annotation. + */ LocatedType[] types() default { LocatedType.ENTITY, LocatedType.BLOCK, LocatedType.OTHER }; } @@ -230,4 +269,31 @@ enum LocatedType { BLOCK, ENTITY, OTHER; } + /** + * Allows to check if some {@link LocatedType}s can be retrieved from a {@link Locatable} class. + *

+ * If the Class clazz does not have a {@link LocatableType} annotation attached, + * then this will return true as it is assumed that it can retrieve all kinds of + * located types. + * + * @param clazz Class implementing the {@link Locatable} interface for which the types + * will get tested. + * @param types Array of {@link LocatedType}s that must be checked they can be retrieved from + * the clazz. + * @return true if the clazz can retrieve all passed types + * OR if the clazz does not have a {@link LocatableType} annotation attached, + * false otherwise. + */ + static boolean hasLocatedTypes(Class clazz, LocatedType... types) { + LocatableType annotation = clazz.getDeclaredAnnotation(Locatable.LocatableType.class); + if (annotation == null) return true; + + Set toTest = new HashSet<>(Arrays.asList(types)); + for (Locatable.LocatedType locatedType : annotation.types()) { + toTest.remove(locatedType); + if (toTest.isEmpty()) return true; + } + return false; + } + } diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java b/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java index 99fe7642..d33a91b9 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java @@ -1,7 +1,7 @@ package fr.skytasul.quests.stages; -import java.util.Collection; import java.util.Collections; +import java.util.Spliterator; import org.bukkit.Bukkit; import org.bukkit.Location; @@ -76,7 +76,7 @@ public Located getLocated() { } @Override - public Collection getNearbyLocated(NearbyFetcher fetcher) { + public Spliterator getNearbyLocated(NearbyFetcher fetcher) { if (block == null) return null; return BQBlock.getNearbyBlocks(fetcher, Collections.singleton(block)); diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageLocation.java b/core/src/main/java/fr/skytasul/quests/stages/StageLocation.java index d4f25742..23e113bd 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageLocation.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageLocation.java @@ -53,7 +53,7 @@ public BQLocation getLocation() { } @Override - public boolean isShown() { + public boolean isShown(Player player) { return isGPSEnabled(); } diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java b/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java index 125cbbfc..e276e950 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java @@ -81,7 +81,7 @@ public void run() { if (QuestsConfiguration.showTalkParticles()) { if (tmp.isEmpty()) return; - QuestsConfiguration.getParticleTalk().send((LivingEntity) en, tmp); + QuestsConfiguration.getParticleTalk().send(en, tmp); } } }.runTaskTimer(BeautyQuests.getInstance(), 20L, 6L); @@ -148,7 +148,7 @@ public void setHid(boolean hide){ } @Override - public boolean isShown() { + public boolean isShown(Player player) { return !hide; } diff --git a/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java b/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java index cd2e48aa..bef98b24 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java +++ b/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java @@ -1,14 +1,17 @@ package fr.skytasul.quests.utils.types; -import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; -import java.util.List; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Spliterator; +import java.util.Spliterators; import org.bukkit.block.Block; import fr.skytasul.quests.api.stages.types.Locatable; import fr.skytasul.quests.api.stages.types.Locatable.Located; +import fr.skytasul.quests.api.stages.types.Locatable.Located.LocatedBlock; +import fr.skytasul.quests.api.stages.types.Locatable.LocatedType; import fr.skytasul.quests.utils.MinecraftNames; import fr.skytasul.quests.utils.XBlock; import fr.skytasul.quests.utils.XMaterial; @@ -47,34 +50,64 @@ public static BQBlock fromString(String string) { return new BQBlockMaterial(XMaterial.valueOf(string)); } - public static Collection getNearbyBlocks(Locatable.MultipleLocatable.NearbyFetcher fetcher, Collection types) { - if (!fetcher.getTargetClass().isAssignableFrom(Located.LocatedBlock.class)) return Collections.emptyList(); - List blocks = new ArrayList<>(); + public static Spliterator getNearbyBlocks(Locatable.MultipleLocatable.NearbyFetcher fetcher, Collection types) { + if (!fetcher.isTargeting(LocatedType.BLOCK)) return Spliterators.emptySpliterator(); int minY = (int) Math.max(fetcher.getCenter().getWorld().getMinHeight(), fetcher.getCenter().getY() - fetcher.getMaxDistance()); double maxY = Math.min(fetcher.getCenter().getWorld().getMaxHeight(), fetcher.getCenter().getY() + fetcher.getMaxDistance()); int centerX = fetcher.getCenter().getBlockX(); int centerZ = fetcher.getCenter().getBlockZ(); - int x = centerX; - int z = centerZ; - spiral: for (int i = 0; i < fetcher.getMaxDistance() * fetcher.getMaxDistance(); i++) { - if (!fetcher.getCenter().getWorld().isChunkLoaded(x >> 4, z >> 4)) continue; - for (int y = minY; y <= maxY; y++) { - Block blockAt = fetcher.getCenter().getWorld().getBlockAt(x, y, z); - if (types.stream().anyMatch(type -> type.applies(blockAt))) { - blocks.add(Locatable.Located.LocatedBlock.create(blockAt)); - if (blocks.size() >= fetcher.getMaxAmount()) break spiral; + + return Spliterators.spliteratorUnknownSize(new Iterator() { + + int x = centerX; + int z = centerZ; + + int i = 0; + int y = minY; + + Locatable.Located.LocatedBlock found = null; + + private boolean findNext() { + for (; y <= maxY; y++) { + Block blockAt = fetcher.getCenter().getWorld().getBlockAt(x, y, z); + if (types.stream().anyMatch(type -> type.applies(blockAt))) { + found = Locatable.Located.LocatedBlock.create(blockAt); + return true; + } } + if (Math.abs(x - centerX) <= Math.abs(z - centerZ) && ((x - centerX) != (z - centerZ) || x >= centerX)) + x += ((z >= centerZ) ? 1 : -1); + else + z += ((x >= centerX) ? -1 : 1); + + i++; + if (i >= fetcher.getMaxDistance() * fetcher.getMaxDistance()) return false; + y = minY; + return findNext(); + + // used the N spiral algorithm from here: https://stackoverflow.com/a/31864777 } - if (Math.abs(x - centerX) <= Math.abs(z - centerZ) && ((x - centerX) != (z - centerZ) || x >= centerX)) - x += ((z >= centerZ) ? 1 : -1); - else - z += ((x >= centerX) ? -1 : 1); - } - - // used the N spiral algorithm from here: https://stackoverflow.com/a/31864777 - return blocks; + + @Override + public boolean hasNext() { + if (found != null) return true; + return findNext(); + } + + @Override + public Located next() { + if (found == null) findNext(); + if (found != null) { + LocatedBlock tmpFound = found; + found = null; + return tmpFound; + } + throw new NoSuchElementException(); + } + + }, Spliterator.ORDERED & Spliterator.IMMUTABLE & Spliterator.DISTINCT & Spliterator.NONNULL); } public static class BQBlockMaterial extends BQBlock { From 16a9e3c295924b285df9f364982e5107ae790f82 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 19 Jun 2022 16:26:32 +0200 Subject: [PATCH 021/111] Fix interaction stage finished twice, added @LocatableType annotations --- .../api/objects/QuestObjectsRegistry.java | 18 ++++++++++++++++-- .../types/AbstractCountableBlockStage.java | 3 +++ .../api/stages/types/AbstractEntityStage.java | 3 +++ .../quests/api/stages/types/Locatable.java | 7 ++++++- .../quests/rewards/CheckpointReward.java | 2 +- .../skytasul/quests/stages/StageInteract.java | 5 +++++ .../skytasul/quests/stages/StageLocation.java | 3 +++ .../fr/skytasul/quests/stages/StageNPC.java | 3 +++ .../skytasul/quests/utils/types/BQBlock.java | 1 + 9 files changed, 41 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectsRegistry.java b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectsRegistry.java index 34fd5943..7149ae00 100644 --- a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectsRegistry.java +++ b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectsRegistry.java @@ -3,6 +3,8 @@ import java.util.Collection; import java.util.List; import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Collectors; import fr.skytasul.quests.api.serializable.SerializableRegistry; import fr.skytasul.quests.gui.creation.QuestObjectGUI; @@ -16,12 +18,24 @@ public QuestObjectsRegistry(String id, String inventoryName) { this.inventoryName = inventoryName; } + public String getInventoryName() { + return inventoryName; + } + public QuestObjectGUI createGUI(QuestObjectLocation location, Consumer> end, List objects) { - return createGUI(inventoryName, location, end, objects); + return createGUI(inventoryName, location, end, objects, null); + } + + public QuestObjectGUI createGUI(QuestObjectLocation location, Consumer> end, List objects, Predicate filter) { + return createGUI(inventoryName, location, end, objects, filter); } public QuestObjectGUI createGUI(String name, QuestObjectLocation location, Consumer> end, List objects) { - return new QuestObjectGUI<>(name, location, (Collection>) creators, end, objects); + return createGUI(name, location, end, objects, null); + } + + public QuestObjectGUI createGUI(String name, QuestObjectLocation location, Consumer> end, List objects, Predicate filter) { + return new QuestObjectGUI<>(name, location, (Collection>) (filter == null ? creators : creators.stream().filter(filter).collect(Collectors.toList())), end, objects); } } diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableBlockStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableBlockStage.java index 768740a4..72a49bf1 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableBlockStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableBlockStage.java @@ -10,6 +10,8 @@ import org.bukkit.inventory.ItemStack; import fr.skytasul.quests.api.stages.StageCreation; +import fr.skytasul.quests.api.stages.types.Locatable.LocatableType; +import fr.skytasul.quests.api.stages.types.Locatable.LocatedType; import fr.skytasul.quests.gui.Inventories; import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.gui.blocks.BlocksGUI; @@ -18,6 +20,7 @@ import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.types.BQBlock; +@LocatableType (types = LocatedType.BLOCK) public abstract class AbstractCountableBlockStage extends AbstractCountableStage implements Locatable.MultipleLocatable { protected AbstractCountableBlockStage(QuestBranch branch, Map> objects) { diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java index 2004a5fe..8e20a857 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractEntityStage.java @@ -17,6 +17,8 @@ import fr.skytasul.quests.QuestsConfiguration; import fr.skytasul.quests.api.stages.AbstractStage; import fr.skytasul.quests.api.stages.StageCreation; +import fr.skytasul.quests.api.stages.types.Locatable.LocatableType; +import fr.skytasul.quests.api.stages.types.Locatable.LocatedType; import fr.skytasul.quests.editors.TextEditor; import fr.skytasul.quests.editors.checkers.NumberParser; import fr.skytasul.quests.gui.ItemUtils; @@ -31,6 +33,7 @@ import fr.skytasul.quests.utils.Utils; import fr.skytasul.quests.utils.XMaterial; +@LocatableType (types = LocatedType.ENTITY) public abstract class AbstractEntityStage extends AbstractStage implements Locatable.MultipleLocatable { protected EntityType entity; diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java index 7cbf7cf8..360f7798 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java @@ -14,6 +14,8 @@ import org.bukkit.entity.Entity; import org.bukkit.entity.Player; +import fr.skytasul.quests.utils.DebugUtils; + /** * This interface indicates that an object can provide some locations on demand. *

@@ -286,7 +288,10 @@ enum LocatedType { */ static boolean hasLocatedTypes(Class clazz, LocatedType... types) { LocatableType annotation = clazz.getDeclaredAnnotation(Locatable.LocatableType.class); - if (annotation == null) return true; + if (annotation == null) { + DebugUtils.logMessage("Class " + clazz.getName() + " does not have the @LocatableType annotation."); + return true; + } Set toTest = new HashSet<>(Arrays.asList(types)); for (Locatable.LocatedType locatedType : annotation.types()) { diff --git a/core/src/main/java/fr/skytasul/quests/rewards/CheckpointReward.java b/core/src/main/java/fr/skytasul/quests/rewards/CheckpointReward.java index e897d6f0..af0071f1 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/CheckpointReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/CheckpointReward.java @@ -66,7 +66,7 @@ public void itemClick(QuestObjectClickEvent event) { actions = rewards; event.updateItemLore(getLore()); event.reopenGUI(); - }, actions).create(event.getPlayer()); + }, actions, null).create(event.getPlayer()); } @Override diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java b/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java index d33a91b9..e83a0592 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java @@ -12,6 +12,7 @@ import org.bukkit.event.inventory.ClickType; import org.bukkit.event.inventory.InventoryType; import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; @@ -19,6 +20,8 @@ import fr.skytasul.quests.api.stages.AbstractStage; import fr.skytasul.quests.api.stages.StageCreation; import fr.skytasul.quests.api.stages.types.Locatable; +import fr.skytasul.quests.api.stages.types.Locatable.LocatableType; +import fr.skytasul.quests.api.stages.types.Locatable.LocatedType; import fr.skytasul.quests.editors.WaitBlockClick; import fr.skytasul.quests.gui.CustomInventory; import fr.skytasul.quests.gui.ItemUtils; @@ -33,6 +36,7 @@ import fr.skytasul.quests.utils.types.BQBlock; import fr.skytasul.quests.utils.types.BQLocation; +@LocatableType (types = { LocatedType.BLOCK, LocatedType.OTHER }) public class StageInteract extends AbstractStage implements Locatable.MultipleLocatable, Locatable.PreciseLocatable { private final boolean left; @@ -85,6 +89,7 @@ public Spliterator getNearbyLocated(NearbyFetcher fetcher) { @EventHandler public void onInteract(PlayerInteractEvent e){ if (e.getClickedBlock() == null) return; + if (e.getHand() != EquipmentSlot.HAND) return; if (left){ if (e.getAction() != Action.LEFT_CLICK_BLOCK) return; }else if (e.getAction() != Action.RIGHT_CLICK_BLOCK) return; diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageLocation.java b/core/src/main/java/fr/skytasul/quests/stages/StageLocation.java index 23e113bd..cce46378 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageLocation.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageLocation.java @@ -13,6 +13,8 @@ import fr.skytasul.quests.api.stages.AbstractStage; import fr.skytasul.quests.api.stages.StageCreation; import fr.skytasul.quests.api.stages.types.Locatable; +import fr.skytasul.quests.api.stages.types.Locatable.LocatableType; +import fr.skytasul.quests.api.stages.types.Locatable.LocatedType; import fr.skytasul.quests.editors.TextEditor; import fr.skytasul.quests.editors.WaitClick; import fr.skytasul.quests.editors.checkers.NumberParser; @@ -29,6 +31,7 @@ import fr.skytasul.quests.utils.compatibility.GPS; import fr.skytasul.quests.utils.types.BQLocation; +@LocatableType (types = LocatedType.OTHER) public class StageLocation extends AbstractStage implements Locatable.PreciseLocatable { private final BQLocation lc; diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java b/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java index e276e950..193555f0 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java @@ -25,6 +25,8 @@ import fr.skytasul.quests.api.stages.StageCreation; import fr.skytasul.quests.api.stages.types.Dialogable; import fr.skytasul.quests.api.stages.types.Locatable; +import fr.skytasul.quests.api.stages.types.Locatable.LocatableType; +import fr.skytasul.quests.api.stages.types.Locatable.LocatedType; import fr.skytasul.quests.editors.DialogEditor; import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.gui.creation.stages.Line; @@ -39,6 +41,7 @@ import fr.skytasul.quests.utils.types.Dialog; import fr.skytasul.quests.utils.types.DialogRunner; +@LocatableType (types = LocatedType.ENTITY) public class StageNPC extends AbstractStage implements Locatable.PreciseLocatable, Dialogable { private BQNPC npc; diff --git a/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java b/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java index bef98b24..9730e0e6 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java +++ b/core/src/main/java/fr/skytasul/quests/utils/types/BQBlock.java @@ -74,6 +74,7 @@ private boolean findNext() { Block blockAt = fetcher.getCenter().getWorld().getBlockAt(x, y, z); if (types.stream().anyMatch(type -> type.applies(blockAt))) { found = Locatable.Located.LocatedBlock.create(blockAt); + y++; return true; } } From 98c90d1734d0d339c9adfbba3fd6607bb983f0dc Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 19 Jun 2022 16:32:42 +0200 Subject: [PATCH 022/111] Fixed @LocatableType with superclasses --- .../quests/api/stages/types/Locatable.java | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java index 360f7798..b8e8205f 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java @@ -287,17 +287,26 @@ enum LocatedType { * false otherwise. */ static boolean hasLocatedTypes(Class clazz, LocatedType... types) { - LocatableType annotation = clazz.getDeclaredAnnotation(Locatable.LocatableType.class); - if (annotation == null) { + Set toTest = new HashSet<>(Arrays.asList(types)); + boolean foundAnnotation = false; + + Class superclass = clazz; + do { + LocatableType annotation = superclass.getDeclaredAnnotation(Locatable.LocatableType.class); + if (annotation != null) { + foundAnnotation = true; + for (Locatable.LocatedType locatedType : annotation.types()) { + toTest.remove(locatedType); + if (toTest.isEmpty()) return true; + } + } + }while ((superclass = superclass.getSuperclass()) != null); + + if (!foundAnnotation) { DebugUtils.logMessage("Class " + clazz.getName() + " does not have the @LocatableType annotation."); return true; } - Set toTest = new HashSet<>(Arrays.asList(types)); - for (Locatable.LocatedType locatedType : annotation.types()) { - toTest.remove(locatedType); - if (toTest.isEmpty()) return true; - } return false; } From c23f87269748adca2ba7bfa6f829487a3d7aab45 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Mon, 20 Jun 2022 21:19:18 +0200 Subject: [PATCH 023/111] :sparkles: Named color editor --- .../skytasul/quests/editors/TextEditor.java | 6 +++ .../editors/checkers/AbstractParser.java | 2 + .../editors/checkers/CollectionParser.java | 42 +++++++++++++++++ .../quests/editors/checkers/ColorParser.java | 13 +++++- .../quests/editors/checkers/EnumParser.java | 45 ++++++------------- .../quests/gui/particles/ParticleListGUI.java | 5 ++- .../java/fr/skytasul/quests/utils/Lang.java | 18 ++++---- core/src/main/resources/locales/en_US.yml | 2 + 8 files changed, 90 insertions(+), 43 deletions(-) create mode 100644 core/src/main/java/fr/skytasul/quests/editors/checkers/CollectionParser.java diff --git a/core/src/main/java/fr/skytasul/quests/editors/TextEditor.java b/core/src/main/java/fr/skytasul/quests/editors/TextEditor.java index 22304197..62c3d015 100644 --- a/core/src/main/java/fr/skytasul/quests/editors/TextEditor.java +++ b/core/src/main/java/fr/skytasul/quests/editors/TextEditor.java @@ -89,4 +89,10 @@ public boolean chat(String msg, String strippedMessage){ return false; } + @Override + protected void begin() { + super.begin(); + if (parser != null) parser.sendIndication(p); + } + } diff --git a/core/src/main/java/fr/skytasul/quests/editors/checkers/AbstractParser.java b/core/src/main/java/fr/skytasul/quests/editors/checkers/AbstractParser.java index b03c392b..68e4b172 100644 --- a/core/src/main/java/fr/skytasul/quests/editors/checkers/AbstractParser.java +++ b/core/src/main/java/fr/skytasul/quests/editors/checkers/AbstractParser.java @@ -6,4 +6,6 @@ public abstract interface AbstractParser { public abstract T parse(Player p, String msg) throws Throwable; + public default void sendIndication(Player p) {} + } diff --git a/core/src/main/java/fr/skytasul/quests/editors/checkers/CollectionParser.java b/core/src/main/java/fr/skytasul/quests/editors/checkers/CollectionParser.java new file mode 100644 index 00000000..dd280557 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/editors/checkers/CollectionParser.java @@ -0,0 +1,42 @@ +package fr.skytasul.quests.editors.checkers; + +import java.util.Collection; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.bukkit.entity.Player; + +import fr.skytasul.quests.utils.Lang; + +public class CollectionParser implements AbstractParser { + + protected Map names; + protected String namesString; + + public CollectionParser(Collection collection, Function namer) { + names = collection.stream().collect(Collectors.toMap(namer, Function.identity())); + namesString = String.join(", ", names.keySet()); + } + + @Override + public T parse(Player p, String msg) throws Throwable { + T obj = names.get(processName(msg)); + if (obj == null) Lang.NO_SUCH_ELEMENT.send(p, namesString); + return obj; + } + + protected String processName(String msg) { + return msg; + } + + @Override + public void sendIndication(Player p) { + Lang.AVAILABLE_ELEMENTS.send(p, namesString); + } + + public String getNames() { + return namesString; + } + +} \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/editors/checkers/ColorParser.java b/core/src/main/java/fr/skytasul/quests/editors/checkers/ColorParser.java index 81a1ca31..24389f71 100644 --- a/core/src/main/java/fr/skytasul/quests/editors/checkers/ColorParser.java +++ b/core/src/main/java/fr/skytasul/quests/editors/checkers/ColorParser.java @@ -3,6 +3,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import org.bukkit.ChatColor; import org.bukkit.Color; import org.bukkit.entity.Player; @@ -34,8 +35,16 @@ public Color parse(Player p, String msg) throws Throwable { green = Integer.parseInt(rgbMatcher.group(2)); blue = Integer.parseInt(rgbMatcher.group(3)); }else { - Lang.INVALID_COLOR.send(p); - return null; + try { + // just in case the user has entered a named color + java.awt.Color awtColor = ChatColor.valueOf(msg.toUpperCase().replace(' ', '_')).asBungee().getColor(); + red = awtColor.getRed(); + green = awtColor.getGreen(); + blue = awtColor.getBlue(); + }catch (IllegalArgumentException ex) { + Lang.INVALID_COLOR.send(p); + return null; + } } } return Color.fromRGB(red, green, blue); diff --git a/core/src/main/java/fr/skytasul/quests/editors/checkers/EnumParser.java b/core/src/main/java/fr/skytasul/quests/editors/checkers/EnumParser.java index e26b1407..7fd8014c 100644 --- a/core/src/main/java/fr/skytasul/quests/editors/checkers/EnumParser.java +++ b/core/src/main/java/fr/skytasul/quests/editors/checkers/EnumParser.java @@ -1,45 +1,28 @@ package fr.skytasul.quests.editors.checkers; -import java.util.HashMap; -import java.util.Map; +import java.util.Arrays; +import java.util.function.Predicate; import java.util.regex.Pattern; +import java.util.stream.Collectors; -import org.bukkit.entity.Player; - -import fr.skytasul.quests.utils.Lang; - -public class EnumParser> implements AbstractParser { +public class EnumParser> extends CollectionParser { private static final Pattern FORMAT = Pattern.compile("[ _]"); - private Map names; - private String namesString; - public EnumParser(Class enumClass) { - try { - T[] values = (T[]) enumClass.getDeclaredMethod("values").invoke(null); - names = new HashMap<>(values.length + 1, 1); - for (T value : values) { - names.put(proceed(value.name()), value); - } - namesString = String.join(", ", names.keySet()); - }catch (ReflectiveOperationException ex) { - ex.printStackTrace(); - } + super(Arrays.asList(enumClass.getEnumConstants()), constant -> processConstantName(constant.name())); } - - @Override - public T parse(Player p, String msg) throws Throwable { - T obj = names.get(proceed(msg)); - if (obj == null) Lang.NO_SUCH_ELEMENT.send(p, namesString); - return obj; + + public EnumParser(Class enumClass, Predicate filter) { + super(Arrays.stream(enumClass.getEnumConstants()).filter(filter).collect(Collectors.toList()), constant -> processConstantName(constant.name())); } - - public String getNames() { - return namesString; + + @Override + protected String processName(String msg) { + return processConstantName(msg); } - - private String proceed(String key) { + + static String processConstantName(String key) { return FORMAT.matcher(key.toLowerCase()).replaceAll(""); } diff --git a/core/src/main/java/fr/skytasul/quests/gui/particles/ParticleListGUI.java b/core/src/main/java/fr/skytasul/quests/gui/particles/ParticleListGUI.java index dcd07eb4..b68ccf86 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/particles/ParticleListGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/particles/ParticleListGUI.java @@ -27,8 +27,9 @@ public ParticleListGUI(Consumer end) { @Override public ItemStack getItemStack(Particle object) { - String[] lore = ParticleEffect.canHaveColor(object) ? new String[] { QuestOption.formatDescription(Lang.particle_colored.toString()) } : new String[0]; - return ItemUtils.item(XMaterial.PAPER, "§e" + object.name(), lore); + boolean colorable = ParticleEffect.canHaveColor(object); + String[] lore = colorable ? new String[] { QuestOption.formatDescription(Lang.particle_colored.toString()) } : new String[0]; + return ItemUtils.item(colorable ? XMaterial.MAP : XMaterial.PAPER, "§e" + object.name(), lore); } @Override diff --git a/core/src/main/java/fr/skytasul/quests/utils/Lang.java b/core/src/main/java/fr/skytasul/quests/utils/Lang.java index ccf485e3..8e38e744 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Lang.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Lang.java @@ -214,7 +214,8 @@ public enum Lang implements Locale { GAME_TICKS("msg.editor.typeGameTicks"), - NO_SUCH_ELEMENT("msg.editor.noSuchElement"), // 0: available elements + AVAILABLE_ELEMENTS("msg.editor.availableElements"), // 0: available elements + NO_SUCH_ELEMENT("msg.editor.noSuchElement", EditorPrefix), // 0: available elements INVALID_PATTERN("msg.editor.invalidPattern"), // 0: pattern COMPARISON_TYPE("msg.editor.comparisonTypeDefault"), // 0: available comparisons, 1: default comparison @@ -226,14 +227,15 @@ public enum Lang implements Locale { POOL_QUESTS_PER_LAUNCH("msg.editor.pool.questsPerLaunch", EditorPrefix), POOL_TIME("msg.editor.pool.timeMsg", EditorPrefix), - TITLE_TITLE("msg.editor.title.title"), - TITLE_SUBTITLE("msg.editor.title.subtitle"), - TITLE_FADEIN("msg.editor.title.fadeIn"), - TITLE_STAY("msg.editor.title.stay"), - TITLE_FADEOUT("msg.editor.title.fadeOut"), + TITLE_TITLE("msg.editor.title.title", EditorPrefix), + TITLE_SUBTITLE("msg.editor.title.subtitle", EditorPrefix), + TITLE_FADEIN("msg.editor.title.fadeIn", EditorPrefix), + TITLE_STAY("msg.editor.title.stay", EditorPrefix), + TITLE_FADEOUT("msg.editor.title.fadeOut", EditorPrefix), - COLOR_EDITOR("msg.editor.color"), - INVALID_COLOR("msg.editor.invalidColor"), + COLOR_NAMED_EDITOR("msg.editor.colorNamed", EditorPrefix), + COLOR_EDITOR("msg.editor.color", EditorPrefix), + INVALID_COLOR("msg.editor.invalidColor", ErrorPrefix), FIREWORK_INVALID("msg.editor.firework.invalid", ErrorPrefix), FIREWORK_INVALID_HAND("msg.editor.firework.invalidHand", ErrorPrefix), diff --git a/core/src/main/resources/locales/en_US.yml b/core/src/main/resources/locales/en_US.yml index cf78e697..ea1466d1 100644 --- a/core/src/main/resources/locales/en_US.yml +++ b/core/src/main/resources/locales/en_US.yml @@ -308,6 +308,7 @@ msg: remove: '§6remove : §eRemove a text.' list: '§6list: §eView all added texts.' close: '§6close: §eValidate the added texts.' + availableElements: 'Available elements: §e{0}' noSuchElement: '§cThere is no such element. Allowed elements are: §e{0}' invalidPattern: '§cInvalid regex pattern §4{0}§c.' comparisonTypeDefault: '§aChoose the comparison type you want among {0}. Default comparison is: §e§l{1}§r§a. Type §onull§r§a to use it.' @@ -323,6 +324,7 @@ msg: fadeIn: 'Write the "fade-in" duration, in ticks (20 ticks = 1 second).' stay: 'Write the "stay" duration, in ticks (20 ticks = 1 second).' fadeOut: 'Write the "fade-out" duration, in ticks (20 ticks = 1 second).' + colorNamed: 'Enter the name of a color.' color: 'Enter a color in the hexadecimal format (#XXXXX) or RGB format (RED GRE BLU).' invalidColor: 'The color you entered is invalid. It must be either hexadecimal or RGB.' firework: From cb7cf9c57924c12709e113355f213ebdd9684e68 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Mon, 4 Jul 2022 18:07:02 +0200 Subject: [PATCH 024/111] Fixed an issue with holograms disappearing --- .../java/fr/skytasul/quests/BeautyQuests.java | 10 ++-- .../fr/skytasul/quests/api/npcs/BQNPC.java | 53 +++++++++++++++---- .../fr/skytasul/quests/commands/Commands.java | 8 +++ .../utils/compatibility/npcs/BQCitizens.java | 2 + 4 files changed, 59 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java index e3dd67e1..b567d9d0 100644 --- a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java +++ b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java @@ -191,9 +191,13 @@ public void run() { @Override public void onDisable(){ try { - Editor.leaveAll(); - Inventories.closeAll(); - stopSaveCycle(); + try { + Editor.leaveAll(); + Inventories.closeAll(); + stopSaveCycle(); + }catch (Exception ex) { + logger.severe("An exception occurred while disabling editing systems.", ex); + } try { if (!disable) saveAllConfig(true); diff --git a/core/src/main/java/fr/skytasul/quests/api/npcs/BQNPC.java b/core/src/main/java/fr/skytasul/quests/api/npcs/BQNPC.java index ccc77c3f..da1f10db 100644 --- a/core/src/main/java/fr/skytasul/quests/api/npcs/BQNPC.java +++ b/core/src/main/java/fr/skytasul/quests/api/npcs/BQNPC.java @@ -41,6 +41,7 @@ public abstract class BQNPC implements Located.LocatedEntity { private BukkitTask launcheableTask; /* Holograms */ + private boolean debug = false; private BukkitTask hologramsTask; private boolean hologramsRemoved = true; private Hologram hologramText = new Hologram(false, QuestsAPI.hasHologramsManager() && !QuestsConfiguration.isTextHologramDisabled(), Lang.HologramText.toString()); @@ -177,7 +178,7 @@ public void run() { if (isSpawned() && getEntity() instanceof LivingEntity) en = (LivingEntity) getEntity(); if (en == null) { - if (!hologramsRemoved) removeHolograms(); // if the NPC is not living and holograms have not been already removed before + if (!hologramsRemoved) removeHolograms(false); // if the NPC is not living and holograms have not been already removed before return; } hologramsRemoved = false; @@ -240,7 +241,6 @@ public Set getPools() { public void addPool(QuestPool pool) { if (!pools.add(pool)) return; if (hologramPool.enabled && (pool.getHologram() != null)) hologramPool.setText(pool.getHologram()); - hologramPool.visible = true; addStartablePredicate(pool::canGive, pool); updatedObjects(); } @@ -249,10 +249,7 @@ public boolean removePool(QuestPool pool) { boolean b = pools.remove(pool); removeStartablePredicate(pool); updatedObjects(); - if (pools.isEmpty()) { - hologramPool.visible = false; - hologramPool.delete(); - } + if (pools.isEmpty()) hologramPool.delete(); return b; } @@ -283,13 +280,13 @@ public boolean canGiveSomething(Player p) { return startable.values().stream().anyMatch(predicate -> predicate.test(p, acc)); } - private void removeHolograms() { + private void removeHolograms(boolean cancelRefresh) { hologramText.delete(); hologramLaunch.delete(); hologramLaunchNo.delete(); hologramPool.delete(); hologramsRemoved = true; - if (hologramsTask != null) { + if (cancelRefresh && hologramsTask != null) { hologramsTask.cancel(); hologramsTask = null; } @@ -301,14 +298,14 @@ private boolean isEmpty() { private void updatedObjects() { if (isEmpty()) { - removeHolograms(); + removeHolograms(true); }else if (holograms && hologramsTask == null) { hologramsTask = startHologramsTask(); } } public void unload() { - removeHolograms(); + removeHolograms(true); if (launcheableTask != null) { launcheableTask.cancel(); launcheableTask = null; @@ -329,6 +326,28 @@ public void delete(String cause) { unload(); } + public void toggleDebug() { + if (debug) + debug = false; + else debug = true; + } + + @Override + public String toString() { + String npcInfo = "NPC #" + getId() + ", " + quests.size() + " quests, " + pools.size() + " pools"; + String hologramsInfo; + if (!holograms) { + hologramsInfo = "no holograms"; + }else if (hologramsRemoved) { + hologramsInfo = "holograms removed"; + }else { + hologramsInfo = "holograms:"; + hologramsInfo += "\n- text=" + hologramText.toString(); + hologramsInfo += "\n- launch=" + hologramLaunch.toString(); + } + return npcInfo + " " + hologramsInfo; + } + public class Hologram { final boolean enabled; boolean visible; @@ -352,9 +371,12 @@ public Hologram(boolean visible, boolean enabled, ItemStack item) { public void refresh(LivingEntity en) { Location lc = Utils.upLocationForEntity(en, getYAdd()); + if (debug) System.out.println("refreshing " + toString() + " (hologram null: " + (hologram == null) + ")"); if (hologram == null) { create(lc); - }else hologram.teleport(lc); + }else { + hologram.teleport(lc); + } } public double getYAdd() { @@ -393,10 +415,19 @@ public void create(Location lc) { } public void delete() { + if (debug) System.out.println("deleting " + toString()); if (hologram == null) return; hologram.delete(); hologram = null; } + + @Override + public String toString() { + if (!enabled) return "disabled"; + if (!canAppear) return "cannot appear"; + return (visible ? "visible" : "invisible") + " by default, " + (item == null ? "" : item.getType().name() + ", ") + (text == null ? "no text" : "text=" + text); + } + } } \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/commands/Commands.java b/core/src/main/java/fr/skytasul/quests/commands/Commands.java index 1af23def..9787f445 100644 --- a/core/src/main/java/fr/skytasul/quests/commands/Commands.java +++ b/core/src/main/java/fr/skytasul/quests/commands/Commands.java @@ -21,6 +21,7 @@ import fr.skytasul.quests.BeautyQuests; import fr.skytasul.quests.api.QuestsAPI; +import fr.skytasul.quests.api.npcs.BQNPC; import fr.skytasul.quests.api.stages.AbstractStage; import fr.skytasul.quests.api.stages.types.Dialogable; import fr.skytasul.quests.editors.Editor; @@ -56,6 +57,13 @@ public class Commands { + @Cmd (permission = "create", args = "NPCSID", min = 1, hide = true) + public void testNPC(CommandContext cmd) { + BQNPC npc = cmd.get(0); + Utils.sendMessage(cmd.sender, npc.toString()); + npc.toggleDebug(); + } + @Cmd (permission = "create", player = true, noEditorInventory = true) public void create(CommandContext cmd){ QuestCreationSession session = new QuestCreationSession(); diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/npcs/BQCitizens.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/npcs/BQCitizens.java index b7ed3894..1deafc00 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/compatibility/npcs/BQCitizens.java +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/npcs/BQCitizens.java @@ -50,11 +50,13 @@ public Collection getIDs() { @EventHandler (priority = EventPriority.HIGHEST) public void onNPCRightClick(NPCRightClickEvent e) { + if (e.getNPC().getOwningRegistry() != CitizensAPI.getNPCRegistry()) return; super.clickEvent(e, e.getNPC().getId(), e.getClicker(), ClickType.RIGHT); } @EventHandler (priority = EventPriority.HIGHEST) public void onNPCLeftClick(NPCLeftClickEvent e) { + if (e.getNPC().getOwningRegistry() != CitizensAPI.getNPCRegistry()) return; super.clickEvent(e, e.getNPC().getId(), e.getClicker(), ClickType.LEFT); } From bd4bf9f1f6113c9faa9a118fbbb11c286a1e3afa Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Mon, 4 Jul 2022 19:02:10 +0200 Subject: [PATCH 025/111] :globe_with_meridians: Added missing translations --- .../java/fr/skytasul/quests/BeautyQuests.java | 4 +- .../api/stages/types/AbstractItemStage.java | 5 +- .../quests/options/OptionStartDialog.java | 2 +- .../requirements/PermissionsRequirement.java | 2 +- .../quests/rewards/RemoveItemsReward.java | 4 +- .../quests/stages/StageBringBack.java | 4 +- .../fr/skytasul/quests/stages/StageCraft.java | 2 +- .../fr/skytasul/quests/stages/StageMobs.java | 4 +- .../java/fr/skytasul/quests/utils/Lang.java | 6 ++ core/src/main/resources/locales/en_US.yml | 6 ++ core/src/main/resources/locales/fr_FR.yml | 8 ++ core/src/main/resources/locales/ru_RU.yml | 11 ++- core/src/main/resources/locales/vi_VN.yml | 79 +++++++++++++++++++ 13 files changed, 119 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java index b567d9d0..75f479c9 100644 --- a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java +++ b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java @@ -195,8 +195,8 @@ public void onDisable(){ Editor.leaveAll(); Inventories.closeAll(); stopSaveCycle(); - }catch (Exception ex) { - logger.severe("An exception occurred while disabling editing systems.", ex); + }catch (Throwable ex) { + logger.severe("An error occurred while disabling editing systems.", ex); } try { diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractItemStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractItemStage.java index dccac855..21e5f81a 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractItemStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractItemStage.java @@ -13,7 +13,6 @@ import org.bukkit.inventory.ItemStack; import fr.skytasul.quests.api.comparison.ItemComparisonMap; -import fr.skytasul.quests.api.stages.types.AbstractCountableStage; import fr.skytasul.quests.api.stages.StageCreation; import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.gui.creation.ItemsGUI; @@ -102,12 +101,12 @@ protected Creator(Line line, boolean ending) { public void setItems(List items) { this.items = Utils.combineItems(items); - line.editItem(6, ItemUtils.lore(line.getItem(6), Lang.optionValue.format(this.items.size() + " item(s)"))); + line.editItem(6, ItemUtils.lore(line.getItem(6), Lang.optionValue.format(Lang.AmountItems.format(this.items.size())))); } public void setComparisons(ItemComparisonMap comparisons) { this.comparisons = comparisons; - line.editItem(7, ItemUtils.lore(line.getItem(7), Lang.optionValue.format(this.comparisons.getEffective().size() + " comparison(s)"))); + line.editItem(7, ItemUtils.lore(line.getItem(7), Lang.optionValue.format(Lang.AmountComparisons.format(this.comparisons.getEffective().size())))); } @Override diff --git a/core/src/main/java/fr/skytasul/quests/options/OptionStartDialog.java b/core/src/main/java/fr/skytasul/quests/options/OptionStartDialog.java index f7783fa8..3aba0cd1 100644 --- a/core/src/main/java/fr/skytasul/quests/options/OptionStartDialog.java +++ b/core/src/main/java/fr/skytasul/quests/options/OptionStartDialog.java @@ -50,7 +50,7 @@ public boolean shouldDisplay(OptionSet options) { } private String[] getLore() { - return new String[] { formatDescription(Lang.startDialogLore.toString()), "", getValue() == null ? Lang.NotSet.toString() : "§7" + getValue().messages.size() + " line(s)" }; + return new String[] { formatDescription(Lang.startDialogLore.toString()), "", getValue() == null ? Lang.NotSet.toString() : "§7" + Lang.AmountDialogLines.format(getValue().messages.size()) }; } @Override diff --git a/core/src/main/java/fr/skytasul/quests/requirements/PermissionsRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/PermissionsRequirement.java index dbedccb7..5c0e0eda 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/PermissionsRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/PermissionsRequirement.java @@ -48,7 +48,7 @@ public void sendReason(Player p){ @Override public String[] getLore() { - return new String[] { "§8> §7" + permissions.size() + " permission(s)", "§8> Message: §7" + (message == null ? Lang.NotSet.toString() : message), "", Lang.RemoveMid.toString() }; + return new String[] { "§8> §7" + Lang.AmountPermissions.format(permissions.size()), "§8> Message: §7" + (message == null ? Lang.NotSet.toString() : message), "", Lang.RemoveMid.toString() }; } @Override diff --git a/core/src/main/java/fr/skytasul/quests/rewards/RemoveItemsReward.java b/core/src/main/java/fr/skytasul/quests/rewards/RemoveItemsReward.java index bac384ff..e7466807 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/RemoveItemsReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/RemoveItemsReward.java @@ -55,8 +55,8 @@ public String getDescription(Player p) { @Override public String[] getLore() { return new String[] { - "§7" + items.size() + " " + Lang.Item.toString(), - "§7" + comparisons.getEffective().size() + " comparison(s)", + "§7" + Lang.AmountItems.format(items.size()), + "§7" + Lang.AmountComparisons.format(comparisons.getEffective().size()), "", "§7" + Lang.ClickLeft.toString() + " > " + Lang.stageItems.toString(), "§7" + Lang.ClickRight.toString() + " > " + Lang.stageItemsComparison.toString(), diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageBringBack.java b/core/src/main/java/fr/skytasul/quests/stages/StageBringBack.java index 3175f459..1d833dfa 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageBringBack.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageBringBack.java @@ -188,7 +188,7 @@ public AbstractCreator(Line line, boolean ending) { public void setItems(List items) { this.items = Utils.combineItems(items); - line.editItem(5, ItemUtils.lore(line.getItem(5), Lang.optionValue.format(this.items.size() + " item(s)"))); + line.editItem(5, ItemUtils.lore(line.getItem(5), Lang.optionValue.format(Lang.AmountItems.format(this.items.size())))); } public void setMessage(String message) { @@ -198,7 +198,7 @@ public void setMessage(String message) { public void setComparisons(ItemComparisonMap comparisons) { this.comparisons = comparisons; - line.editItem(10, ItemUtils.lore(line.getItem(10), Lang.optionValue.format(this.comparisons.getEffective().size() + " comparison(s)"))); + line.editItem(10, ItemUtils.lore(line.getItem(10), Lang.optionValue.format(Lang.AmountComparisons.format(this.comparisons.getEffective().size())))); } @Override diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageCraft.java b/core/src/main/java/fr/skytasul/quests/stages/StageCraft.java index 9e3ecef8..f81c8f86 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageCraft.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageCraft.java @@ -201,7 +201,7 @@ public void setItem(ItemStack item) { public void setComparisons(ItemComparisonMap comparisons) { this.comparisons = comparisons; - line.editItem(COMPARISONS_SLOT, ItemUtils.lore(line.getItem(COMPARISONS_SLOT), Lang.optionValue.format(this.comparisons.getEffective().size() + " comparison(s)"))); + line.editItem(COMPARISONS_SLOT, ItemUtils.lore(line.getItem(COMPARISONS_SLOT), Lang.optionValue.format(Lang.AmountComparisons.format(this.comparisons.getEffective().size())))); } @Override diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageMobs.java b/core/src/main/java/fr/skytasul/quests/stages/StageMobs.java index b2d26819..8a588ce6 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageMobs.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageMobs.java @@ -10,8 +10,8 @@ import org.bukkit.event.entity.EntityDamageEvent.DamageCause; import fr.skytasul.quests.api.mobs.Mob; -import fr.skytasul.quests.api.stages.types.AbstractCountableStage; import fr.skytasul.quests.api.stages.StageCreation; +import fr.skytasul.quests.api.stages.types.AbstractCountableStage; import fr.skytasul.quests.gui.Inventories; import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.gui.creation.stages.Line; @@ -124,7 +124,7 @@ public Creator(Line line, boolean ending) { public void setMobs(Map, Integer>> mobs) { this.mobs = mobs; - line.editItem(7, ItemUtils.lore(line.getItem(7), Lang.optionValue.format(mobs.size() + " mob(s)"))); + line.editItem(7, ItemUtils.lore(line.getItem(7), Lang.optionValue.format(Lang.AmountMobs.format(mobs.size())))); } public void setShoot(boolean shoot) { diff --git a/core/src/main/java/fr/skytasul/quests/utils/Lang.java b/core/src/main/java/fr/skytasul/quests/utils/Lang.java index 8e38e744..1ed6b9f0 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Lang.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Lang.java @@ -787,6 +787,12 @@ public enum Lang implements Locale { ClickShiftLeft("misc.click.shift-left"), ClickMiddle("misc.click.middle"), + AmountItems("misc.amounts.items"), // 0: amount + AmountComparisons("misc.amounts.comparisons"), // 0: amount + AmountDialogLines("misc.amounts.dialogLines"), // 0: amount + AmountPermissions("misc.amounts.permissions"), // 0: amount + AmountMobs("misc.amounts.mobs"), // 0: amount + HologramText("misc.hologramText"), PoolHologramText("misc.poolHologramText"), MobsProgression("misc.mobsProgression"), diff --git a/core/src/main/resources/locales/en_US.yml b/core/src/main/resources/locales/en_US.yml index ea1466d1..31816477 100644 --- a/core/src/main/resources/locales/en_US.yml +++ b/core/src/main/resources/locales/en_US.yml @@ -822,6 +822,12 @@ misc: shift-right: Shift-right click shift-left: Shift-left click middle: Middle click + amounts: + items: '{0} item(s)' + comparisons: '{0} comparaison(s)' + dialogLines: '{0} line(s)' + permissions: '{0} permission(s)' + mobs: '{0} mob(s)' ticks: '{0} ticks' questItemLore: §e§oQuest Item hologramText: §8§lQuest NPC diff --git a/core/src/main/resources/locales/fr_FR.yml b/core/src/main/resources/locales/fr_FR.yml index 1d7559fb..3df4b75a 100644 --- a/core/src/main/resources/locales/fr_FR.yml +++ b/core/src/main/resources/locales/fr_FR.yml @@ -276,6 +276,7 @@ msg: remove: '§6remove : §eSupprimer un texte.' list: '§6list: §eVoir tout les textes.' close: '§6close: §eValider les textes.' + availableElements: 'Éléments disponibles : §e{0}' noSuchElement: '§cIl n''y a pas d’élément correspondant. Les éléments permis sont : §e{0}' invalidPattern: '§cRegex invalide: §4{0}§c.' comparisonTypeDefault: '§aChoissez le type de comparaison que vous voulez parmi {0}. La comparaison par défaut est: §e§l{1}§r§a. Tapez §onull§r§a pour l''utiliser.' @@ -291,6 +292,7 @@ msg: fadeIn: 'Écrivez la durée du fondu d''entrée, en ticks (20 ticks = 1 seconde).' stay: 'Écrivez la durée où le titre reste affiché, en ticks (20 ticks = 1 seconde).' fadeOut: 'Écrivez la durée du fondu de sortie, en ticks (20 ticks = 1 seconde).' + colorNamed: 'Saisissez le nom de la couleur :' color: 'Entrez une couleur au format hexadécimal (#XXXXX) ou RVB (ROUGE VERT BLEU).' invalidColor: 'La couleur que vous avez saisie n''est pas valide. Elle doit être hexadécimale ou RVB.' firework: @@ -778,6 +780,12 @@ misc: shift-right: Maj + clic-droit shift-left: Maj + clic-gauche middle: Clic central + amounts: + items: '{0} item(s)' + comparisons: '{0} comparaison(s)' + dialogLines: '{0} ligne(s)' + permissions: '{0} permission(s)' + mobs: '{0} mob(s)' ticks: '{0} ticks' questItemLore: '§e§oItem de Quête' hologramText: '§8§lNPC de Quête' diff --git a/core/src/main/resources/locales/ru_RU.yml b/core/src/main/resources/locales/ru_RU.yml index 6cdb2359..f6424705 100644 --- a/core/src/main/resources/locales/ru_RU.yml +++ b/core/src/main/resources/locales/ru_RU.yml @@ -11,6 +11,7 @@ msg: cancelling: '§cПроцесс создания квеста был прерван.' editCancelling: '§cПроцесс редактирования квеста был прерван.' invalidID: '§cКвест под номером {0}, не найден.' + invalidPoolID: '§cВетка {0} не существует.' alreadyStarted: '§cВы уже начали квест!' quests: maxLaunched: '§cВы не можете взять больше {0} задания(й) одновременно...' @@ -26,6 +27,7 @@ msg: questItem: drop: '§cВы не можете выбросить предмет из задания!' craft: '§cВы не можете использовать предмет задания для крафта!' + eat: '§cВы не можете съесть это!' stageMobs: noMobs: '§cНа этом этапе не нужно убивать мобов.' listMobs: '§aВам нужно убить {0}.' @@ -38,6 +40,7 @@ msg: writeMessage: '§aНапишите сообщение, которое будет отправлено игроку' writeStartMessage: '§aНапишите сообщение, которое будет отправлено в начале задания: «null», если вы хотите использовать значение по умолчанию, или «none», если вы не хотите:' writeEndMsg: '§aНапишите сообщение, которое будет отправлено в конце квеста, "null", если вы хотите стандартное сообщение или "none", если вы не хотите никакое. Вы можете использовать "{0}", оно будет заменено на награду' + writeEndSound: '§aНапишите сообщение, которое будет отправлено в начале задания: «null», если вы хотите использовать значение по умолчанию, или «none», если вы не хотите:' writeDescriptionText: '§aНапишите текст, который описывает цель этапа:' writeStageText: '§aНапишите текст, который будет отправлен игроку в начале этапа:' moveToTeleportPoint: '§aПерейдите к локации для телепорта.' @@ -71,15 +74,19 @@ msg: negative: '§cВы должны ввести положительное число!' zero: '§cВы должны ввести число, отличное от 0!' invalid: '§c{0} не допустимое число.' + notInBounds: Число должно быть между {0} и {1} errorOccurred: '§cПроизошла ошибка, свяжитесь с администратором! §4§lКод ошибки: {0}' commandsDisabled: '§cВ настоящее время у вас нет прав на выполнение команд!' indexOutOfBounds: '§cЧисло {0} за пределами! Оно должно между {1} и {2}.' invalidBlockData: '§cДанные блока {0} недопустимы или несовместимы с блоком {1}.' + invalidBlockTag: '§cНедоступный блочный тег {0}.' bringBackObjects: Принеси мне {0}. inventoryFull: '§cВаш инвентарь переполнен, предмет выпал на землю.' playerNeverConnected: '§cНе удалось найти информацию о игроке {0}.' playerNotOnline: '§cИгрок {0} не в сети.' + playerDataNotFound: '§cДанные игрока {0} не найдены.' versionRequired: 'Требуемая версия: §l{0}' + restartServer: '§7Перезагрузите сервер, чтобы увидеть изменения.' command: downloadTranslations: syntax: '§cНеобходимо указать язык для загрузки. Пример: "/quests downloadTranslations ru_RU".' @@ -242,13 +249,11 @@ msg: list: '§6close: §eПодтвердить все сообщения.' close: '§6close: §eПроверить добавленные тексты.' noSuchElement: '§cНет такого элемента. Допустимые элементы: §e{0}' - comparisonType: '§aВыберите тип сравнения, который вы хотите среди {0}. Сравнение по умолчанию: §e§lgreater или equals§r§a. Введите §onull§r§a чтобы использовать его.' scoreboardObjectiveNotFound: '§cНеизвестный объект таблицы.' pool: hologramText: 'Напишите пользовательский текст голограммы для этого бассейна (или напишите "null", если вы хотите использовать текст по умолчанию).' maxQuests: 'Напишите максимальное количество заданий, запускаемых из этого пула.' questsPerLaunch: 'Напишите количество заданий, которое игроки смогут получить, нажав на NPC.' - time: 'Напишите количество дней, прежде чем игроки смогут взять новое задание.' title: title: 'Напишите текст заголовка (или «null», если не хотите).' subtitle: 'Напишите текст подзаголовка (или «null», если не хотите).' @@ -472,7 +477,6 @@ inv: notStarted: Не начатые квесты finished: Завершенные квесты inProgress: Квесты в процессе выполнения - loreCancel: '§c§oНажмите, чтобы отменить квест.' loreStart: '§a§oНажмите, чтобы начать задание.' loreStartUnavailable: '§c§oВы не соответствуете требованиям для начала задания.' timeToWaitRedo: '§3§oВы можете возобновить это задание через {0}.' @@ -601,7 +605,6 @@ scoreboard: craft: '§eСоздать §6{0}' bucket: '§eЗаполнить §6{0}' location: '§eИди к §6{0}§e, §6{1}§e, §6{2}§e в §6{3}' - playTime: '§eИгровое время в тиках §6{0}' breed: '§eПорода §6{0}' tame: '§eПриручить §6{0}' indication: diff --git a/core/src/main/resources/locales/vi_VN.yml b/core/src/main/resources/locales/vi_VN.yml index a6429a5c..8c0c60c1 100644 --- a/core/src/main/resources/locales/vi_VN.yml +++ b/core/src/main/resources/locales/vi_VN.yml @@ -40,6 +40,7 @@ msg: writeMessage: '§aViết tin nhắn sẽ gửi đến người chơi' writeStartMessage: '§aViết thông báo sẽ gửi khi bắt đầu nhiệm vụ, "null" nếu bạn muốn là 1 thông báo bình thường hoặc "none" nếu bạn không muốn' writeEndMsg: '§aViết tin nhắn sẽ gửi khi nhiệm vụ kết thúc, "null" nếu bạn muốn một cái bình thường hoặc "none" nếu bạn không muốn gì hết. Bạn cso thể dùng "{0}" để thay thể với thông báo phần thưởng' + writeEndSound: '§aGhi tên của âm thanh sẽ được phát cho người chơi khi nhiệm vụ kết thúc, "null" nếu bạn muốn dùng cái mặc định hoặc "none" nếu bạn muốn không:' writeDescriptionText: '§aViết văn bản mô tả mục tiêu của giai đoạn:' writeStageText: '§aViết văn bản sẽ được gửi đến người chơi lúc đầu của giai đoạn:' moveToTeleportPoint: '§aĐi đến địa điểm muốn đến.' @@ -73,6 +74,7 @@ msg: negative: '§cBạn phải nhập một số dương!' zero: '§cBạn phải nhập một số khác với 0!' invalid: '§c{0} không phải là con số.' + notInBounds: '§cSố của bạn phải nằm ở giữa {0} và {1}.' errorOccurred: '§cĐã xảy ra lỗi, hãy liên hệ với quản trị viên! §4§lMã lỗi: {0}' commandsDisabled: '§cHiện tại bạn không được phép thực hiện lệnhs!' indexOutOfBounds: '§cSố {0} nằm ngoài giới hạn! Nó phải nằm trong khoảng từ {1} đến {2}.' @@ -87,6 +89,7 @@ msg: restartServer: '§7Khởi động lại máy chủ để thấy sửa đổi' dialogs: skipped: '§8§oLời thoại được bỏ qua' + tooFar: '§7§oBạn đang ở quá xa {0}...' command: downloadTranslations: syntax: '§cBạn cần phải tải file ngôn ngữ. Ví dụ: "/quests downloadTranslations vi_VN"' @@ -134,6 +137,9 @@ msg: backupQuestsFailed: '§cTạo backup cho toàn bộ nhiệm vụ thất bại' adminModeEntered: '§aBạn đã kích hoạt chế độ admin' adminModeLeft: '§aBạn đã thoát khỏi chế độ admin' + resetPlayerPool: + timer: '§aBạn đã đặt lại {0} bộ hẹn giờ của {1}.' + full: '§aBạn đã đặt lại {0} dữ liệu của {1}.' scoreboard: lineSet: '§6Bạn đã chỉnh sửa thành công dòng {0}' lineReset: '§6Bạn đã loại cài lại dòng {0}' @@ -142,6 +148,9 @@ msg: resetAll: '§6Bạn đã đặt lại bảng nhiệm vụ của người chơi {0}' hidden: '§6Bảng nhiệm vụ của người chơi {0} đã bị ẩn' shown: '§6Bảng nhiệm vụ của người chơi {0} đã được đặt lại' + own: + hidden: '§6Bảng thông tin của bạn hiện tại đã bị ẩn.' + shown: '§6Bảng thông tin của bạn hiện đã được hiển thị lại.' help: header: '§6§lBeautyQuest - Help' create: '§6/{0} create: §eTạo nhiệm vụ mới' @@ -149,14 +158,17 @@ msg: remove: '§6/{0} remove : §eXóa nhiệm vụ với id của nó hoặc click vào NPC khi chưa xác định' finishAll: '§6/{0} fisnishAll : §e Hoàn thành tất cả nhiệm vụ mà người chơi đó đang nhận' setStage: '§6/{0} setStage [nhánh nhiệm vụ mới] [giai đoạn mới]: §eBỏ qua giai đoạn hiện tại để bắt đầu một nhanh nhiệm vụ mới' + startDialog: '§6/{0} startDialog : §eBắt đầu hộp thoại đang chờ xử lý cho một giai đoạn của NPC hoặc hộp thoại bắt đầu cho một nhiệm vụ.' resetPlayer: '§6/{0} resetPlayer : §eXóa toàn bộ thông tin người chơi' resetPlayerQuest: '§6/{0} resetPlayerQuest [id nhiệm vụ] §eXóa thông tin nhiệm vụ từ người chơi' seePlayer: '§6/{0} seePlayer : §eXem thông tin của người chơi' reload: '§6/{0} reload : §eLưu và tải lại tất cả mọi thứ §c(Lệnh này có thể mang lại lỗi cho máy chủ bạn đến 99.9% nên hãy restart lại máy chủ)' start: '§6/{0} start [id nhiệm vụ]:§e Bắt buộc người chơi bắt đầu nhiệm vụ' setItem: '§6/{0} setItem :§e Lưu vật phẩm hologram' + setFirework: '§6/{0} setFirework: §eChỉnh sửa pháo hoa mặc định lúc kết thúc.' adminMode: '§6/{0} adminMode:§e Bật/tắt chế độ admin (Nó khá hữu dụng cho hiện một vài thông tin log)' version: '§6/{0} version: §eKiểm tra phiên bản của plugins' + downloadTranslations: '§6/{0} downloadTranslations : §eTải xuống một tệp bản dịch vanilla' save: '§6/{0} save: §eLưu plugins cách thủ công' list: '§6/{0} list:§e Xem tất cả các nhiệm vụ (Chỉ cho phiên bản được hỗ trợ)' typeCancel: '§aGõ "cancel" để trở lại dòng tin nhắn cuối cùng' @@ -170,9 +182,13 @@ msg: typeLocationRadius: '§aViết yêu cầu tầm xa từ vị trí đã đặt' typeGameTicks: '§aViết các dấu tích trò chơi cần thiết:' already: '§cBạn đã ở sẵn trong chế độ chỉnh sửa' + stage: + location: + typeWorldPattern: '§aViết regex cho tên của các thế giới:' enter: title: '§6- Chế Độ Chỉnh Sửa -' subtitle: '§6Write "/quests editor" to force leaving.' + list: '§c⚠ §7Bạn đã nhập trình chỉnh sửa "list". Dùng "add" để thêm dòng. Hãy tham khảo "help" (với không dấu gạch chéo) để được trợ giúp. §e§lNhập "close" để thoát khỏi trình chỉnh sửa.' chat: '§6You are currently in the Editor Mode. Write "/quests editor" to force leaving the editor. (Highly not recommended, consider using commands such as "close" or "cancel" to come back to the previous inventory.)' npc: enter: '§aNhấn vào NPC, hoặc gõ "cancel"' @@ -194,6 +210,9 @@ msg: permissionWorld: '§aViết thế giới trong đó quyền sẽ được chỉnh sửa hoặc "null" nếu bạn muốn là toàn cầu.' money: '§aViết số tiền sẽ nhận được' wait: '§aViết thời gian game để chờ: (1 giây = 20 game ticks)' + random: + min: '§aViết số lượng tối thiểu của phần thưởng được gửi tới người chơi (bao gồm).' + max: '§aViết số lượng tối đa của phần thưởng được gửi tới người chơi (bao gồm).' chooseObjectiveRequired: '§aViết tên dự án.' chooseObjectiveTargetScore: '§aGhi điểm mục tiêu cho vật (Target Score For The Objective).' chooseRegionRequired: '§aViết tên yêu cầu của khu vực (Bạn phải ở cùng 1 thế giới)' @@ -230,17 +249,54 @@ msg: edit: 'Chỉnh sửa tin nhắn' addSound: '§6addSound : §eThêm âm thanh khi tin nhắn hiển thị.' clear: '§6clear: §eXóa toàn bộ tin nhắn.' + close: '§6Đóng: §eXác thực tất cả tin nhắn.' + setTime: '§6setTime : §eĐặt tốc độ (trong ticks) trước tin nhắn tiếp theo được phát tự động.' + npcName: '§6npcName [Tên NPC tùy chỉnh]: §eĐặt (hoặc đặt lại về mặc định) tên tùy chỉnh của NPC trong hộp thoại.' + skippable: '§6skippable [true|false]: §eĐặt hộp thoại có thể được bỏ qua hay không' + timeSet: '§aThời gian đã được chỉnh sửa cho tin nhắn {0}: hiện tại là {1} ticks.' + timeRemoved: '§aThời gian đã được xóa cho tin nhắn {0}.' + npcName: + set: '§aTên NPC tùy chỉnh đã được đặt thành §7{1}§a (trước đó là §7{0}§a)' + unset: '§aCustom NPC đã được đổi tên về mặc định (§7{0}§a)' + skippable: + set: '§eTrạng thái bỏ qua hộp thoại hiện tại đã được đặt thành §7{1}§a (trước đó là §7{0}§a)' + unset: '§aTrạng thái bỏ qua hộp thoại hiện đã được đặt về mặc định (trước đó là §7{0}§a)' mythicmobs: list: '§aDanh sách tất cả Mythic Mobs:' isntMythicMob: '§cMythic Mob không tồn tại.' disabled: '§cMythicMob đã tắt' epicBossDoesntExist: '§cEpic Boss không tồn tại.' textList: + syntax: '§cCú pháp chính xác: ' + added: '§aĐã thêm vào văn bản "§7{0}§a"' + removed: '§aĐã loại bỏ văn bản "§7{0}§a"' help: header: '§6§lBeautyQuests - Giúp đỡ chỉnh sửa đoạn hội thoại' + add: '§6add : §eThêm một văn bản.' + remove: '§6remove : §eLoại bỏ một văn bản.' + list: '§6list: §eXem tất cả văn bản đã thêm.' + noSuchElement: '§cKhông tồn tại. Phần hợp lệ chỉ có: §e{0}' + comparisonTypeDefault: '§aChọn kiểu bạn muốn: {0}. Mặc định là: §e§l{1}§r§a. Gõ §onull§r§a để sử dụng.' + scoreboardObjectiveNotFound: '§cKhông rõ scoreboard' + pool: + hologramText: 'Viết Hologram tùy chỉnh cho Pool này (hoặc gõ "null" nếu bạn muốn sử dụng loại mặc định)' + maxQuests: 'Viết số lượng nhiệm vụ tối đa có thể khởi chạy cho Pool này.' + questsPerLaunch: 'Viết số lượng nhiệm vụ được giao khi click vào NPC.' + timeMsg: 'Viết thời gian trước khi người chơi có thể nhận một nhiệm vụ mới. (đơn vị: days).' title: title: 'Thêm một tiêu đề (để "null" nếu bạn muốn để trống)' subtitle: 'Thêm một phụ đề (để "null" nếu bạn muốn để trống)' + fadeIn: 'Viết thời gian "fade-in", đơn vị: Ticks (20 ticks = 1 giây)' + stay: 'Viết thời gian "stay", đơn vị: Ticks (20 ticks = 1 giây).' + fadeOut: 'Viết thời gian "fade-out", đơn vị: Ticks (20 ticks = 1 giây).' + color: 'Nhập định dạng màu. Định dạng Hexadecimal (#XXXXX) hoặc định dạng RGB (RED GRE BLU).' + invalidColor: 'Định dạng màu bạn nhập không hợp lệ. Bắt buộc phải là định dạng Hexadecimal hoặc RGB.' + firework: + invalid: 'Pháo hoa này không hợp lệ.' + invalidHand: 'Bạn phải cầm pháo ra ở tay chính.' + edited: 'Đã chỉnh sửa Quest Firework thành công!' + removed: 'Đã xóa Quest Firework thành công!' + writeCommandDelay: '§aViết độ trễ của lệnh. Đơn vị: Ticks.' advancement: finished: Đã Hoàn Thành notStarted: Chưa bắt đầu @@ -256,6 +312,8 @@ inv: create: stageCreate: '§aTạo giai đoạn mới' stageRemove: '§cXóa giai đoạn này' + stageUp: Lên + stageDown: Xuống findNPC: '§aTìm NPC' bringBack: '§aMang 1 vật phẩm trả lại !' findRegion: '§aTìm khu vực region' @@ -265,16 +323,26 @@ inv: talkChat: '§aNhắn tin trên thanh trò chuyện' interact: '§aTương tác với khối' fish: '§aBắt cá' + melt: '§6Vật phẩm tan chảy' + enchant: '§dVật phẩm cường hóa' craft: '§aChế tạo vật phẩm' bucket: '§aLàm đầy cái xô' location: '§aĐến địa điểm' playTime: '§aThời gian chơi' breedAnimals: '§aLàm no động vật' tameAnimals: '§aThuần phục động vật' + death: '§cChết' NPCText: '§eChỉnh sửa cuộc hội thoại' + dialogLines: '{0} dòng' NPCSelect: '§eChọn hoặc tạo NPC' hideClues: Ẩn particles và holograms + gps: Hiện thị la bàn hướng tới mục tiêu editMobsKill: '§eChỉnh sửa mobs bị tiêu diệt' + mobsKillFromAFar: Cần phải tiêu diệt bằng vật thể bay (VD:Mũi tên, cầu tuyết, trứng, ...) + editBlocksMine: '§eChỉnh block để đào' + preventBlockPlace: Ngăn người chơi phá block của họ + editBlocksPlace: '§eChỉnh block để đặt' + editMessageType: '§eChỉnh tin nhắn để viết' selectItems: '§eChỉnh sửa yêu cầu vật phẩm' selectRegion: '§7Chọn khu vực region' toggleRegionExit: Khi kết thúc nhiệm vụ @@ -289,6 +357,17 @@ inv: descriptionTextItem: '§eChỉnh sửa tiêu đề của nhiệm vụ' details: customDescription: '§eChỉnh sửa tiêu đề của nhiệm vụ' + hideNoRequirementsItem: Ẩn khi không đạt các yêu cầu + hideNoRequirementsItemLore: Nếu bật, nhiệm vụ sẽ không hiện lên Quests Menu nếu không đạt các yêu cầu. + bypassLimit: Không giới hạn nhiệm vụ + bypassLimitLore: Nếu bật, người chơi có thể nhận nhiệm vụ cho dù đã đạt số lượng nhiệm vụ đã nhận tối đa. + auto: Tự động bắt đầu khi lần đầu tham gia + autoLore: Nếu bật, nhiệm vụ sẽ tự động được nhận khi người chơi đó lần đầu tiên tham gia máy chủ. + questName: '§a§lChỉnh tên nhiệm vụ' + questNameLore: Bắt buộc phải đặt tên để hoành thành chỉnh sửa nhiệm vụ + setItemsRewards: '§eChỉnh phần thưởng' + removeItemsReward: '§eXóa vật phẩm khỏi kho đồ' + setXPRewards: '§eChỉnh phần thưởng. kinh nghiệm' setRewardStopQuest: '§cDừng nhiệm vụ' selectStarterNPC: '§e§lChọn NPC khởi đầu' createQuestName: '§lTạo nhiệm vụ' From 67aa03a118cfe304442cf105cb8f23a74c717e83 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Wed, 6 Jul 2022 11:43:54 +0200 Subject: [PATCH 026/111] Fixed issues with 1.8 --- .../main/java/fr/skytasul/quests/stages/StageInteract.java | 3 ++- core/src/main/java/fr/skytasul/quests/utils/Utils.java | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java b/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java index e83a0592..2d6b0234 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java @@ -33,6 +33,7 @@ import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.Utils; import fr.skytasul.quests.utils.XMaterial; +import fr.skytasul.quests.utils.nms.NMS; import fr.skytasul.quests.utils.types.BQBlock; import fr.skytasul.quests.utils.types.BQLocation; @@ -89,7 +90,7 @@ public Spliterator getNearbyLocated(NearbyFetcher fetcher) { @EventHandler public void onInteract(PlayerInteractEvent e){ if (e.getClickedBlock() == null) return; - if (e.getHand() != EquipmentSlot.HAND) return; + if (NMS.getMCVersion() >= 9 && e.getHand() != EquipmentSlot.HAND) return; if (left){ if (e.getAction() != Action.LEFT_CLICK_BLOCK) return; }else if (e.getAction() != Action.RIGHT_CLICK_BLOCK) return; diff --git a/core/src/main/java/fr/skytasul/quests/utils/Utils.java b/core/src/main/java/fr/skytasul/quests/utils/Utils.java index 2e3733f3..38590d78 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Utils.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Utils.java @@ -11,6 +11,7 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.Optional; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import java.util.regex.Matcher; @@ -31,7 +32,6 @@ import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.metadata.FixedMetadataValue; import org.bukkit.scoreboard.DisplaySlot; -import org.bukkit.util.Consumer; import fr.skytasul.quests.BeautyQuests; import fr.skytasul.quests.QuestsConfiguration; @@ -77,7 +77,7 @@ public static void spawnFirework(Location lc, FireworkMeta meta) { fw.setFireworkMeta(meta); }; if (NMS.getMCVersion() >= 12) { - lc.getWorld().spawn(lc, Firework.class, fwConsumer); + lc.getWorld().spawn(lc, Firework.class, fw -> fwConsumer.accept(fw)); // much better to use the built-in since 1.12 method to do operations on entity // before it is sent to the players, as it will not create flickering }else { From 7d98e6210519ec3ccbf48a735d0a474324f68e20 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Wed, 6 Jul 2022 20:24:24 +0200 Subject: [PATCH 027/111] Updated XMaterial, closes #227 --- .../java/fr/skytasul/quests/api/mobs/Mob.java | 3 +- .../fr/skytasul/quests/gui/ItemUtils.java | 2 +- .../quests/gui/mobs/EntityTypeGUI.java | 3 +- .../fr/skytasul/quests/gui/npc/NPCGUI.java | 2 +- .../java/fr/skytasul/quests/utils/Utils.java | 18 + .../fr/skytasul/quests/utils/XMaterial.java | 5307 +++++++---------- .../utils/compatibility/mobs/BQBoss.java | 3 +- .../utils/compatibility/mobs/MythicMobs.java | 2 +- .../utils/compatibility/mobs/MythicMobs5.java | 2 +- 9 files changed, 2177 insertions(+), 3165 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/api/mobs/Mob.java b/core/src/main/java/fr/skytasul/quests/api/mobs/Mob.java index dd1a7fca..ab1dce8c 100644 --- a/core/src/main/java/fr/skytasul/quests/api/mobs/Mob.java +++ b/core/src/main/java/fr/skytasul/quests/api/mobs/Mob.java @@ -11,6 +11,7 @@ import fr.skytasul.quests.BeautyQuests; import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.Utils; import fr.skytasul.quests.utils.XMaterial; public class Mob implements Cloneable { @@ -42,7 +43,7 @@ public ItemStack createItemStack(int amount) { lore.add(Lang.click.toString()); XMaterial mobItem; try { - mobItem = XMaterial.mobItem(factory.getEntityType(data)); + mobItem = Utils.mobItem(factory.getEntityType(data)); }catch (Exception ex) { mobItem = XMaterial.SPONGE; BeautyQuests.logger.warning("Unknow entity type for mob " + factory.getName(data), ex); diff --git a/core/src/main/java/fr/skytasul/quests/gui/ItemUtils.java b/core/src/main/java/fr/skytasul/quests/gui/ItemUtils.java index 9ad0e189..5b1ec6dc 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/ItemUtils.java +++ b/core/src/main/java/fr/skytasul/quests/gui/ItemUtils.java @@ -70,7 +70,7 @@ public static ItemStack item(XMaterial type, String name, List lore) { * @return the ItemStack instance */ public static ItemStack skull(String name, String skull, String... lore) { - ItemStack is = XMaterial.playerSkullItem(); + ItemStack is = XMaterial.PLAYER_HEAD.parseItem(); SkullMeta im = (SkullMeta) is.getItemMeta(); if (skull != null) im.setOwner(skull); is.setItemMeta(applyMeta(im, name, lore)); diff --git a/core/src/main/java/fr/skytasul/quests/gui/mobs/EntityTypeGUI.java b/core/src/main/java/fr/skytasul/quests/gui/mobs/EntityTypeGUI.java index 6fa543c3..3e38e6b7 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/mobs/EntityTypeGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/mobs/EntityTypeGUI.java @@ -17,6 +17,7 @@ import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.gui.templates.PagedGUI; import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.Utils; import fr.skytasul.quests.utils.XMaterial; public class EntityTypeGUI extends PagedGUI{ @@ -26,7 +27,7 @@ public class EntityTypeGUI extends PagedGUI{ for (EntityType en : EntityType.values()){ if (!en.isAlive()) continue; if (en == EntityType.PLAYER) continue; - XMaterial mat = XMaterial.mobItem(en); + XMaterial mat = Utils.mobItem(en); if (mat == null) continue; entities.put(en, ItemUtils.item(mat, en.getName())); } diff --git a/core/src/main/java/fr/skytasul/quests/gui/npc/NPCGUI.java b/core/src/main/java/fr/skytasul/quests/gui/npc/NPCGUI.java index 5e0e7203..5a07ef14 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/npc/NPCGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/npc/NPCGUI.java @@ -70,7 +70,7 @@ private void setType(EntityType type) { this.en = type; if (en == EntityType.PLAYER) { inv.setItem(5, ItemUtils.skull(Lang.type.toString(), null, Lang.optionValue.format("player"))); - }else inv.setItem(5, ItemUtils.item(XMaterial.mobItem(en), Lang.type.toString(), Lang.optionValue.format(en.getName()))); + }else inv.setItem(5, ItemUtils.item(Utils.mobItem(en), Lang.type.toString(), Lang.optionValue.format(en.getName()))); } private void setSkin(String skin) { diff --git a/core/src/main/java/fr/skytasul/quests/utils/Utils.java b/core/src/main/java/fr/skytasul/quests/utils/Utils.java index 38590d78..741a2e0d 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Utils.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Utils.java @@ -24,6 +24,7 @@ import org.bukkit.World; import org.bukkit.command.CommandSender; import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.EntityType; import org.bukkit.entity.Firework; import org.bukkit.entity.LivingEntity; import org.bukkit.entity.Player; @@ -483,5 +484,22 @@ public static boolean isQuestItem(ItemStack item) { } return false; } + + public static XMaterial mobItem(EntityType type) { + if (type == null) return XMaterial.SPONGE; + Optional material = XMaterial.matchXMaterial(type.name() + "_SPAWN_EGG"); + if (material.isPresent()) return material.get(); + if (type == EntityType.WITHER) return XMaterial.WITHER_SKELETON_SKULL; + if (type == EntityType.IRON_GOLEM) return XMaterial.IRON_BLOCK; + if (type == EntityType.SNOWMAN) return XMaterial.SNOW_BLOCK; + if (type == EntityType.MUSHROOM_COW) return XMaterial.MOOSHROOM_SPAWN_EGG; + if (type == EntityType.GIANT) return XMaterial.ZOMBIE_SPAWN_EGG; + if (type == EntityType.ARMOR_STAND) return XMaterial.ARMOR_STAND; + if (type == EntityType.PLAYER) return XMaterial.PLAYER_HEAD; + if (type == EntityType.ENDER_DRAGON) return XMaterial.DRAGON_HEAD; + if (type.name().equals("PIG_ZOMBIE") || type.name().equals("ZOMBIFIED_PIGLIN")) return XMaterial.ZOMBIFIED_PIGLIN_SPAWN_EGG; + if (type.name().equals("ILLUSIONER")) return XMaterial.BLAZE_POWDER; + return XMaterial.SPONGE; + } } \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/utils/XMaterial.java b/core/src/main/java/fr/skytasul/quests/utils/XMaterial.java index 193363d0..2dc3c868 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/XMaterial.java +++ b/core/src/main/java/fr/skytasul/quests/utils/XMaterial.java @@ -22,37 +22,24 @@ */ package fr.skytasul.quests.utils; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Locale; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.regex.PatternSyntaxException; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.Validate; -import org.apache.commons.lang.WordUtils; +import com.google.common.base.Enums; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; import org.bukkit.Bukkit; import org.bukkit.Material; -import org.bukkit.SkullType; -import org.bukkit.entity.EntityType; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.SpawnEggMeta; import org.bukkit.potion.Potion; -import com.google.common.base.Enums; -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; +import java.util.stream.Collectors; /** * XMaterial - Data Values/Pre-flattening
@@ -74,3160 +61,2164 @@ * /give @p minecraft:dirt 1 10 where 1 is the item amount, and 10 is the data value. The material {@link #DIRT} with a data value of {@code 10} doesn't exist. * * @author Crypto Morin - * @version 10.1.1.1 + * @version 11.0.0 * @see Material * @see ItemStack */ public enum XMaterial { - ACACIA_BOAT( - "BOAT_ACACIA"), - ACACIA_BUTTON( - "WOOD_BUTTON"), - ACACIA_DOOR( - "ACACIA_DOOR", - "ACACIA_DOOR_ITEM"), - ACACIA_FENCE, - ACACIA_FENCE_GATE, - ACACIA_LEAVES( - "LEAVES_2"), - ACACIA_LOG( - "LOG_2"), - ACACIA_PLANKS( - 4, - "WOOD"), - ACACIA_PRESSURE_PLATE( - "WOOD_PLATE"), - ACACIA_SAPLING( - 4, - "SAPLING"), - ACACIA_SIGN( - "SIGN_POST", - "SIGN"), - ACACIA_SLAB( - 4, - "WOOD_DOUBLE_STEP", - "WOOD_STEP", - "WOODEN_SLAB"), - ACACIA_STAIRS, - ACACIA_TRAPDOOR( - "TRAP_DOOR"), - ACACIA_WALL_SIGN( - "WALL_SIGN"), - ACACIA_WOOD( - "LOG_2"), - ACTIVATOR_RAIL, - /** - * https://minecraft.gamepedia.com/Air - * {@link Material#isAir()} - * - * @see #VOID_AIR - * @see #CAVE_AIR - */ - AIR, - ALLIUM( - 2, - "RED_ROSE"), - AMETHYST_BLOCK, - AMETHYST_CLUSTER, - AMETHYST_SHARD, - ANCIENT_DEBRIS, - ANDESITE( - 5, - "STONE"), - ANDESITE_SLAB, - ANDESITE_STAIRS, - ANDESITE_WALL, - ANVIL, - APPLE, - ARMOR_STAND, - ARROW, - ATTACHED_MELON_STEM( - 7, - "MELON_STEM"), - ATTACHED_PUMPKIN_STEM( - 7, - "PUMPKIN_STEM"), - AXOLOTL_BUCKET, - AXOLOTL_SPAWN_EGG, - AZALEA, - AZALEA_LEAVES, - AZURE_BLUET( - 3, - "RED_ROSE"), - BAKED_POTATO, - BAMBOO, - BAMBOO_SAPLING, - BARREL, - BARRIER, - BASALT, - BAT_SPAWN_EGG( - 65, - "MONSTER_EGG"), - BEACON, - BEDROCK, - BEEF( - "RAW_BEEF"), - BEEHIVE, - /** - * Beetroot is a known material in pre-1.13 - */ - BEETROOT( - "BEETROOT_BLOCK"), - BEETROOTS( - "BEETROOT"), - BEETROOT_SEEDS, - BEETROOT_SOUP, - BEE_NEST, - BEE_SPAWN_EGG, - BELL, - BIG_DRIPLEAF, - BIG_DRIPLEAF_STEM, - BIRCH_BOAT( - "BOAT_BIRCH"), - BIRCH_BUTTON( - "WOOD_BUTTON"), - BIRCH_DOOR( - "BIRCH_DOOR", - "BIRCH_DOOR_ITEM"), - BIRCH_FENCE, - BIRCH_FENCE_GATE, - BIRCH_LEAVES( - 2, - "LEAVES"), - BIRCH_LOG( - 2, - "LOG"), - BIRCH_PLANKS( - 2, - "WOOD"), - BIRCH_PRESSURE_PLATE( - "WOOD_PLATE"), - BIRCH_SAPLING( - 2, - "SAPLING"), - BIRCH_SIGN( - "SIGN_POST", - "SIGN"), - BIRCH_SLAB( - 2, - "WOOD_DOUBLE_STEP", - "WOOD_STEP", - "WOODEN_SLAB"), - BIRCH_STAIRS( - "BIRCH_WOOD_STAIRS"), - BIRCH_TRAPDOOR( - "TRAP_DOOR"), - BIRCH_WALL_SIGN( - "WALL_SIGN"), - BIRCH_WOOD( - 2, - "LOG"), - BLACKSTONE, - BLACKSTONE_SLAB, - BLACKSTONE_STAIRS, - BLACKSTONE_WALL, - BLACK_BANNER( - "STANDING_BANNER", - "BANNER"), - /** - * Version 1.12+ interprets "BED" as BLACK_BED due to enum alphabetic ordering. - */ - BLACK_BED( - supports(12) ? 15 : 0, - "BED_BLOCK", - "BED"), - BLACK_CANDLE, - BLACK_CANDLE_CAKE, - BLACK_CARPET( - 15, - "CARPET"), - BLACK_CONCRETE( - 15, - "CONCRETE"), - BLACK_CONCRETE_POWDER( - 15, - "CONCRETE_POWDER"), - BLACK_DYE, - BLACK_GLAZED_TERRACOTTA, - BLACK_SHULKER_BOX, - BLACK_STAINED_GLASS( - 15, - "STAINED_GLASS"), - BLACK_STAINED_GLASS_PANE( - 15, - "STAINED_GLASS_PANE"), - BLACK_TERRACOTTA( - 15, - "STAINED_CLAY"), - BLACK_WALL_BANNER( - "WALL_BANNER"), - BLACK_WOOL( - 15, - "WOOL"), - BLAST_FURNACE, - BLAZE_POWDER, - BLAZE_ROD, - BLAZE_SPAWN_EGG( - 61, - "MONSTER_EGG"), - BLUE_BANNER( - 4, - "STANDING_BANNER", - "BANNER"), - BLUE_BED( - supports(12) ? 11 : 0, - "BED_BLOCK", - "BED"), - BLUE_CANDLE, - BLUE_CANDLE_CAKE, - BLUE_CARPET( - 11, - "CARPET"), - BLUE_CONCRETE( - 11, - "CONCRETE"), - BLUE_CONCRETE_POWDER( - 11, - "CONCRETE_POWDER"), - BLUE_DYE( - 4, - "INK_SACK", - "LAPIS_LAZULI"), - BLUE_GLAZED_TERRACOTTA, - BLUE_ICE, - BLUE_ORCHID( - 1, - "RED_ROSE"), - BLUE_SHULKER_BOX, - BLUE_STAINED_GLASS( - 11, - "STAINED_GLASS"), - BLUE_STAINED_GLASS_PANE( - 11, - "THIN_GLASS", - "STAINED_GLASS_PANE"), - BLUE_TERRACOTTA( - 11, - "STAINED_CLAY"), - BLUE_WALL_BANNER( - 4, - "WALL_BANNER"), - BLUE_WOOL( - 11, - "WOOL"), - BONE, - BONE_BLOCK, - BONE_MEAL( - 15, - "INK_SACK"), - BOOK, - BOOKSHELF, - BOW, - BOWL, - BRAIN_CORAL, - BRAIN_CORAL_BLOCK, - BRAIN_CORAL_FAN, - BRAIN_CORAL_WALL_FAN, - BREAD, - BREWING_STAND( - "BREWING_STAND", - "BREWING_STAND_ITEM"), - BRICK( - "CLAY_BRICK"), - BRICKS( - "BRICKS", - "BRICK"), - BRICK_SLAB( - 4, - "STEP"), - BRICK_STAIRS, - BRICK_WALL, - BROWN_BANNER( - 3, - "STANDING_BANNER", - "BANNER"), - BROWN_BED( - supports(12) ? 12 : 0, - "BED_BLOCK", - "BED"), - BROWN_CANDLE, - BROWN_CANDLE_CAKE, - BROWN_CARPET( - 12, - "CARPET"), - BROWN_CONCRETE( - 12, - "CONCRETE"), - BROWN_CONCRETE_POWDER( - 12, - "CONCRETE_POWDER"), - BROWN_DYE( - 3, - "INK_SACK", - "DYE", - "COCOA_BEANS"), - BROWN_GLAZED_TERRACOTTA, - BROWN_MUSHROOM, - BROWN_MUSHROOM_BLOCK( - "BROWN_MUSHROOM", - "HUGE_MUSHROOM_1"), - BROWN_SHULKER_BOX, - BROWN_STAINED_GLASS( - 12, - "STAINED_GLASS"), - BROWN_STAINED_GLASS_PANE( - 12, - "THIN_GLASS", - "STAINED_GLASS_PANE"), - BROWN_TERRACOTTA( - 12, - "STAINED_CLAY"), - BROWN_WALL_BANNER( - 3, - "WALL_BANNER"), - BROWN_WOOL( - 12, - "WOOL"), - BUBBLE_COLUMN, - BUBBLE_CORAL, - BUBBLE_CORAL_BLOCK, - BUBBLE_CORAL_FAN, - BUBBLE_CORAL_WALL_FAN, - BUCKET, - BUDDING_AMETHYST, - BUNDLE, - CACTUS, - CAKE( - "CAKE_BLOCK"), - CALCITE, - CAMPFIRE, - CANDLE, - CANDLE_CAKE, - CARROT( - "CARROT_ITEM"), - CARROTS( - "CARROT"), - CARROT_ON_A_STICK( - "CARROT_STICK"), - CARTOGRAPHY_TABLE, - CARVED_PUMPKIN, - CAT_SPAWN_EGG, - CAULDRON( - "CAULDRON", - "CAULDRON_ITEM"), - /** - * 1.13 tag is not added because it's the same thing as {@link #AIR} - * - * @see #VOID_AIR - */ - CAVE_AIR( - "AIR"), - CAVE_SPIDER_SPAWN_EGG( - 59, - "MONSTER_EGG"), - CAVE_VINES, - CAVE_VINES_PLANT, - CHAIN, - CHAINMAIL_BOOTS, - CHAINMAIL_CHESTPLATE, - CHAINMAIL_HELMET, - CHAINMAIL_LEGGINGS, - CHAIN_COMMAND_BLOCK( - "COMMAND", - "COMMAND_CHAIN"), - CHARCOAL( - 1, - "COAL"), - CHEST( - "LOCKED_CHEST"), - CHEST_MINECART( - "STORAGE_MINECART"), - CHICKEN( - "RAW_CHICKEN"), - CHICKEN_SPAWN_EGG( - 93, - "MONSTER_EGG"), - CHIPPED_ANVIL( - 1, - "ANVIL"), - CHISELED_DEEPSLATE, - CHISELED_NETHER_BRICKS( - 1, - "NETHER_BRICKS"), - CHISELED_POLISHED_BLACKSTONE( - "POLISHED_BLACKSTONE"), - CHISELED_QUARTZ_BLOCK( - 1, - "QUARTZ_BLOCK"), - CHISELED_RED_SANDSTONE( - 1, - "RED_SANDSTONE"), - CHISELED_SANDSTONE( - 1, - "SANDSTONE"), - CHISELED_STONE_BRICKS( - 3, - "SMOOTH_BRICK"), - CHORUS_FLOWER, - CHORUS_FRUIT, - CHORUS_PLANT, - CLAY, - CLAY_BALL, - CLOCK( - "WATCH"), - COAL, - COAL_BLOCK, - COAL_ORE, - COARSE_DIRT( - 1, - "DIRT"), - COBBLED_DEEPSLATE, - COBBLED_DEEPSLATE_SLAB, - COBBLED_DEEPSLATE_STAIRS, - COBBLED_DEEPSLATE_WALL, - COBBLESTONE, - COBBLESTONE_SLAB( - 3, - "STEP"), - COBBLESTONE_STAIRS, - COBBLESTONE_WALL( - "COBBLE_WALL"), - COBWEB( - "WEB"), - COCOA, - COCOA_BEANS( - 3, - "INK_SACK"), - COD( - "RAW_FISH"), - COD_BUCKET, - COD_SPAWN_EGG, - COMMAND_BLOCK( - "COMMAND"), - COMMAND_BLOCK_MINECART( - "COMMAND_MINECART"), - /** - * Unlike redstone torch and redstone lamp... neither REDTONE_COMPARATOR_OFF nor REDSTONE_COMPARATOR_ON - * are items. REDSTONE_COMPARATOR is. - * - * @see #REDSTONE_TORCH - * @see #REDSTONE_LAMP - */ - COMPARATOR( - "REDSTONE_COMPARATOR_OFF", - "REDSTONE_COMPARATOR_ON", - "REDSTONE_COMPARATOR"), - COMPASS, - COMPOSTER, - CONDUIT, - COOKED_BEEF, - COOKED_CHICKEN, - COOKED_COD( - "COOKED_FISH"), - COOKED_MUTTON, - COOKED_PORKCHOP( - "GRILLED_PORK"), - COOKED_RABBIT, - COOKED_SALMON( - 1, - "COOKED_FISH"), - COOKIE, - COPPER_BLOCK, - COPPER_INGOT, - COPPER_ORE, - CORNFLOWER, - COW_SPAWN_EGG( - 92, - "MONSTER_EGG"), - CRACKED_DEEPSLATE_BRICKS, - CRACKED_DEEPSLATE_TILES, - CRACKED_NETHER_BRICKS( - 2, - "NETHER_BRICKS"), - CRACKED_POLISHED_BLACKSTONE_BRICKS( - "POLISHED_BLACKSTONE_BRICKS"), - CRACKED_STONE_BRICKS( - 2, - "SMOOTH_BRICK"), - CRAFTING_TABLE( - "WORKBENCH"), - CREEPER_BANNER_PATTERN, - CREEPER_HEAD( - 4, - "SKULL", - "SKULL_ITEM"), - CREEPER_SPAWN_EGG( - 50, - "MONSTER_EGG"), - CREEPER_WALL_HEAD( - 4, - "SKULL", - "SKULL_ITEM"), - CRIMSON_BUTTON, - CRIMSON_DOOR, - CRIMSON_FENCE, - CRIMSON_FENCE_GATE, - CRIMSON_FUNGUS, - CRIMSON_HYPHAE, - CRIMSON_NYLIUM, - CRIMSON_PLANKS, - CRIMSON_PRESSURE_PLATE, - CRIMSON_ROOTS, - CRIMSON_SIGN( - "SIGN_POST"), - CRIMSON_SLAB, - CRIMSON_STAIRS, - CRIMSON_STEM, - CRIMSON_TRAPDOOR, - CRIMSON_WALL_SIGN( - "WALL_SIGN"), - CROSSBOW, - CRYING_OBSIDIAN, - CUT_COPPER, - CUT_COPPER_SLAB, - CUT_COPPER_STAIRS, - CUT_RED_SANDSTONE, - CUT_RED_SANDSTONE_SLAB( - "STONE_SLAB2"), - CUT_SANDSTONE, - CUT_SANDSTONE_SLAB( - 1, - "STEP"), - CYAN_BANNER( - 6, - "STANDING_BANNER", - "BANNER"), - CYAN_BED( - supports(12) ? 9 : 0, - "BED_BLOCK", - "BED"), - CYAN_CANDLE, - CYAN_CANDLE_CAKE, - CYAN_CARPET( - 9, - "CARPET"), - CYAN_CONCRETE( - 9, - "CONCRETE"), - CYAN_CONCRETE_POWDER( - 9, - "CONCRETE_POWDER"), - CYAN_DYE( - 6, - "INK_SACK"), - CYAN_GLAZED_TERRACOTTA, - CYAN_SHULKER_BOX, - CYAN_STAINED_GLASS( - 9, - "STAINED_GLASS"), - CYAN_STAINED_GLASS_PANE( - 9, - "STAINED_GLASS_PANE"), - CYAN_TERRACOTTA( - 9, - "STAINED_CLAY"), - CYAN_WALL_BANNER( - 6, - "WALL_BANNER"), - CYAN_WOOL( - 9, - "WOOL"), - DAMAGED_ANVIL( - 2, - "ANVIL"), - DANDELION( - "YELLOW_FLOWER"), - DARK_OAK_BOAT( - "BOAT_DARK_OAK"), - DARK_OAK_BUTTON( - "WOOD_BUTTON"), - DARK_OAK_DOOR( - "DARK_OAK_DOOR", - "DARK_OAK_DOOR_ITEM"), - DARK_OAK_FENCE, - DARK_OAK_FENCE_GATE, - DARK_OAK_LEAVES( - 1, - "LEAVES_2"), - DARK_OAK_LOG( - 1, - "LOG_2"), - DARK_OAK_PLANKS( - 5, - "WOOD"), - DARK_OAK_PRESSURE_PLATE( - "WOOD_PLATE"), - DARK_OAK_SAPLING( - 5, - "SAPLING"), - DARK_OAK_SIGN( - "SIGN_POST", - "SIGN"), - DARK_OAK_SLAB( - 5, - "WOOD_DOUBLE_STEP", - "WOOD_STEP", - "WOODEN_SLAB"), - DARK_OAK_STAIRS, - DARK_OAK_TRAPDOOR( - "TRAP_DOOR"), - DARK_OAK_WALL_SIGN( - "WALL_SIGN"), - DARK_OAK_WOOD( - 1, - "LOG_2"), - DARK_PRISMARINE( - 2, - "PRISMARINE"), - DARK_PRISMARINE_SLAB, - DARK_PRISMARINE_STAIRS, - DAYLIGHT_DETECTOR( - "DAYLIGHT_DETECTOR_INVERTED"), - DEAD_BRAIN_CORAL, - DEAD_BRAIN_CORAL_BLOCK, - DEAD_BRAIN_CORAL_FAN, - DEAD_BRAIN_CORAL_WALL_FAN, - DEAD_BUBBLE_CORAL, - DEAD_BUBBLE_CORAL_BLOCK, - DEAD_BUBBLE_CORAL_FAN, - DEAD_BUBBLE_CORAL_WALL_FAN, - DEAD_BUSH( - "LONG_GRASS"), - DEAD_FIRE_CORAL, - DEAD_FIRE_CORAL_BLOCK, - DEAD_FIRE_CORAL_FAN, - DEAD_FIRE_CORAL_WALL_FAN, - DEAD_HORN_CORAL, - DEAD_HORN_CORAL_BLOCK, - DEAD_HORN_CORAL_FAN, - DEAD_HORN_CORAL_WALL_FAN, - DEAD_TUBE_CORAL, - DEAD_TUBE_CORAL_BLOCK, - DEAD_TUBE_CORAL_FAN, - DEAD_TUBE_CORAL_WALL_FAN, - DEBUG_STICK, - DEEPSLATE, - DEEPSLATE_BRICKS, - DEEPSLATE_BRICK_SLAB, - DEEPSLATE_BRICK_STAIRS, - DEEPSLATE_BRICK_WALL, - DEEPSLATE_COAL_ORE, - DEEPSLATE_COPPER_ORE, - DEEPSLATE_DIAMOND_ORE, - DEEPSLATE_EMERALD_ORE, - DEEPSLATE_GOLD_ORE, - DEEPSLATE_IRON_ORE, - DEEPSLATE_LAPIS_ORE, - DEEPSLATE_REDSTONE_ORE, - DEEPSLATE_TILES, - DEEPSLATE_TILE_SLAB, - DEEPSLATE_TILE_STAIRS, - DEEPSLATE_TILE_WALL, - DETECTOR_RAIL, - DIAMOND, - DIAMOND_AXE, - DIAMOND_BLOCK, - DIAMOND_BOOTS, - DIAMOND_CHESTPLATE, - DIAMOND_HELMET, - DIAMOND_HOE, - DIAMOND_HORSE_ARMOR( - "DIAMOND_BARDING"), - DIAMOND_LEGGINGS, - DIAMOND_ORE, - DIAMOND_PICKAXE, - DIAMOND_SHOVEL( - "DIAMOND_SPADE"), - DIAMOND_SWORD, - DIORITE( - 3, - "STONE"), - DIORITE_SLAB, - DIORITE_STAIRS, - DIORITE_WALL, - DIRT, - /** - * Changed in 1.17 - */ - DIRT_PATH( - "GRASS_PATH"), - DISPENSER, - DOLPHIN_SPAWN_EGG, - DONKEY_SPAWN_EGG( - 32, - "MONSTER_EGG"), - DRAGON_BREATH( - "DRAGONS_BREATH"), - DRAGON_EGG, - DRAGON_HEAD( - 5, - "SKULL", - "SKULL_ITEM"), - DRAGON_WALL_HEAD( - 5, - "SKULL", - "SKULL_ITEM"), - DRIED_KELP, - DRIED_KELP_BLOCK, - DRIPSTONE_BLOCK, - DROPPER, - DROWNED_SPAWN_EGG, - EGG, - ELDER_GUARDIAN_SPAWN_EGG( - 4, - "MONSTER_EGG"), - ELYTRA, - EMERALD, - EMERALD_BLOCK, - EMERALD_ORE, - ENCHANTED_BOOK, - ENCHANTED_GOLDEN_APPLE( - 1, - "GOLDEN_APPLE"), - ENCHANTING_TABLE( - "ENCHANTMENT_TABLE"), - ENDERMAN_SPAWN_EGG( - 58, - "MONSTER_EGG"), - ENDERMITE_SPAWN_EGG( - 67, - "MONSTER_EGG"), - ENDER_CHEST, - ENDER_EYE( - "EYE_OF_ENDER"), - ENDER_PEARL, - END_CRYSTAL, - END_GATEWAY, - END_PORTAL( - "ENDER_PORTAL"), - END_PORTAL_FRAME( - "ENDER_PORTAL_FRAME"), - END_ROD, - END_STONE( - "ENDER_STONE"), - END_STONE_BRICKS( - "END_BRICKS"), - END_STONE_BRICK_SLAB, - END_STONE_BRICK_STAIRS, - END_STONE_BRICK_WALL, - EVOKER_SPAWN_EGG( - 34, - "MONSTER_EGG"), - EXPERIENCE_BOTTLE( - "EXP_BOTTLE"), - EXPOSED_COPPER, - EXPOSED_CUT_COPPER, - EXPOSED_CUT_COPPER_SLAB, - EXPOSED_CUT_COPPER_STAIRS, - FARMLAND( - "SOIL"), - FEATHER, - FERMENTED_SPIDER_EYE, - FERN( - 2, - "LONG_GRASS"), - /** - * For some reasons filled map items are really special. - * Their data value starts from 0 and every time a player - * creates a new map that maps data value increases. - * https://github.com/CryptoMorin/XSeries/issues/91 - */ - FILLED_MAP( - "MAP"), - FIRE, - FIREWORK_ROCKET( - "FIREWORK"), - FIREWORK_STAR( - "FIREWORK_CHARGE"), - FIRE_CHARGE( - "FIREBALL"), - FIRE_CORAL, - FIRE_CORAL_BLOCK, - FIRE_CORAL_FAN, - FIRE_CORAL_WALL_FAN, - FISHING_ROD, - FLETCHING_TABLE, - FLINT, - FLINT_AND_STEEL, - FLOWERING_AZALEA, - FLOWERING_AZALEA_LEAVES, - FLOWER_BANNER_PATTERN, - FLOWER_POT( - "FLOWER_POT", - "FLOWER_POT_ITEM"), - FOX_SPAWN_EGG, - /** - * This special material cannot be obtained as an item. - */ - FROSTED_ICE, - FURNACE( - "BURNING_FURNACE"), - FURNACE_MINECART( - "POWERED_MINECART"), - GHAST_SPAWN_EGG( - 56, - "MONSTER_EGG"), - GHAST_TEAR, - GILDED_BLACKSTONE, - GLASS, - GLASS_BOTTLE, - GLASS_PANE( - "THIN_GLASS"), - GLISTERING_MELON_SLICE( - "SPECKLED_MELON"), - GLOBE_BANNER_PATTERN, - GLOWSTONE, - GLOWSTONE_DUST, - GLOW_BERRIES, - GLOW_INK_SAC, - GLOW_ITEM_FRAME, - GLOW_LICHEN, - GLOW_SQUID_SPAWN_EGG, - GOAT_SPAWN_EGG, - GOLDEN_APPLE, - GOLDEN_AXE( - "GOLD_AXE"), - GOLDEN_BOOTS( - "GOLD_BOOTS"), - GOLDEN_CARROT, - GOLDEN_CHESTPLATE( - "GOLD_CHESTPLATE"), - GOLDEN_HELMET( - "GOLD_HELMET"), - GOLDEN_HOE( - "GOLD_HOE"), - GOLDEN_HORSE_ARMOR( - "GOLD_BARDING"), - GOLDEN_LEGGINGS( - "GOLD_LEGGINGS"), - GOLDEN_PICKAXE( - "GOLD_PICKAXE"), - GOLDEN_SHOVEL( - "GOLD_SPADE"), - GOLDEN_SWORD( - "GOLD_SWORD"), - GOLD_BLOCK, - GOLD_INGOT, - GOLD_NUGGET, - GOLD_ORE, - GRANITE( - 1, - "STONE"), - GRANITE_SLAB, - GRANITE_STAIRS, - GRANITE_WALL, - GRASS( - 1, - "LONG_GRASS"), - GRASS_BLOCK( - "GRASS"), - GRAVEL, - GRAY_BANNER( - 8, - "STANDING_BANNER", - "BANNER"), - GRAY_BED( - supports(12) ? 7 : 0, - "BED_BLOCK", - "BED"), - GRAY_CANDLE, - GRAY_CANDLE_CAKE, - GRAY_CARPET( - 7, - "CARPET"), - GRAY_CONCRETE( - 7, - "CONCRETE"), - GRAY_CONCRETE_POWDER( - 7, - "CONCRETE_POWDER"), - GRAY_DYE( - 8, - "INK_SACK"), - GRAY_GLAZED_TERRACOTTA, - GRAY_SHULKER_BOX, - GRAY_STAINED_GLASS( - 7, - "STAINED_GLASS"), - GRAY_STAINED_GLASS_PANE( - 7, - "THIN_GLASS", - "STAINED_GLASS_PANE"), - GRAY_TERRACOTTA( - 7, - "STAINED_CLAY"), - GRAY_WALL_BANNER( - 8, - "WALL_BANNER"), - GRAY_WOOL( - 7, - "WOOL"), - GREEN_BANNER( - 2, - "STANDING_BANNER", - "BANNER"), - GREEN_BED( - supports(12) ? 13 : 0, - "BED_BLOCK", - "BED"), - GREEN_CANDLE, - GREEN_CANDLE_CAKE, - GREEN_CARPET( - 13, - "CARPET"), - GREEN_CONCRETE( - 13, - "CONCRETE"), - GREEN_CONCRETE_POWDER( - 13, - "CONCRETE_POWDER"), - /** - * 1.13 renamed to CACTUS_GREEN - * 1.14 renamed to GREEN_DYE - */ - GREEN_DYE( - 2, - "INK_SACK", - "CACTUS_GREEN"), - GREEN_GLAZED_TERRACOTTA, - GREEN_SHULKER_BOX, - GREEN_STAINED_GLASS( - 13, - "STAINED_GLASS"), - GREEN_STAINED_GLASS_PANE( - 13, - "THIN_GLASS", - "STAINED_GLASS_PANE"), - GREEN_TERRACOTTA( - 13, - "STAINED_CLAY"), - GREEN_WALL_BANNER( - 2, - "WALL_BANNER"), - GREEN_WOOL( - 13, - "WOOL"), - GRINDSTONE, - GUARDIAN_SPAWN_EGG( - 68, - "MONSTER_EGG"), - GUNPOWDER( - "SULPHUR"), - HANGING_ROOTS, - HAY_BLOCK, - HEART_OF_THE_SEA, - HEAVY_WEIGHTED_PRESSURE_PLATE( - "IRON_PLATE"), - HOGLIN_SPAWN_EGG( - "MONSTER_EGG"), - HONEYCOMB, - HONEYCOMB_BLOCK, - HONEY_BLOCK, - HONEY_BOTTLE, - HOPPER, - HOPPER_MINECART, - HORN_CORAL, - HORN_CORAL_BLOCK, - HORN_CORAL_FAN, - HORN_CORAL_WALL_FAN, - HORSE_SPAWN_EGG( - 100, - "MONSTER_EGG"), - HUSK_SPAWN_EGG( - 23, - "MONSTER_EGG"), - ICE, - INFESTED_CHISELED_STONE_BRICKS( - 5, - "MONSTER_EGGS"), - INFESTED_COBBLESTONE( - 1, - "MONSTER_EGGS"), - INFESTED_CRACKED_STONE_BRICKS( - 4, - "MONSTER_EGGS"), - INFESTED_DEEPSLATE, - INFESTED_MOSSY_STONE_BRICKS( - 3, - "MONSTER_EGGS"), - INFESTED_STONE( - "MONSTER_EGGS"), - INFESTED_STONE_BRICKS( - 2, - "MONSTER_EGGS"), - /** - * We will only add "INK_SAC" for {@link #BLACK_DYE} since it's - * the only material (linked with this material) that is added - * after 1.13, which means it can use both INK_SACK and INK_SAC. - */ - INK_SAC( - "INK_SACK"), - IRON_AXE, - IRON_BARS( - "IRON_FENCE"), - IRON_BLOCK, - IRON_BOOTS, - IRON_CHESTPLATE, - IRON_DOOR( - "IRON_DOOR_BLOCK"), - IRON_HELMET, - IRON_HOE, - IRON_HORSE_ARMOR( - "IRON_BARDING"), - IRON_INGOT, - IRON_LEGGINGS, - IRON_NUGGET, - IRON_ORE, - IRON_PICKAXE, - IRON_SHOVEL( - "IRON_SPADE"), - IRON_SWORD, - IRON_TRAPDOOR, - ITEM_FRAME, - JACK_O_LANTERN, - JIGSAW, - JUKEBOX, - JUNGLE_BOAT( - "BOAT_JUNGLE"), - JUNGLE_BUTTON( - "WOOD_BUTTON"), - JUNGLE_DOOR( - "JUNGLE_DOOR", - "JUNGLE_DOOR_ITEM"), - JUNGLE_FENCE, - JUNGLE_FENCE_GATE, - JUNGLE_LEAVES( - 3, - "LEAVES"), - JUNGLE_LOG( - 3, - "LOG"), - JUNGLE_PLANKS( - 3, - "WOOD"), - JUNGLE_PRESSURE_PLATE( - "WOOD_PLATE"), - JUNGLE_SAPLING( - 3, - "SAPLING"), - JUNGLE_SIGN( - "SIGN_POST", - "SIGN"), - JUNGLE_SLAB( - 3, - "WOOD_DOUBLE_STEP", - "WOOD_STEP", - "WOODEN_SLAB"), - JUNGLE_STAIRS( - "JUNGLE_WOOD_STAIRS"), - JUNGLE_TRAPDOOR( - "TRAP_DOOR"), - JUNGLE_WALL_SIGN( - "WALL_SIGN"), - JUNGLE_WOOD( - 3, - "LOG"), - KELP, - KELP_PLANT, - KNOWLEDGE_BOOK( - "BOOK"), - LADDER, - LANTERN, - LAPIS_BLOCK, - LAPIS_LAZULI( - 4, - "INK_SACK"), - LAPIS_ORE, - LARGE_AMETHYST_BUD, - LARGE_FERN( - 3, - "DOUBLE_PLANT"), - LAVA( - "STATIONARY_LAVA"), - LAVA_BUCKET, - LAVA_CAULDRON, - LEAD( - "LEASH"), - LEATHER, - LEATHER_BOOTS, - LEATHER_CHESTPLATE, - LEATHER_HELMET, - LEATHER_HORSE_ARMOR( - "IRON_HORSE_ARMOR"), - LEATHER_LEGGINGS, - LECTERN, - LEVER, - LIGHT, - LIGHTNING_ROD, - LIGHT_BLUE_BANNER( - 12, - "STANDING_BANNER", - "BANNER"), - LIGHT_BLUE_BED( - supports(12) ? 3 : 0, - "BED_BLOCK", - "BED"), - LIGHT_BLUE_CANDLE, - LIGHT_BLUE_CANDLE_CAKE, - LIGHT_BLUE_CARPET( - 3, - "CARPET"), - LIGHT_BLUE_CONCRETE( - 3, - "CONCRETE"), - LIGHT_BLUE_CONCRETE_POWDER( - 3, - "CONCRETE_POWDER"), - LIGHT_BLUE_DYE( - 12, - "INK_SACK"), - LIGHT_BLUE_GLAZED_TERRACOTTA, - LIGHT_BLUE_SHULKER_BOX, - LIGHT_BLUE_STAINED_GLASS( - 3, - "STAINED_GLASS"), - LIGHT_BLUE_STAINED_GLASS_PANE( - 3, - "THIN_GLASS", - "STAINED_GLASS_PANE"), - LIGHT_BLUE_TERRACOTTA( - 3, - "STAINED_CLAY"), - LIGHT_BLUE_WALL_BANNER( - 12, - "WALL_BANNER", - "STANDING_BANNER", - "BANNER"), - LIGHT_BLUE_WOOL( - 3, - "WOOL"), - LIGHT_GRAY_BANNER( - 7, - "STANDING_BANNER", - "BANNER"), - LIGHT_GRAY_BED( - supports(12) ? 8 : 0, - "BED_BLOCK", - "BED"), - LIGHT_GRAY_CANDLE, - LIGHT_GRAY_CANDLE_CAKE, - LIGHT_GRAY_CARPET( - 8, - "CARPET"), - LIGHT_GRAY_CONCRETE( - 8, - "CONCRETE"), - LIGHT_GRAY_CONCRETE_POWDER( - 8, - "CONCRETE_POWDER"), - LIGHT_GRAY_DYE( - 7, - "INK_SACK"), - /** - * Renamed to SILVER_GLAZED_TERRACOTTA in 1.12 - * Renamed to LIGHT_GRAY_GLAZED_TERRACOTTA in 1.14 - */ - LIGHT_GRAY_GLAZED_TERRACOTTA( - "SILVER_GLAZED_TERRACOTTA"), - LIGHT_GRAY_SHULKER_BOX( - "SILVER_SHULKER_BOX"), - LIGHT_GRAY_STAINED_GLASS( - 8, - "STAINED_GLASS"), - LIGHT_GRAY_STAINED_GLASS_PANE( - 8, - "THIN_GLASS", - "STAINED_GLASS_PANE"), - LIGHT_GRAY_TERRACOTTA( - 8, - "STAINED_CLAY"), - LIGHT_GRAY_WALL_BANNER( - 7, - "WALL_BANNER"), - LIGHT_GRAY_WOOL( - 8, - "WOOL"), - LIGHT_WEIGHTED_PRESSURE_PLATE( - "GOLD_PLATE"), - LILAC( - 1, - "DOUBLE_PLANT"), - LILY_OF_THE_VALLEY, - LILY_PAD( - "WATER_LILY"), - LIME_BANNER( - 10, - "STANDING_BANNER", - "BANNER"), - LIME_BED( - supports(12) ? 5 : 0, - "BED_BLOCK", - "BED"), - LIME_CANDLE, - LIME_CANDLE_CAKE, - LIME_CARPET( - 5, - "CARPET"), - LIME_CONCRETE( - 5, - "CONCRETE"), - LIME_CONCRETE_POWDER( - 5, - "CONCRETE_POWDER"), - LIME_DYE( - 10, - "INK_SACK"), - LIME_GLAZED_TERRACOTTA, - LIME_SHULKER_BOX, - LIME_STAINED_GLASS( - 5, - "STAINED_GLASS"), - LIME_STAINED_GLASS_PANE( - 5, - "STAINED_GLASS_PANE"), - LIME_TERRACOTTA( - 5, - "STAINED_CLAY"), - LIME_WALL_BANNER( - 10, - "WALL_BANNER"), - LIME_WOOL( - 5, - "WOOL"), - LINGERING_POTION, - LLAMA_SPAWN_EGG( - 103, - "MONSTER_EGG"), - LODESTONE, - LOOM, - MAGENTA_BANNER( - 13, - "STANDING_BANNER", - "BANNER"), - MAGENTA_BED( - supports(12) ? 2 : 0, - "BED_BLOCK", - "BED"), - MAGENTA_CANDLE, - MAGENTA_CANDLE_CAKE, - MAGENTA_CARPET( - 2, - "CARPET"), - MAGENTA_CONCRETE( - 2, - "CONCRETE"), - MAGENTA_CONCRETE_POWDER( - 2, - "CONCRETE_POWDER"), - MAGENTA_DYE( - 13, - "INK_SACK"), - MAGENTA_GLAZED_TERRACOTTA, - MAGENTA_SHULKER_BOX, - MAGENTA_STAINED_GLASS( - 2, - "STAINED_GLASS"), - MAGENTA_STAINED_GLASS_PANE( - 2, - "THIN_GLASS", - "STAINED_GLASS_PANE"), - MAGENTA_TERRACOTTA( - 2, - "STAINED_CLAY"), - MAGENTA_WALL_BANNER( - 13, - "WALL_BANNER"), - MAGENTA_WOOL( - 2, - "WOOL"), - MAGMA_BLOCK( - "MAGMA"), - MAGMA_CREAM, - MAGMA_CUBE_SPAWN_EGG( - 62, - "MONSTER_EGG"), - /** - * Adding this to the duplicated list will give you a filled map - * for 1.13+ versions and removing it from duplicated list will - * still give you a filled map in -1.12 versions. - * Since higher versions are our priority I'll keep 1.13+ support - * until I can come up with something to fix it. - */ - MAP( - "EMPTY_MAP"), - MEDIUM_AMETHYST_BUD, - MELON( - "MELON_BLOCK"), - MELON_SEEDS, - MELON_SLICE( - "MELON"), - MELON_STEM, - MILK_BUCKET, - MINECART, - MOJANG_BANNER_PATTERN, - MOOSHROOM_SPAWN_EGG( - 96, - "MONSTER_EGG"), - MOSSY_COBBLESTONE, - MOSSY_COBBLESTONE_SLAB(), - MOSSY_COBBLESTONE_STAIRS, - MOSSY_COBBLESTONE_WALL( - 1, - "COBBLE_WALL", - "COBBLESTONE_WALL"), - MOSSY_STONE_BRICKS( - 1, - "SMOOTH_BRICK"), - MOSSY_STONE_BRICK_SLAB, - MOSSY_STONE_BRICK_STAIRS, - MOSSY_STONE_BRICK_WALL, - MOSS_BLOCK, - MOSS_CARPET, - MOVING_PISTON( - "PISTON_MOVING_PIECE"), - MULE_SPAWN_EGG( - 32, - "MONSTER_EGG"), - MUSHROOM_STEM( - "BROWN_MUSHROOM"), - MUSHROOM_STEW( - "MUSHROOM_SOUP"), - MUSIC_DISC_11( - "GOLD_RECORD"), - MUSIC_DISC_13( - "GREEN_RECORD"), - MUSIC_DISC_BLOCKS( - "RECORD_3"), - MUSIC_DISC_CAT( - "RECORD_4"), - MUSIC_DISC_CHIRP( - "RECORD_5"), - MUSIC_DISC_FAR( - "RECORD_6"), - MUSIC_DISC_MALL( - "RECORD_7"), - MUSIC_DISC_MELLOHI( - "RECORD_8"), - MUSIC_DISC_OTHERSIDE, - MUSIC_DISC_PIGSTEP, - MUSIC_DISC_STAL( - "RECORD_9"), - MUSIC_DISC_STRAD( - "RECORD_10"), - MUSIC_DISC_WAIT( - "RECORD_11"), - MUSIC_DISC_WARD( - "RECORD_12"), - MUTTON, - MYCELIUM( - "MYCEL"), - NAME_TAG, - NAUTILUS_SHELL, - NETHERITE_AXE, - NETHERITE_BLOCK, - NETHERITE_BOOTS, - NETHERITE_CHESTPLATE, - NETHERITE_HELMET, - NETHERITE_HOE, - NETHERITE_INGOT, - NETHERITE_LEGGINGS, - NETHERITE_PICKAXE, - NETHERITE_SCRAP, - NETHERITE_SHOVEL, - NETHERITE_SWORD, - NETHERRACK, - NETHER_BRICK( - "NETHER_BRICK_ITEM"), - NETHER_BRICKS( - "NETHER_BRICK"), - NETHER_BRICK_FENCE( - "NETHER_FENCE"), - NETHER_BRICK_SLAB( - 6, - "STEP"), - NETHER_BRICK_STAIRS, - NETHER_BRICK_WALL, - NETHER_GOLD_ORE, - NETHER_PORTAL( - "PORTAL"), - NETHER_QUARTZ_ORE( - "QUARTZ_ORE"), - NETHER_SPROUTS, - NETHER_STAR, - /** - * Just like mentioned in https://minecraft.gamepedia.com/Nether_Wart - * Nether wart is also known as nether stalk in the code. - * NETHER_STALK is the planted state of nether warts. - */ - NETHER_WART( - "NETHER_WARTS", - "NETHER_STALK"), - NETHER_WART_BLOCK, - NOTE_BLOCK, - OAK_BOAT( - "BOAT"), - OAK_BUTTON( - "WOOD_BUTTON"), - OAK_DOOR( - "WOODEN_DOOR", - "WOOD_DOOR"), - OAK_FENCE( - "FENCE"), - OAK_FENCE_GATE( - "FENCE_GATE"), - OAK_LEAVES( - "LEAVES"), - OAK_LOG( - "LOG"), - OAK_PLANKS( - "WOOD"), - OAK_PRESSURE_PLATE( - "WOOD_PLATE"), - OAK_SAPLING( - "SAPLING"), - OAK_SIGN( - "SIGN_POST", - "SIGN"), - OAK_SLAB( - "WOOD_DOUBLE_STEP", - "WOOD_STEP", - "WOODEN_SLAB"), - OAK_STAIRS( - "WOOD_STAIRS"), - OAK_TRAPDOOR( - "TRAP_DOOR"), - OAK_WALL_SIGN( - "WALL_SIGN"), - OAK_WOOD( - "LOG"), - OBSERVER, - OBSIDIAN, - OCELOT_SPAWN_EGG( - 98, - "MONSTER_EGG"), - ORANGE_BANNER( - 14, - "STANDING_BANNER", - "BANNER"), - ORANGE_BED( - supports(12) ? 1 : 0, - "BED_BLOCK", - "BED"), - ORANGE_CANDLE, - ORANGE_CANDLE_CAKE, - ORANGE_CARPET( - 1, - "CARPET"), - ORANGE_CONCRETE( - 1, - "CONCRETE"), - ORANGE_CONCRETE_POWDER( - 1, - "CONCRETE_POWDER"), - ORANGE_DYE( - 14, - "INK_SACK"), - ORANGE_GLAZED_TERRACOTTA, - ORANGE_SHULKER_BOX, - ORANGE_STAINED_GLASS( - 1, - "STAINED_GLASS"), - ORANGE_STAINED_GLASS_PANE( - 1, - "STAINED_GLASS_PANE"), - ORANGE_TERRACOTTA( - 1, - "STAINED_CLAY"), - ORANGE_TULIP( - 5, - "RED_ROSE"), - ORANGE_WALL_BANNER( - 14, - "WALL_BANNER"), - ORANGE_WOOL( - 1, - "WOOL"), - OXEYE_DAISY( - 8, - "RED_ROSE"), - OXIDIZED_COPPER, - OXIDIZED_CUT_COPPER, - OXIDIZED_CUT_COPPER_SLAB, - OXIDIZED_CUT_COPPER_STAIRS, - PACKED_ICE, - PAINTING, - PANDA_SPAWN_EGG, - PAPER, - PARROT_SPAWN_EGG( - 105, - "MONSTER_EGG"), - PEONY( - 5, - "DOUBLE_PLANT"), - PETRIFIED_OAK_SLAB( - "WOOD_STEP"), - PHANTOM_MEMBRANE, - PHANTOM_SPAWN_EGG, - PIGLIN_BANNER_PATTERN, - PIGLIN_BRUTE_SPAWN_EGG, - PIGLIN_SPAWN_EGG( - 57, - "MONSTER_EGG"), - PIG_SPAWN_EGG( - 90, - "MONSTER_EGG"), - PILLAGER_SPAWN_EGG, - PINK_BANNER( - 9, - "STANDING_BANNER", - "BANNER"), - PINK_BED( - supports(12) ? 6 : 0, - "BED_BLOCK", - "BED"), - PINK_CANDLE, - PINK_CANDLE_CAKE, - PINK_CARPET( - 6, - "CARPET"), - PINK_CONCRETE( - 6, - "CONCRETE"), - PINK_CONCRETE_POWDER( - 6, - "CONCRETE_POWDER"), - PINK_DYE( - 9, - "INK_SACK"), - PINK_GLAZED_TERRACOTTA, - PINK_SHULKER_BOX, - PINK_STAINED_GLASS( - 6, - "STAINED_GLASS"), - PINK_STAINED_GLASS_PANE( - 6, - "THIN_GLASS", - "STAINED_GLASS_PANE"), - PINK_TERRACOTTA( - 6, - "STAINED_CLAY"), - PINK_TULIP( - 7, - "RED_ROSE"), - PINK_WALL_BANNER( - 9, - "WALL_BANNER"), - PINK_WOOL( - 6, - "WOOL"), - PISTON( - "PISTON_BASE"), - PISTON_HEAD( - "PISTON_EXTENSION"), - PLAYER_HEAD( - 3, - "SKULL", - "SKULL_ITEM"), - PLAYER_WALL_HEAD( - 3, - "SKULL", - "SKULL_ITEM"), - PODZOL( - 2, - "DIRT"), - POINTED_DRIPSTONE, - POISONOUS_POTATO, - POLAR_BEAR_SPAWN_EGG( - 102, - "MONSTER_EGG"), - POLISHED_ANDESITE( - 6, - "STONE"), - POLISHED_ANDESITE_SLAB, - POLISHED_ANDESITE_STAIRS, - POLISHED_BASALT, - POLISHED_BLACKSTONE, - POLISHED_BLACKSTONE_BRICKS, - POLISHED_BLACKSTONE_BRICK_SLAB, - POLISHED_BLACKSTONE_BRICK_STAIRS, - POLISHED_BLACKSTONE_BRICK_WALL, - POLISHED_BLACKSTONE_BUTTON, - POLISHED_BLACKSTONE_PRESSURE_PLATE, - POLISHED_BLACKSTONE_SLAB, - POLISHED_BLACKSTONE_STAIRS, - POLISHED_BLACKSTONE_WALL, - POLISHED_DEEPSLATE, - POLISHED_DEEPSLATE_SLAB, - POLISHED_DEEPSLATE_STAIRS, - POLISHED_DEEPSLATE_WALL, - POLISHED_DIORITE( - 4, - "STONE"), - POLISHED_DIORITE_SLAB, - POLISHED_DIORITE_STAIRS, - POLISHED_GRANITE( - 2, - "STONE"), - POLISHED_GRANITE_SLAB, - POLISHED_GRANITE_STAIRS, - POPPED_CHORUS_FRUIT( - "CHORUS_FRUIT_POPPED"), - POPPY( - "RED_ROSE"), - PORKCHOP( - "PORK"), - POTATO( - "POTATO_ITEM"), - POTATOES( - "POTATO"), - POTION, - POTTED_ACACIA_SAPLING( - 4, - "FLOWER_POT"), - POTTED_ALLIUM( - 2, - "RED_ROSE", - "FLOWER_POT"), - POTTED_AZALEA_BUSH, - POTTED_AZURE_BLUET( - 3, - "RED_ROSE", - "FLOWER_POT"), - POTTED_BAMBOO, - POTTED_BIRCH_SAPLING( - 2, - "FLOWER_POT"), - POTTED_BLUE_ORCHID( - 1, - "RED_ROSE", - "FLOWER_POT"), - POTTED_BROWN_MUSHROOM( - "FLOWER_POT"), - POTTED_CACTUS( - "FLOWER_POT"), - POTTED_CORNFLOWER, - POTTED_CRIMSON_FUNGUS, - POTTED_CRIMSON_ROOTS, - POTTED_DANDELION( - "YELLOW_FLOWER", - "FLOWER_POT"), - POTTED_DARK_OAK_SAPLING( - 5, - "FLOWER_POT"), - POTTED_DEAD_BUSH( - "FLOWER_POT"), - POTTED_FERN( - 2, - "LONG_GRASS", - "FLOWER_POT"), - POTTED_FLOWERING_AZALEA_BUSH, - POTTED_JUNGLE_SAPLING( - 3, - "FLOWER_POT"), - POTTED_LILY_OF_THE_VALLEY, - POTTED_OAK_SAPLING( - "FLOWER_POT"), - POTTED_ORANGE_TULIP( - 5, - "RED_ROSE", - "FLOWER_POT"), - POTTED_OXEYE_DAISY( - 8, - "RED_ROSE", - "FLOWER_POT"), - POTTED_PINK_TULIP( - 7, - "RED_ROSE", - "FLOWER_POT"), - POTTED_POPPY( - "RED_ROSE", - "FLOWER_POT"), - POTTED_RED_MUSHROOM( - "FLOWER_POT"), - POTTED_RED_TULIP( - 4, - "RED_ROSE", - "FLOWER_POT"), - POTTED_SPRUCE_SAPLING( - 1, - "FLOWER_POT"), - POTTED_WARPED_FUNGUS, - POTTED_WARPED_ROOTS, - POTTED_WHITE_TULIP( - 6, - "RED_ROSE", - "FLOWER_POT"), - POTTED_WITHER_ROSE, - POWDER_SNOW, - POWDER_SNOW_BUCKET, - POWDER_SNOW_CAULDRON, - POWERED_RAIL, - PRISMARINE, - PRISMARINE_BRICKS( - 1, - "PRISMARINE"), - PRISMARINE_BRICK_SLAB, - PRISMARINE_BRICK_STAIRS, - PRISMARINE_CRYSTALS, - PRISMARINE_SHARD, - PRISMARINE_SLAB, - PRISMARINE_STAIRS, - PRISMARINE_WALL, - PUFFERFISH( - 3, - "RAW_FISH"), - PUFFERFISH_BUCKET, - PUFFERFISH_SPAWN_EGG, - PUMPKIN, - PUMPKIN_PIE, - PUMPKIN_SEEDS, - PUMPKIN_STEM, - PURPLE_BANNER( - 5, - "STANDING_BANNER", - "BANNER"), - PURPLE_BED( - supports(12) ? 10 : 0, - "BED_BLOCK", - "BED"), - PURPLE_CANDLE, - PURPLE_CANDLE_CAKE, - PURPLE_CARPET( - 10, - "CARPET"), - PURPLE_CONCRETE( - 10, - "CONCRETE"), - PURPLE_CONCRETE_POWDER( - 10, - "CONCRETE_POWDER"), - PURPLE_DYE( - 5, - "INK_SACK"), - PURPLE_GLAZED_TERRACOTTA, - PURPLE_SHULKER_BOX, - PURPLE_STAINED_GLASS( - 10, - "STAINED_GLASS"), - PURPLE_STAINED_GLASS_PANE( - 10, - "THIN_GLASS", - "STAINED_GLASS_PANE"), - PURPLE_TERRACOTTA( - 10, - "STAINED_CLAY"), - PURPLE_WALL_BANNER( - 5, - "WALL_BANNER"), - PURPLE_WOOL( - 10, - "WOOL"), - PURPUR_BLOCK, - PURPUR_PILLAR, - PURPUR_SLAB( - "PURPUR_DOUBLE_SLAB"), - PURPUR_STAIRS, - QUARTZ, - QUARTZ_BLOCK, - QUARTZ_BRICKS, - QUARTZ_PILLAR( - 2, - "QUARTZ_BLOCK"), - QUARTZ_SLAB( - 7, - "STEP"), - QUARTZ_STAIRS, - RABBIT, - RABBIT_FOOT, - RABBIT_HIDE, - RABBIT_SPAWN_EGG( - 101, - "MONSTER_EGG"), - RABBIT_STEW, - RAIL( - "RAILS"), - RAVAGER_SPAWN_EGG, - RAW_COPPER, - RAW_COPPER_BLOCK, - RAW_GOLD, - RAW_GOLD_BLOCK, - RAW_IRON, - RAW_IRON_BLOCK, - REDSTONE, - REDSTONE_BLOCK, - /** - * Unlike redstone torch, REDSTONE_LAMP_ON isn't an item. - * The name is just here on the list for matching. - * - * @see #REDSTONE_TORCH - */ - REDSTONE_LAMP( - "REDSTONE_LAMP_ON", - "REDSTONE_LAMP_OFF"), - REDSTONE_ORE( - "GLOWING_REDSTONE_ORE"), - /** - * REDSTONE_TORCH_OFF isn't an item, but a block. - * But REDSTONE_TORCH_ON is the item. - * The name is just here on the list for matching. - */ - REDSTONE_TORCH( - "REDSTONE_TORCH_OFF", - "REDSTONE_TORCH_ON"), - REDSTONE_WALL_TORCH, - REDSTONE_WIRE, - RED_BANNER( - 1, - "STANDING_BANNER", - "BANNER"), - /** - * Data value 14 or 0 - */ - RED_BED( - supports(12) ? 14 : 0, - "BED_BLOCK", - "BED"), - RED_CANDLE, - RED_CANDLE_CAKE, - RED_CARPET( - 14, - "CARPET"), - RED_CONCRETE( - 14, - "CONCRETE"), - RED_CONCRETE_POWDER( - 14, - "CONCRETE_POWDER"), - RED_DYE( - 1, - "INK_SACK", - "ROSE_RED"), - RED_GLAZED_TERRACOTTA, - RED_MUSHROOM, - RED_MUSHROOM_BLOCK( - "RED_MUSHROOM", - "HUGE_MUSHROOM_2"), - RED_NETHER_BRICKS( - "RED_NETHER_BRICK"), - RED_NETHER_BRICK_SLAB, - RED_NETHER_BRICK_STAIRS, - RED_NETHER_BRICK_WALL, - RED_SAND( - 1, - "SAND"), - RED_SANDSTONE, - RED_SANDSTONE_SLAB( - "DOUBLE_STONE_SLAB2", - "STONE_SLAB2"), - RED_SANDSTONE_STAIRS, - RED_SANDSTONE_WALL, - RED_SHULKER_BOX, - RED_STAINED_GLASS( - 14, - "STAINED_GLASS"), - RED_STAINED_GLASS_PANE( - 14, - "THIN_GLASS", - "STAINED_GLASS_PANE"), - RED_TERRACOTTA( - 14, - "STAINED_CLAY"), - RED_TULIP( - 4, - "RED_ROSE"), - RED_WALL_BANNER( - 1, - "WALL_BANNER"), - RED_WOOL( - 14, - "WOOL"), - REPEATER( - "DIODE_BLOCK_ON", - "DIODE_BLOCK_OFF", - "DIODE"), - REPEATING_COMMAND_BLOCK( - "COMMAND", - "COMMAND_REPEATING"), - RESPAWN_ANCHOR, - ROOTED_DIRT, - ROSE_BUSH( - 4, - "DOUBLE_PLANT"), - ROTTEN_FLESH, - SADDLE, - SALMON( - 1, - "RAW_FISH"), - SALMON_BUCKET, - SALMON_SPAWN_EGG, - SAND, - SANDSTONE, - SANDSTONE_SLAB( - 1, - "DOUBLE_STEP", - "STEP", - "STONE_SLAB"), - SANDSTONE_STAIRS, - SANDSTONE_WALL, - SCAFFOLDING, - SCULK_SENSOR, - SCUTE, - SEAGRASS, - SEA_LANTERN, - SEA_PICKLE, - SHEARS, - SHEEP_SPAWN_EGG( - 91, - "MONSTER_EGG"), - SHIELD, - SHROOMLIGHT, - SHULKER_BOX( - "PURPLE_SHULKER_BOX"), - SHULKER_SHELL, - SHULKER_SPAWN_EGG( - 69, - "MONSTER_EGG"), - SILVERFISH_SPAWN_EGG( - 60, - "MONSTER_EGG"), - SKELETON_HORSE_SPAWN_EGG( - 28, - "MONSTER_EGG"), - SKELETON_SKULL( - "SKULL", - "SKULL_ITEM"), - SKELETON_SPAWN_EGG( - 51, - "MONSTER_EGG"), - SKELETON_WALL_SKULL( - "SKULL", - "SKULL_ITEM"), - SKULL_BANNER_PATTERN, - SLIME_BALL, - SLIME_BLOCK, - SLIME_SPAWN_EGG( - 55, - "MONSTER_EGG"), - SMALL_AMETHYST_BUD, - SMALL_DRIPLEAF, - SMITHING_TABLE, - SMOKER, - SMOOTH_BASALT, - SMOOTH_QUARTZ, - SMOOTH_QUARTZ_SLAB, - SMOOTH_QUARTZ_STAIRS, - SMOOTH_RED_SANDSTONE( - 2, - "RED_SANDSTONE"), - SMOOTH_RED_SANDSTONE_SLAB( - "STONE_SLAB2"), - SMOOTH_RED_SANDSTONE_STAIRS, - SMOOTH_SANDSTONE( - 2, - "SANDSTONE"), - SMOOTH_SANDSTONE_SLAB, - SMOOTH_SANDSTONE_STAIRS, - SMOOTH_STONE, - SMOOTH_STONE_SLAB, - SNOW, - SNOWBALL( - "SNOW_BALL"), - SNOW_BLOCK, - SOUL_CAMPFIRE, - SOUL_FIRE, - SOUL_LANTERN, - SOUL_SAND, - SOUL_SOIL, - SOUL_TORCH, - SOUL_WALL_TORCH, - SPAWNER( - "MOB_SPAWNER"), - SPECTRAL_ARROW, - SPIDER_EYE, - SPIDER_SPAWN_EGG( - 52, - "MONSTER_EGG"), - SPLASH_POTION, - SPONGE, - SPORE_BLOSSOM, - SPRUCE_BOAT( - "BOAT_SPRUCE"), - SPRUCE_BUTTON( - "WOOD_BUTTON"), - SPRUCE_DOOR( - "SPRUCE_DOOR", - "SPRUCE_DOOR_ITEM"), - SPRUCE_FENCE, - SPRUCE_FENCE_GATE, - SPRUCE_LEAVES( - 1, - "LEAVES"), - SPRUCE_LOG( - 1, - "LOG"), - SPRUCE_PLANKS( - 1, - "WOOD"), - SPRUCE_PRESSURE_PLATE( - "WOOD_PLATE"), - SPRUCE_SAPLING( - 1, - "SAPLING"), - SPRUCE_SIGN( - "SIGN_POST", - "SIGN"), - SPRUCE_SLAB( - 1, - "WOOD_DOUBLE_STEP", - "WOOD_STEP", - "WOODEN_SLAB"), - SPRUCE_STAIRS( - "SPRUCE_WOOD_STAIRS"), - SPRUCE_TRAPDOOR( - "TRAP_DOOR"), - SPRUCE_WALL_SIGN( - "WALL_SIGN"), - SPRUCE_WOOD( - 1, - "LOG"), - SPYGLASS, - SQUID_SPAWN_EGG( - 94, - "MONSTER_EGG"), - STICK, - STICKY_PISTON( - "PISTON_BASE", - "PISTON_STICKY_BASE"), - STONE, - STONECUTTER, - STONE_AXE, - STONE_BRICKS( - "SMOOTH_BRICK"), - STONE_BRICK_SLAB( - 5, - "DOUBLE_STEP", - "STEP", - "STONE_SLAB"), - STONE_BRICK_STAIRS( - "SMOOTH_STAIRS"), - STONE_BRICK_WALL, - STONE_BUTTON, - STONE_HOE, - STONE_PICKAXE, - STONE_PRESSURE_PLATE( - "STONE_PLATE"), - STONE_SHOVEL( - "STONE_SPADE"), - STONE_SLAB( - "DOUBLE_STEP", - "STEP"), - STONE_STAIRS, - STONE_SWORD, - STRAY_SPAWN_EGG( - 6, - "MONSTER_EGG"), - STRIDER_SPAWN_EGG, - STRING, - STRIPPED_ACACIA_LOG( - "LOG_2"), - STRIPPED_ACACIA_WOOD( - "LOG_2"), - STRIPPED_BIRCH_LOG( - 2, - "LOG"), - STRIPPED_BIRCH_WOOD( - 2, - "LOG"), - STRIPPED_CRIMSON_HYPHAE, - STRIPPED_CRIMSON_STEM, - STRIPPED_DARK_OAK_LOG( - "LOG"), - STRIPPED_DARK_OAK_WOOD( - "LOG"), - STRIPPED_JUNGLE_LOG( - 3, - "LOG"), - STRIPPED_JUNGLE_WOOD( - 3, - "LOG"), - STRIPPED_OAK_LOG( - "LOG"), - STRIPPED_OAK_WOOD( - "LOG"), - STRIPPED_SPRUCE_LOG( - 1, - "LOG"), - STRIPPED_SPRUCE_WOOD( - 1, - "LOG"), - STRIPPED_WARPED_HYPHAE, - STRIPPED_WARPED_STEM, - STRUCTURE_BLOCK, - /** - * Originally developers used barrier blocks for its purpose. - * So technically this isn't really considered as a suggested material. - */ - STRUCTURE_VOID( - 10, - "BARRIER"), - SUGAR, - /** - * Sugar Cane is a known material in pre-1.13 - */ - SUGAR_CANE( - "SUGAR_CANE_BLOCK"), - SUNFLOWER( - "DOUBLE_PLANT"), - SUSPICIOUS_STEW, - SWEET_BERRIES, - SWEET_BERRY_BUSH, - TALL_GRASS( - 2, - "DOUBLE_PLANT"), - TALL_SEAGRASS, - TARGET, - TERRACOTTA( - "HARD_CLAY"), - TINTED_GLASS, - TIPPED_ARROW, - TNT, - TNT_MINECART( - "EXPLOSIVE_MINECART"), - TORCH, - TOTEM_OF_UNDYING( - "TOTEM"), - TRADER_LLAMA_SPAWN_EGG, - TRAPPED_CHEST, - TRIDENT, - TRIPWIRE, - TRIPWIRE_HOOK, - TROPICAL_FISH( - 2, - "RAW_FISH"), - TROPICAL_FISH_BUCKET( - "BUCKET", - "WATER_BUCKET"), - TROPICAL_FISH_SPAWN_EGG( - "MONSTER_EGG"), - TUBE_CORAL, - TUBE_CORAL_BLOCK, - TUBE_CORAL_FAN, - TUBE_CORAL_WALL_FAN, - TUFF, - TURTLE_EGG, - TURTLE_HELMET, - TURTLE_SPAWN_EGG, - TWISTING_VINES, - TWISTING_VINES_PLANT, - VEX_SPAWN_EGG( - 35, - "MONSTER_EGG"), - VILLAGER_SPAWN_EGG( - 120, - "MONSTER_EGG"), - VINDICATOR_SPAWN_EGG( - 36, - "MONSTER_EGG"), - VINE, - /** - * 1.13 tag is not added because it's the same thing as {@link #AIR} - * - * @see #CAVE_AIR - */ - VOID_AIR( - "AIR"), - WALL_TORCH( - "TORCH"), - WANDERING_TRADER_SPAWN_EGG, - WARPED_BUTTON, - WARPED_DOOR, - WARPED_FENCE, - WARPED_FENCE_GATE, - WARPED_FUNGUS, - WARPED_FUNGUS_ON_A_STICK, - WARPED_HYPHAE, - WARPED_NYLIUM, - WARPED_PLANKS, - WARPED_PRESSURE_PLATE, - WARPED_ROOTS, - WARPED_SIGN( - "SIGN_POST"), - WARPED_SLAB, - WARPED_STAIRS, - WARPED_STEM, - WARPED_TRAPDOOR, - WARPED_WALL_SIGN( - "WALL_SIGN"), - WARPED_WART_BLOCK, - /** - * This is used for blocks only. - * In 1.13- WATER will turn into STATIONARY_WATER after it finished spreading. - * After 1.13+ this uses - * https://hub.spigotmc.org/javadocs/spigot/org/bukkit/block/data/Levelled.html water flowing system. - */ - WATER( - "STATIONARY_WATER"), - WATER_BUCKET, - WATER_CAULDRON, - WAXED_COPPER_BLOCK, - WAXED_CUT_COPPER, - WAXED_CUT_COPPER_SLAB, - WAXED_CUT_COPPER_STAIRS, - WAXED_EXPOSED_COPPER, - WAXED_EXPOSED_CUT_COPPER, - WAXED_EXPOSED_CUT_COPPER_SLAB, - WAXED_EXPOSED_CUT_COPPER_STAIRS, - WAXED_OXIDIZED_COPPER, - WAXED_OXIDIZED_CUT_COPPER, - WAXED_OXIDIZED_CUT_COPPER_SLAB, - WAXED_OXIDIZED_CUT_COPPER_STAIRS, - WAXED_WEATHERED_COPPER, - WAXED_WEATHERED_CUT_COPPER, - WAXED_WEATHERED_CUT_COPPER_SLAB, - WAXED_WEATHERED_CUT_COPPER_STAIRS, - WEATHERED_COPPER, - WEATHERED_CUT_COPPER, - WEATHERED_CUT_COPPER_SLAB, - WEATHERED_CUT_COPPER_STAIRS, - WEEPING_VINES, - WEEPING_VINES_PLANT, - WET_SPONGE( - 1, - "SPONGE"), - /** - * Wheat is a known material in pre-1.13 - */ - WHEAT( - "CROPS"), - WHEAT_SEEDS( - "SEEDS"), - WHITE_BANNER( - 15, - "STANDING_BANNER", - "BANNER"), - WHITE_BED( - "BED_BLOCK", - "BED"), - WHITE_CANDLE, - WHITE_CANDLE_CAKE, - WHITE_CARPET( - "CARPET"), - WHITE_CONCRETE( - "CONCRETE"), - WHITE_CONCRETE_POWDER( - "CONCRETE_POWDER"), - WHITE_DYE( - 15, - "INK_SACK", - "BONE_MEAL"), - WHITE_GLAZED_TERRACOTTA, - WHITE_SHULKER_BOX, - WHITE_STAINED_GLASS( - "STAINED_GLASS"), - WHITE_STAINED_GLASS_PANE( - "THIN_GLASS", - "STAINED_GLASS_PANE"), - WHITE_TERRACOTTA( - "STAINED_CLAY"), - WHITE_TULIP( - 6, - "RED_ROSE"), - WHITE_WALL_BANNER( - 15, - "WALL_BANNER"), - WHITE_WOOL( - "WOOL"), - WITCH_SPAWN_EGG( - 66, - "MONSTER_EGG"), - WITHER_ROSE, - WITHER_SKELETON_SKULL( - 1, - "SKULL", - "SKULL_ITEM"), - WITHER_SKELETON_SPAWN_EGG( - 5, - "MONSTER_EGG"), - WITHER_SKELETON_WALL_SKULL( - 1, - "SKULL", - "SKULL_ITEM"), - WOLF_SPAWN_EGG( - 95, - "MONSTER_EGG"), - WOODEN_AXE( - "WOOD_AXE"), - WOODEN_HOE( - "WOOD_HOE"), - WOODEN_PICKAXE( - "WOOD_PICKAXE"), - WOODEN_SHOVEL( - "WOOD_SPADE"), - WOODEN_SWORD( - "WOOD_SWORD"), - WRITABLE_BOOK( - "BOOK_AND_QUILL"), - WRITTEN_BOOK, - YELLOW_BANNER( - 11, - "STANDING_BANNER", - "BANNER"), - YELLOW_BED( - supports(12) ? 4 : 0, - "BED_BLOCK", - "BED"), - YELLOW_CANDLE, - YELLOW_CANDLE_CAKE, - YELLOW_CARPET( - 4, - "CARPET"), - YELLOW_CONCRETE( - 4, - "CONCRETE"), - YELLOW_CONCRETE_POWDER( - 4, - "CONCRETE_POWDER"), - YELLOW_DYE( - 11, - "INK_SACK", - "DANDELION_YELLOW"), - YELLOW_GLAZED_TERRACOTTA, - YELLOW_SHULKER_BOX, - YELLOW_STAINED_GLASS( - 4, - "STAINED_GLASS"), - YELLOW_STAINED_GLASS_PANE( - 4, - "THIN_GLASS", - "STAINED_GLASS_PANE"), - YELLOW_TERRACOTTA( - 4, - "STAINED_CLAY"), - YELLOW_WALL_BANNER( - 11, - "WALL_BANNER"), - YELLOW_WOOL( - 4, - "WOOL"), - ZOGLIN_SPAWN_EGG, - ZOMBIE_HEAD( - 2, - "SKULL", - "SKULL_ITEM"), - ZOMBIE_HORSE_SPAWN_EGG( - 29, - "MONSTER_EGG"), - ZOMBIE_SPAWN_EGG( - 54, - "MONSTER_EGG"), - ZOMBIE_VILLAGER_SPAWN_EGG( - 27, - "MONSTER_EGG"), - ZOMBIE_WALL_HEAD( - 2, - "SKULL", - "SKULL_ITEM"), - ZOMBIFIED_PIGLIN_SPAWN_EGG( - 57, - "MONSTER_EGG", - "ZOMBIE_PIGMAN_SPAWN_EGG"); - - /** - * Cached array of {@link XMaterial#values()} to avoid allocating memory for - * calling the method every time. - * - * @since 2.0.0 - */ - public static final XMaterial[] VALUES = values(); - - /** - * We don't want to use {@link Enums#getIfPresent(Class, String)} to avoid a few checks. - * - * @since 5.1.0 - */ - private static final Map NAMES = new HashMap<>(); - - /** - * Guava (Google Core Libraries for Java)'s cache for performance and timed caches. - * For strings that match a certain XMaterial. Mostly cached for configs. - * - * @since 1.0.0 - */ - private static final Cache NAME_CACHE = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.HOURS).build(); - /** - * This is used for {@link #isOneOf(Collection)} - * - * @since 3.4.0 - */ - private static final Cache CACHED_REGEX = CacheBuilder.newBuilder().expireAfterAccess(3, TimeUnit.HOURS).build(); - /** - * The maximum data value in the pre-flattening update which belongs to {@link #VILLAGER_SPAWN_EGG}
- * https://minecraftitemids.com/types/spawn-egg - * - * @see #matchXMaterialWithData(String) - * @since 8.0.0 - */ - private static final byte MAX_DATA_VALUE = 120; - /** - * Used to tell the system that the passed object's (name or material) data value - * is not provided or is invalid. - * - * @since 8.0.0 - */ - private static final byte UNKNOWN_DATA_VALUE = -1; - /** - * The maximum material ID before the pre-flattening update which belongs to {@link #MUSIC_DISC_WAIT} - * - * @since 8.1.0 - */ - private static final short MAX_ID = 2267; - /** - * XMaterial Paradox (Duplication Check) - *

- * A set of duplicated material names in 1.13 and 1.12 that will conflict with the legacy names. - * Values are the new material names. This map also contains illegal elements. Check the static initializer for more info. - *

- * Duplications are not useful at all in versions above the flattening update {@link Data#ISFLAT} - * This set is only used for matching materials, for parsing refer to {@link #isDuplicated()} - * - * @since 3.0.0 - */ - private static final Set DUPLICATED; - - static { - for (XMaterial material : VALUES) NAMES.put(material.name(), material); - } - - static { - if (Data.ISFLAT) { - // It's not needed at all if it's the newer version. We can save some memory. - DUPLICATED = null; - }else { - // MELON_SLICE, CARROTS, POTATOES, BEETROOTS, GRASS_BLOCK, BRICKS, NETHER_BRICKS, BROWN_MUSHROOM - // Using the constructor to add elements will decide to allocate more size which we don't need. - DUPLICATED = new HashSet<>(4); - DUPLICATED.add(GRASS.name()); - DUPLICATED.add(MELON.name()); - DUPLICATED.add(BRICK.name()); - DUPLICATED.add(NETHER_BRICK.name()); - } - } - - /** - * The data value of this material https://minecraft.gamepedia.com/Java_Edition_data_values/Pre-flattening - * It's never a negative number. - * - * @see #getData() - */ - private final byte data; - /** - * A list of material names that was being used for older verions. - * - * @see #getLegacy() - */ - @Nonnull - private final String[] legacy; - /** - * The cached Bukkit parsed material. - * - * @see #parseMaterial() - * @since 9.0.0 - */ - @Nullable - private final Material material; - - XMaterial(int data, @Nonnull String... legacy) { - this.data = (byte) data; - this.legacy = legacy; - - Material mat = null; - if ((!Data.ISFLAT && this.isDuplicated()) || (mat = Material.getMaterial(this.name())) == null) { - for (int i = legacy.length - 1; i >= 0; i--) { - mat = Material.getMaterial(legacy[i]); - if (mat != null) break; - } - } - this.material = mat; - } - - XMaterial(String... legacy) { - this(0, legacy); - } - - /** - * Checks if the version is 1.13 Aquatic Update or higher. - * An invocation of this method yields the cached result from the expression: - *

- *

- * {@link #supports(int) 13}} - *
- * - * @return true if 1.13 or higher. - * @see #getVersion() - * @see #supports(int) - * @since 1.0.0 - * @deprecated Use {@code XMaterial.supports(13)} instead. This method name can be confusing. - */ - @Deprecated - public static boolean isNewVersion() { - return Data.ISFLAT; - } - - /** - * This is just an extra method that can be used for many cases. - * It can be used in {@link org.bukkit.event.player.PlayerInteractEvent} - * or when accessing {@link org.bukkit.entity.Player#getMainHand()}, - * or other compatibility related methods. - *

- * An invocation of this method yields exactly the same result as the expression: - *

- *

- * !{@link #supports(int)} 9 - *
- * - * @since 2.0.0 - * @deprecated Use {@code !XMaterial.supports(9)} instead. - */ - @Deprecated - public static boolean isOneEight() { - return !supports(9); - } - - /** - * Gets the XMaterial with this name similar to {@link #valueOf(String)} - * without throwing an exception. - * - * @param name the name of the material. - * - * @return an optional that can be empty. - * @since 5.1.0 - */ - @Nonnull - private static Optional getIfPresent(@Nonnull String name) { - return Optional.ofNullable(NAMES.get(name)); - } - - /** - * The current version of the server. - * - * @return the current server version minor number. - * @see #supports(int) - * @since 2.0.0 - */ - public static int getVersion() { - return Data.VERSION; - } - - /** - * When using 1.13+, this helps to find the old material name - * with its data value using a cached search for optimization. - * - * @see #matchDefinedXMaterial(String, byte) - * @since 1.0.0 - */ - @Nullable - private static XMaterial requestOldXMaterial(@Nonnull String name, byte data) { - String holder = name + data; - XMaterial cache = NAME_CACHE.getIfPresent(holder); - if (cache != null) return cache; - - for (XMaterial material : VALUES) { - // Not using material.name().equals(name) check is intended. - if ((data == UNKNOWN_DATA_VALUE || data == material.data) && material.anyMatchLegacy(name)) { - NAME_CACHE.put(holder, material); - return material; - } - } - - return null; - } - - /** - * Parses the given material name as an XMaterial with a given data - * value in the string if attached. Check {@link #matchXMaterialWithData(String)} for more info. - * - * @see #matchXMaterialWithData(String) - * @see #matchDefinedXMaterial(String, byte) - * @since 2.0.0 - */ - @Nonnull - public static Optional matchXMaterial(@Nonnull String name) { - Validate.notEmpty(name, "Cannot match a material with null or empty material name"); - Optional oldMatch = matchXMaterialWithData(name); - return oldMatch.isPresent() ? oldMatch : matchDefinedXMaterial(format(name), UNKNOWN_DATA_VALUE); - } - - /** - * Parses material name and data value from the specified string. - * The separator for the material name and its data value is {@code :} - * Spaces are allowed. Mostly used when getting materials from config for old school minecrafters. - *

- * Examples - *

-	 *     {@code INK_SACK:1 -> RED_DYE}
-	 *     {@code WOOL: 14  -> RED_WOOL}
-	 * 
- * - * @param name the material string that consists of the material name, data and separator character. - * - * @return the parsed XMaterial. - * @see #matchXMaterial(String) - * @since 3.0.0 - */ - @Nonnull - private static Optional matchXMaterialWithData(@Nonnull String name) { - int index = name.indexOf(':'); - if (index != -1) { - String mat = format(name.substring(0, index)); - try { - // We don't use Byte.parseByte because we have our own range check. - byte data = (byte) Integer.parseInt(StringUtils.deleteWhitespace(name.substring(index + 1))); - return data >= 0 && data < MAX_DATA_VALUE ? matchDefinedXMaterial(mat, data) : matchDefinedXMaterial(mat, UNKNOWN_DATA_VALUE); - }catch (NumberFormatException ignored) { - return matchDefinedXMaterial(mat, UNKNOWN_DATA_VALUE); - } - } - - return Optional.empty(); - } - - /** - * Parses the given material as an XMaterial. - * - * @throws IllegalArgumentException may be thrown as an unexpected exception. - * @see #matchDefinedXMaterial(String, byte) - * @see #matchXMaterial(ItemStack) - * @since 2.0.0 - */ - @Nonnull - public static XMaterial matchXMaterial(@Nonnull Material material) { - Objects.requireNonNull(material, "Cannot match null material"); - return matchDefinedXMaterial(material.name(), UNKNOWN_DATA_VALUE).orElseThrow(() -> new IllegalArgumentException("Unsupported material with no data value: " + material.name())); - } - - /** - * Parses the given item as an XMaterial using its material and data value (durability) - * if not a damageable item {@link ItemStack#getDurability()}. - * - * @param item the ItemStack to match. - * - * @return an XMaterial if matched any. - * @throws IllegalArgumentException may be thrown as an unexpected exception. - * @see #matchXMaterial(Material) - * @since 2.0.0 - */ - @Nonnull - @SuppressWarnings ("deprecation") - public static XMaterial matchXMaterial(@Nonnull ItemStack item) { - Objects.requireNonNull(item, "Cannot match null ItemStack"); - String material = item.getType().name(); - byte data = (byte) (Data.ISFLAT || item.getType().getMaxDurability() > 0 ? 0 : item.getDurability()); - - // They didn't really use the items data value in older versions. - if (!Data.ISFLAT && item.hasItemMeta() && material.equals("MONSTER_EGG")) { - ItemMeta meta = item.getItemMeta(); - if (meta instanceof SpawnEggMeta) { - SpawnEggMeta egg = (SpawnEggMeta) meta; - material = egg.getSpawnedType().name() + "_SPAWN_EGG"; - } - } - - // Potions used the items data value to store - // information about the type of potion in 1.8 - if (!supports(9) && material.endsWith("ION")) { - // There's also 16000+ data value technique, but this is more reliable. - return Potion.fromItemStack(item).isSplash() ? SPLASH_POTION : POTION; - } - - // Refer to the enum for info. - // Currently this is the only material with a non-zero data value - // that has been renamed after the flattening update. - // If this happens to more materials in the future, - // I might have to change then system. - if (Data.ISFLAT && !supports(14) && material.equals("CACTUS_GREEN")) return GREEN_DYE; - - // Check FILLED_MAP enum for more info. - // if (!Data.ISFLAT && item.hasItemMeta() && item.getItemMeta() instanceof org.bukkit.inventory.meta.MapMeta) return FILLED_MAP; - - // No orElseThrow, I don't want to deal with Java's final variable bullshit. - Optional result = matchDefinedXMaterial(material, data); - if (result.isPresent()) return result.get(); - throw new IllegalArgumentException("Unsupported material from item: " + material + " (" + data + ')'); - } - - /** - * The main method that parses the given material name and data value as an XMaterial. - * All the values passed to this method will not be null or empty and are formatted correctly. - * - * @param name the formatted name of the material. - * @param data the data value of the material. Is always 0 or {@link #UNKNOWN_DATA_VALUE} when {@link Data#ISFLAT} - * - * @return an XMaterial (with the same data value if specified) - * @see #matchXMaterial(Material) - * @see #matchXMaterial(int, byte) - * @see #matchXMaterial(ItemStack) - * @since 3.0.0 - */ - @Nonnull - protected static Optional matchDefinedXMaterial(@Nonnull String name, byte data) { - // if (!Boolean.valueOf(Boolean.getBoolean(Boolean.TRUE.toString())).equals(Boolean.FALSE.booleanValue())) return null; - Boolean duplicated = null; - boolean isAMap = name.equalsIgnoreCase("MAP"); - - // Do basic number and boolean checks before accessing more complex enum stuff. - if (Data.ISFLAT || (!isAMap && data <= 0 && !(duplicated = isDuplicated(name)))) { - Optional xMaterial = getIfPresent(name); - if (xMaterial.isPresent()) return xMaterial; - } - // Usually flat versions wouldn't pass this point, but some special materials do. - - XMaterial oldXMaterial = requestOldXMaterial(name, data); - if (oldXMaterial == null) { - // Special case. Refer to FILLED_MAP for more info. - return (data >= 0 && isAMap) ? Optional.of(FILLED_MAP) : Optional.empty(); - } - - if (!Data.ISFLAT && oldXMaterial.isPlural() && (duplicated == null ? isDuplicated(name) : duplicated)) return getIfPresent(name); - return Optional.of(oldXMaterial); - } - - /** - * XMaterial Paradox (Duplication Check) - * Checks if the material has any duplicates. - *

- * Example: - *

{@code MELON, CARROT, POTATO, BEETROOT -> true} - * - * @param name the name of the material to check. - * - * @return true if there's a duplicated material for this material, otherwise false. - * @since 2.0.0 - */ - private static boolean isDuplicated(@Nonnull String name) { - // Don't use matchXMaterial() since this method is being called from matchXMaterial() itself and will cause a StackOverflowError. - return DUPLICATED.contains(name); - } - - /** - * Gets the XMaterial based on the material's ID (Magic Value) and data value.
- * You should avoid using this for performance issues. - * - * @param id the ID (Magic value) of the material. - * @param data the data value of the material. - * - * @return a parsed XMaterial with the same ID and data value. - * @see #matchXMaterial(ItemStack) - * @since 2.0.0 - * @deprecated this method loops through all the available materials and matches their ID using {@link #getId()} - * which takes a really long time. Plugins should no longer support IDs. If you want, you can make a {@link Map} cache yourself. - * This method obviously doesn't work for 1.13+ and will not be supported. This is only here for debugging purposes. - */ - @Nonnull - @Deprecated - public static Optional matchXMaterial(int id, byte data) { - if (id < 0 || id > MAX_ID || data < 0) return Optional.empty(); - for (XMaterial materials : VALUES) { - if (materials.data == data && materials.getId() == id) return Optional.of(materials); - } - return Optional.empty(); - } - - /** - * Attempts to build the string like an enum name. - * Removes all the spaces, and extra non-English characters. Also removes some config/in-game based strings. - * While this method is hard to maintain, it's extremely efficient. It's approximately more than x5 times faster than - * the normal RegEx + String Methods approach for both formatted and unformatted material names. - * - * @param name the material name to modify. - * - * @return an enum name. - * @since 2.0.0 - */ - @Nonnull - protected static String format(@Nonnull String name) { - int len = name.length(); - char[] chs = new char[len]; - int count = 0; - boolean appendUnderline = false; - - for (int i = 0; i < len; i++) { - char ch = name.charAt(i); - - if (!appendUnderline && count != 0 && (ch == '-' || ch == ' ' || ch == '_') && chs[count] != '_') - appendUnderline = true; - else { - boolean number = false; - // Old materials have numbers in them. - if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (number = (ch >= '0' && ch <= '9'))) { - if (appendUnderline) { - chs[count++] = '_'; - appendUnderline = false; - } - - if (number) - chs[count++] = ch; - else chs[count++] = (char) (ch & 0x5f); - } - } - } - - return new String(chs, 0, count); - } - - /** - * Checks if the specified version is the same version or higher than the current server version. - * - * @param version the major version to be checked. "1." is ignored. E.g. 1.12 = 12 | 1.9 = 9 - * - * @return true of the version is equal or higher than the current version. - * @since 2.0.0 - */ - public static boolean supports(int version) { - return Data.VERSION >= version; - } - - public String[] getLegacy() { - return this.legacy; - } - - /** - * XMaterial Paradox (Duplication Check) - * I've concluded that this is just an infinite loop that keeps - * going around the Singular Form and the Plural Form materials. A waste of brain cells and a waste of time. - * This solution works just fine anyway. - *

- * A solution for XMaterial Paradox. - * Manually parses the duplicated materials to find the exact material based on the server version. - * If the name ends with "S" -> Plural Form Material. - * Plural methods are only plural if they're also {@link #DUPLICATED} - *

- * The only special exceptions are {@link #BRICKS} and {@link #NETHER_BRICKS} - * - * @return true if this material is a plural form material, otherwise false. - * @since 8.0.0 - */ - private boolean isPlural() { - // this.name().charAt(this.name().length() - 1) == 'S' - return this == CARROTS || this == POTATOES; - } - - /** - * Checks if the list of given material names matches the given base material. - * Mostly used for configs. - *

- * Supports {@link String#contains} {@code CONTAINS:NAME} and Regular Expression {@code REGEX:PATTERN} formats. - *

- * Example: - *

-	 *     XMaterial material = {@link #matchXMaterial(ItemStack)};
-	 *     if (material.isOneOf(plugin.getConfig().getStringList("disabled-items")) return;
-	 * 
- *
- * {@code CONTAINS} Examples: - *
-	 *     {@code "CONTAINS:CHEST" -> CHEST, ENDERCHEST, TRAPPED_CHEST -> true}
-	 *     {@code "cOnTaINS:dYe" -> GREEN_DYE, YELLOW_DYE, BLUE_DYE, INK_SACK -> true}
-	 * 
- *

- * {@code REGEX} Examples - *

-	 *     {@code "REGEX:^.+_.+_.+$" -> Every Material with 3 underlines or more: SHULKER_SPAWN_EGG, SILVERFISH_SPAWN_EGG, SKELETON_HORSE_SPAWN_EGG}
-	 *     {@code "REGEX:^.{1,3}$" -> Material names that have 3 letters only: BED, MAP, AIR}
-	 * 
- *

- * The reason that there are tags for {@code CONTAINS} and {@code REGEX} is for the performance. - * Although RegEx patterns are cached in this method, - * please avoid using the {@code REGEX} tag if you can use the {@code CONTAINS} tag instead. - * It'll have a huge impact on performance. - * Please avoid using {@code (capturing groups)} there's no use for them in this case. - * If you want to use groups, use {@code (?: non-capturing groups)}. It's faster. - *

- * Want to learn RegEx? You can mess around in RegExr website. - * - * @param materials the material names to check base material on. - * - * @return true if one of the given material names is similar to the base material. - * @since 3.1.1 - */ - public boolean isOneOf(@Nullable Collection materials) { - if (materials == null || materials.isEmpty()) return false; - String name = this.name(); + ACACIA_BOAT("BOAT_ACACIA"), + ACACIA_BUTTON("WOOD_BUTTON"), + ACACIA_CHEST_BOAT, + ACACIA_DOOR("ACACIA_DOOR", "ACACIA_DOOR_ITEM"), + ACACIA_FENCE, + ACACIA_FENCE_GATE, + ACACIA_LEAVES(4, "LEAVES_2"), + ACACIA_LOG(4, "LOG_2"), + ACACIA_PLANKS(4, "WOOD"), + ACACIA_PRESSURE_PLATE("WOOD_PLATE"), + ACACIA_SAPLING(4, "SAPLING"), + ACACIA_SIGN("SIGN_POST", "SIGN"), + ACACIA_SLAB(4, "WOOD_DOUBLE_STEP", "WOOD_STEP", "WOODEN_SLAB"), + ACACIA_STAIRS, + ACACIA_TRAPDOOR("TRAP_DOOR"), + ACACIA_WALL_SIGN("WALL_SIGN"), + ACACIA_WOOD(4, "LOG_2"), + ACTIVATOR_RAIL, + /** + * https://minecraft.gamepedia.com/Air + * {@link Material#isAir()} + * + * @see #VOID_AIR + * @see #CAVE_AIR + */ + AIR, + ALLAY_SPAWN_EGG, + ALLIUM(2, "RED_ROSE"), + AMETHYST_BLOCK, + AMETHYST_CLUSTER, + AMETHYST_SHARD, + ANCIENT_DEBRIS, + ANDESITE(5, "STONE"), + ANDESITE_SLAB, + ANDESITE_STAIRS, + ANDESITE_WALL, + ANVIL, + APPLE, + ARMOR_STAND, + ARROW, + ATTACHED_MELON_STEM(7, "MELON_STEM"), + ATTACHED_PUMPKIN_STEM(7, "PUMPKIN_STEM"), + AXOLOTL_BUCKET, + AXOLOTL_SPAWN_EGG, + AZALEA, + AZALEA_LEAVES, + AZURE_BLUET(3, "RED_ROSE"), + BAKED_POTATO, + BAMBOO, + BAMBOO_SAPLING, + BARREL, + BARRIER, + BASALT, + BAT_SPAWN_EGG(65, "MONSTER_EGG"), + BEACON, + BEDROCK, + BEEF("RAW_BEEF"), + BEEHIVE, + /** + * Beetroot is a known material in pre-1.13 + */ + BEETROOT("BEETROOT_BLOCK"), + BEETROOTS("BEETROOT"), + BEETROOT_SEEDS, + BEETROOT_SOUP, + BEE_NEST, + BEE_SPAWN_EGG, + BELL, + BIG_DRIPLEAF, + BIG_DRIPLEAF_STEM, + BIRCH_BOAT("BOAT_BIRCH"), + BIRCH_BUTTON("WOOD_BUTTON"), + BIRCH_CHEST_BOAT, + BIRCH_DOOR("BIRCH_DOOR", "BIRCH_DOOR_ITEM"), + BIRCH_FENCE, + BIRCH_FENCE_GATE, + BIRCH_LEAVES(2, "LEAVES"), + BIRCH_LOG(2, "LOG"), + BIRCH_PLANKS(2, "WOOD"), + BIRCH_PRESSURE_PLATE("WOOD_PLATE"), + BIRCH_SAPLING(2, "SAPLING"), + BIRCH_SIGN("SIGN_POST", "SIGN"), + BIRCH_SLAB(2, "WOOD_DOUBLE_STEP", "WOOD_STEP", "WOODEN_SLAB"), + BIRCH_STAIRS("BIRCH_WOOD_STAIRS"), + BIRCH_TRAPDOOR("TRAP_DOOR"), + BIRCH_WALL_SIGN("WALL_SIGN"), + BIRCH_WOOD(2, "LOG"), + BLACKSTONE, + BLACKSTONE_SLAB, + BLACKSTONE_STAIRS, + BLACKSTONE_WALL, + BLACK_BANNER("STANDING_BANNER", "BANNER"), + /** + * Version 1.12+ interprets "BED" as BLACK_BED due to enum alphabetic ordering. + */ + BLACK_BED(supports(12) ? 15 : 0, "BED_BLOCK", "BED"), + BLACK_CANDLE, + BLACK_CANDLE_CAKE, + BLACK_CARPET(15, "CARPET"), + BLACK_CONCRETE(15, "CONCRETE"), + BLACK_CONCRETE_POWDER(15, "CONCRETE_POWDER"), + BLACK_DYE, + BLACK_GLAZED_TERRACOTTA, + BLACK_SHULKER_BOX, + BLACK_STAINED_GLASS(15, "STAINED_GLASS"), + BLACK_STAINED_GLASS_PANE(15, "STAINED_GLASS_PANE"), + BLACK_TERRACOTTA(15, "STAINED_CLAY"), + BLACK_WALL_BANNER("WALL_BANNER"), + BLACK_WOOL(15, "WOOL"), + BLAST_FURNACE, + BLAZE_POWDER, + BLAZE_ROD, + BLAZE_SPAWN_EGG(61, "MONSTER_EGG"), + BLUE_BANNER(4, "STANDING_BANNER", "BANNER"), + BLUE_BED(supports(12) ? 11 : 0, "BED_BLOCK", "BED"), + BLUE_CANDLE, + BLUE_CANDLE_CAKE, + BLUE_CARPET(11, "CARPET"), + BLUE_CONCRETE(11, "CONCRETE"), + BLUE_CONCRETE_POWDER(11, "CONCRETE_POWDER"), + BLUE_DYE(4, "INK_SACK", "LAPIS_LAZULI"), + BLUE_GLAZED_TERRACOTTA, + BLUE_ICE, + BLUE_ORCHID(1, "RED_ROSE"), + BLUE_SHULKER_BOX, + BLUE_STAINED_GLASS(11, "STAINED_GLASS"), + BLUE_STAINED_GLASS_PANE(11, "THIN_GLASS", "STAINED_GLASS_PANE"), + BLUE_TERRACOTTA(11, "STAINED_CLAY"), + BLUE_WALL_BANNER(4, "WALL_BANNER"), + BLUE_WOOL(11, "WOOL"), + BONE, + BONE_BLOCK, + BONE_MEAL(15, "INK_SACK"), + BOOK, + BOOKSHELF, + BOW, + BOWL, + BRAIN_CORAL, + BRAIN_CORAL_BLOCK, + BRAIN_CORAL_FAN, + BRAIN_CORAL_WALL_FAN, + BREAD, + BREWING_STAND("BREWING_STAND", "BREWING_STAND_ITEM"), + BRICK("CLAY_BRICK"), + BRICKS("BRICKS", "BRICK"), + BRICK_SLAB(4, "STEP"), + BRICK_STAIRS, + BRICK_WALL, + BROWN_BANNER(3, "STANDING_BANNER", "BANNER"), + BROWN_BED(supports(12) ? 12 : 0, "BED_BLOCK", "BED"), + BROWN_CANDLE, + BROWN_CANDLE_CAKE, + BROWN_CARPET(12, "CARPET"), + BROWN_CONCRETE(12, "CONCRETE"), + BROWN_CONCRETE_POWDER(12, "CONCRETE_POWDER"), + BROWN_DYE(3, "INK_SACK", "DYE", "COCOA_BEANS"), + BROWN_GLAZED_TERRACOTTA, + BROWN_MUSHROOM, + BROWN_MUSHROOM_BLOCK("BROWN_MUSHROOM", "HUGE_MUSHROOM_1"), + BROWN_SHULKER_BOX, + BROWN_STAINED_GLASS(12, "STAINED_GLASS"), + BROWN_STAINED_GLASS_PANE(12, "THIN_GLASS", "STAINED_GLASS_PANE"), + BROWN_TERRACOTTA(12, "STAINED_CLAY"), + BROWN_WALL_BANNER(3, "WALL_BANNER"), + BROWN_WOOL(12, "WOOL"), + BUBBLE_COLUMN, + BUBBLE_CORAL, + BUBBLE_CORAL_BLOCK, + BUBBLE_CORAL_FAN, + BUBBLE_CORAL_WALL_FAN, + BUCKET, + BUDDING_AMETHYST, + BUNDLE, + CACTUS, + CAKE("CAKE_BLOCK"), + CALCITE, + CAMPFIRE, + CANDLE, + CANDLE_CAKE, + CARROT("CARROT_ITEM"), + CARROTS("CARROT"), + CARROT_ON_A_STICK("CARROT_STICK"), + CARTOGRAPHY_TABLE, + CARVED_PUMPKIN, + CAT_SPAWN_EGG, + CAULDRON("CAULDRON", "CAULDRON_ITEM"), + /** + * 1.13 tag is not added because it's the same thing as {@link #AIR} + * + * @see #VOID_AIR + */ + CAVE_AIR("AIR"), + CAVE_SPIDER_SPAWN_EGG(59, "MONSTER_EGG"), + CAVE_VINES, + CAVE_VINES_PLANT, + CHAIN, + CHAINMAIL_BOOTS, + CHAINMAIL_CHESTPLATE, + CHAINMAIL_HELMET, + CHAINMAIL_LEGGINGS, + CHAIN_COMMAND_BLOCK("COMMAND", "COMMAND_CHAIN"), + CHARCOAL(1, "COAL"), + CHEST("LOCKED_CHEST"), + CHEST_MINECART("STORAGE_MINECART"), + CHICKEN("RAW_CHICKEN"), + CHICKEN_SPAWN_EGG(93, "MONSTER_EGG"), + CHIPPED_ANVIL(1, "ANVIL"), + CHISELED_DEEPSLATE, + CHISELED_NETHER_BRICKS(1, "NETHER_BRICKS"), + CHISELED_POLISHED_BLACKSTONE("POLISHED_BLACKSTONE"), + CHISELED_QUARTZ_BLOCK(1, "QUARTZ_BLOCK"), + CHISELED_RED_SANDSTONE(1, "RED_SANDSTONE"), + CHISELED_SANDSTONE(1, "SANDSTONE"), + CHISELED_STONE_BRICKS(3, "SMOOTH_BRICK"), + CHORUS_FLOWER, + CHORUS_FRUIT, + CHORUS_PLANT, + CLAY, + CLAY_BALL, + CLOCK("WATCH"), + COAL, + COAL_BLOCK, + COAL_ORE, + COARSE_DIRT(1, "DIRT"), + COBBLED_DEEPSLATE, + COBBLED_DEEPSLATE_SLAB, + COBBLED_DEEPSLATE_STAIRS, + COBBLED_DEEPSLATE_WALL, + COBBLESTONE, + COBBLESTONE_SLAB(3, "STEP"), + COBBLESTONE_STAIRS, + COBBLESTONE_WALL("COBBLE_WALL"), + COBWEB("WEB"), + COCOA, + COCOA_BEANS(3, "INK_SACK"), + COD("RAW_FISH"), + COD_BUCKET, + COD_SPAWN_EGG, + COMMAND_BLOCK("COMMAND"), + COMMAND_BLOCK_MINECART("COMMAND_MINECART"), + /** + * Unlike redstone torch and redstone lamp... neither REDTONE_COMPARATOR_OFF nor REDSTONE_COMPARATOR_ON + * are items. REDSTONE_COMPARATOR is. + * + * @see #REDSTONE_TORCH + * @see #REDSTONE_LAMP + */ + COMPARATOR("REDSTONE_COMPARATOR_OFF", "REDSTONE_COMPARATOR_ON", "REDSTONE_COMPARATOR"), + COMPASS, + COMPOSTER, + CONDUIT, + COOKED_BEEF, + COOKED_CHICKEN, + COOKED_COD("COOKED_FISH"), + COOKED_MUTTON, + COOKED_PORKCHOP("GRILLED_PORK"), + COOKED_RABBIT, + COOKED_SALMON(1, "COOKED_FISH"), + COOKIE, + COPPER_BLOCK, + COPPER_INGOT, + COPPER_ORE, + CORNFLOWER, + COW_SPAWN_EGG(92, "MONSTER_EGG"), + CRACKED_DEEPSLATE_BRICKS, + CRACKED_DEEPSLATE_TILES, + CRACKED_NETHER_BRICKS(2, "NETHER_BRICKS"), + CRACKED_POLISHED_BLACKSTONE_BRICKS("POLISHED_BLACKSTONE_BRICKS"), + CRACKED_STONE_BRICKS(2, "SMOOTH_BRICK"), + CRAFTING_TABLE("WORKBENCH"), + CREEPER_BANNER_PATTERN, + CREEPER_HEAD(4, "SKULL", "SKULL_ITEM"), + CREEPER_SPAWN_EGG(50, "MONSTER_EGG"), + CREEPER_WALL_HEAD(4, "SKULL", "SKULL_ITEM"), + CRIMSON_BUTTON, + CRIMSON_DOOR, + CRIMSON_FENCE, + CRIMSON_FENCE_GATE, + CRIMSON_FUNGUS, + CRIMSON_HYPHAE, + CRIMSON_NYLIUM, + CRIMSON_PLANKS, + CRIMSON_PRESSURE_PLATE, + CRIMSON_ROOTS, + CRIMSON_SIGN("SIGN_POST"), + CRIMSON_SLAB, + CRIMSON_STAIRS, + CRIMSON_STEM, + CRIMSON_TRAPDOOR, + CRIMSON_WALL_SIGN("WALL_SIGN"), + CROSSBOW, + CRYING_OBSIDIAN, + CUT_COPPER, + CUT_COPPER_SLAB, + CUT_COPPER_STAIRS, + CUT_RED_SANDSTONE, + CUT_RED_SANDSTONE_SLAB("STONE_SLAB2"), + CUT_SANDSTONE, + CUT_SANDSTONE_SLAB(1, "STEP"), + CYAN_BANNER(6, "STANDING_BANNER", "BANNER"), + CYAN_BED(supports(12) ? 9 : 0, "BED_BLOCK", "BED"), + CYAN_CANDLE, + CYAN_CANDLE_CAKE, + CYAN_CARPET(9, "CARPET"), + CYAN_CONCRETE(9, "CONCRETE"), + CYAN_CONCRETE_POWDER(9, "CONCRETE_POWDER"), + CYAN_DYE(6, "INK_SACK"), + CYAN_GLAZED_TERRACOTTA, + CYAN_SHULKER_BOX, + CYAN_STAINED_GLASS(9, "STAINED_GLASS"), + CYAN_STAINED_GLASS_PANE(9, "STAINED_GLASS_PANE"), + CYAN_TERRACOTTA(9, "STAINED_CLAY"), + CYAN_WALL_BANNER(6, "WALL_BANNER"), + CYAN_WOOL(9, "WOOL"), + DAMAGED_ANVIL(2, "ANVIL"), + DANDELION("YELLOW_FLOWER"), + DARK_OAK_BOAT("BOAT_DARK_OAK"), + DARK_OAK_BUTTON("WOOD_BUTTON"), + DARK_OAK_CHEST_BOAT, + DARK_OAK_DOOR("DARK_OAK_DOOR", "DARK_OAK_DOOR_ITEM"), + DARK_OAK_FENCE, + DARK_OAK_FENCE_GATE, + DARK_OAK_LEAVES(5, "LEAVES_2"), + DARK_OAK_LOG(5, "LOG_2"), + DARK_OAK_PLANKS(5, "WOOD"), + DARK_OAK_PRESSURE_PLATE("WOOD_PLATE"), + DARK_OAK_SAPLING(5, "SAPLING"), + DARK_OAK_SIGN("SIGN_POST", "SIGN"), + DARK_OAK_SLAB(5, "WOOD_DOUBLE_STEP", "WOOD_STEP", "WOODEN_SLAB"), + DARK_OAK_STAIRS, + DARK_OAK_TRAPDOOR("TRAP_DOOR"), + DARK_OAK_WALL_SIGN("WALL_SIGN"), + DARK_OAK_WOOD(5, "LOG_2"), + DARK_PRISMARINE(2, "PRISMARINE"), + DARK_PRISMARINE_SLAB, + DARK_PRISMARINE_STAIRS, + DAYLIGHT_DETECTOR("DAYLIGHT_DETECTOR_INVERTED"), + DEAD_BRAIN_CORAL, + DEAD_BRAIN_CORAL_BLOCK, + DEAD_BRAIN_CORAL_FAN, + DEAD_BRAIN_CORAL_WALL_FAN, + DEAD_BUBBLE_CORAL, + DEAD_BUBBLE_CORAL_BLOCK, + DEAD_BUBBLE_CORAL_FAN, + DEAD_BUBBLE_CORAL_WALL_FAN, + DEAD_BUSH("LONG_GRASS"), + DEAD_FIRE_CORAL, + DEAD_FIRE_CORAL_BLOCK, + DEAD_FIRE_CORAL_FAN, + DEAD_FIRE_CORAL_WALL_FAN, + DEAD_HORN_CORAL, + DEAD_HORN_CORAL_BLOCK, + DEAD_HORN_CORAL_FAN, + DEAD_HORN_CORAL_WALL_FAN, + DEAD_TUBE_CORAL, + DEAD_TUBE_CORAL_BLOCK, + DEAD_TUBE_CORAL_FAN, + DEAD_TUBE_CORAL_WALL_FAN, + DEBUG_STICK, + DEEPSLATE, + DEEPSLATE_BRICKS, + DEEPSLATE_BRICK_SLAB, + DEEPSLATE_BRICK_STAIRS, + DEEPSLATE_BRICK_WALL, + DEEPSLATE_COAL_ORE, + DEEPSLATE_COPPER_ORE, + DEEPSLATE_DIAMOND_ORE, + DEEPSLATE_EMERALD_ORE, + DEEPSLATE_GOLD_ORE, + DEEPSLATE_IRON_ORE, + DEEPSLATE_LAPIS_ORE, + DEEPSLATE_REDSTONE_ORE, + DEEPSLATE_TILES, + DEEPSLATE_TILE_SLAB, + DEEPSLATE_TILE_STAIRS, + DEEPSLATE_TILE_WALL, + DETECTOR_RAIL, + DIAMOND, + DIAMOND_AXE, + DIAMOND_BLOCK, + DIAMOND_BOOTS, + DIAMOND_CHESTPLATE, + DIAMOND_HELMET, + DIAMOND_HOE, + DIAMOND_HORSE_ARMOR("DIAMOND_BARDING"), + DIAMOND_LEGGINGS, + DIAMOND_ORE, + DIAMOND_PICKAXE, + DIAMOND_SHOVEL("DIAMOND_SPADE"), + DIAMOND_SWORD, + DIORITE(3, "STONE"), + DIORITE_SLAB, + DIORITE_STAIRS, + DIORITE_WALL, + DIRT, + /** + * Changed in 1.17 + */ + DIRT_PATH("GRASS_PATH"), + DISC_FRAGMENT_5, + DISPENSER, + DOLPHIN_SPAWN_EGG, + DONKEY_SPAWN_EGG(32, "MONSTER_EGG"), + DRAGON_BREATH("DRAGONS_BREATH"), + DRAGON_EGG, + DRAGON_HEAD(5, "SKULL", "SKULL_ITEM"), + DRAGON_WALL_HEAD(5, "SKULL", "SKULL_ITEM"), + DRIED_KELP, + DRIED_KELP_BLOCK, + DRIPSTONE_BLOCK, + DROPPER, + DROWNED_SPAWN_EGG, + ECHO_SHARD, + EGG, + ELDER_GUARDIAN_SPAWN_EGG(4, "MONSTER_EGG"), + ELYTRA, + EMERALD, + EMERALD_BLOCK, + EMERALD_ORE, + ENCHANTED_BOOK, + ENCHANTED_GOLDEN_APPLE(1, "GOLDEN_APPLE"), + ENCHANTING_TABLE("ENCHANTMENT_TABLE"), + ENDERMAN_SPAWN_EGG(58, "MONSTER_EGG"), + ENDERMITE_SPAWN_EGG(67, "MONSTER_EGG"), + ENDER_CHEST, + ENDER_EYE("EYE_OF_ENDER"), + ENDER_PEARL, + END_CRYSTAL, + END_GATEWAY, + END_PORTAL("ENDER_PORTAL"), + END_PORTAL_FRAME("ENDER_PORTAL_FRAME"), + END_ROD, + END_STONE("ENDER_STONE"), + END_STONE_BRICKS("END_BRICKS"), + END_STONE_BRICK_SLAB, + END_STONE_BRICK_STAIRS, + END_STONE_BRICK_WALL, + EVOKER_SPAWN_EGG(34, "MONSTER_EGG"), + EXPERIENCE_BOTTLE("EXP_BOTTLE"), + EXPOSED_COPPER, + EXPOSED_CUT_COPPER, + EXPOSED_CUT_COPPER_SLAB, + EXPOSED_CUT_COPPER_STAIRS, + FARMLAND("SOIL"), + FEATHER, + FERMENTED_SPIDER_EYE, + FERN(2, "LONG_GRASS"), + /** + * For some reasons filled map items are really special. + * Their data value starts from 0 and every time a player + * creates a new map that maps data value increases. + * https://github.com/CryptoMorin/XSeries/issues/91 + */ + FILLED_MAP("MAP"), + FIRE, + FIREWORK_ROCKET("FIREWORK"), + FIREWORK_STAR("FIREWORK_CHARGE"), + FIRE_CHARGE("FIREBALL"), + FIRE_CORAL, + FIRE_CORAL_BLOCK, + FIRE_CORAL_FAN, + FIRE_CORAL_WALL_FAN, + FISHING_ROD, + FLETCHING_TABLE, + FLINT, + FLINT_AND_STEEL, + FLOWERING_AZALEA, + FLOWERING_AZALEA_LEAVES, + FLOWER_BANNER_PATTERN, + FLOWER_POT("FLOWER_POT", "FLOWER_POT_ITEM"), + FOX_SPAWN_EGG, + FROGSPAWN, + FROG_SPAWN_EGG, + /** + * This special material cannot be obtained as an item. + */ + FROSTED_ICE, + FURNACE("BURNING_FURNACE"), + FURNACE_MINECART("POWERED_MINECART"), + GHAST_SPAWN_EGG(56, "MONSTER_EGG"), + GHAST_TEAR, + GILDED_BLACKSTONE, + GLASS, + GLASS_BOTTLE, + GLASS_PANE("THIN_GLASS"), + GLISTERING_MELON_SLICE("SPECKLED_MELON"), + GLOBE_BANNER_PATTERN, + GLOWSTONE, + GLOWSTONE_DUST, + GLOW_BERRIES, + GLOW_INK_SAC, + GLOW_ITEM_FRAME, + GLOW_LICHEN, + GLOW_SQUID_SPAWN_EGG, + GOAT_HORN, + GOAT_SPAWN_EGG, + GOLDEN_APPLE, + GOLDEN_AXE("GOLD_AXE"), + GOLDEN_BOOTS("GOLD_BOOTS"), + GOLDEN_CARROT, + GOLDEN_CHESTPLATE("GOLD_CHESTPLATE"), + GOLDEN_HELMET("GOLD_HELMET"), + GOLDEN_HOE("GOLD_HOE"), + GOLDEN_HORSE_ARMOR("GOLD_BARDING"), + GOLDEN_LEGGINGS("GOLD_LEGGINGS"), + GOLDEN_PICKAXE("GOLD_PICKAXE"), + GOLDEN_SHOVEL("GOLD_SPADE"), + GOLDEN_SWORD("GOLD_SWORD"), + GOLD_BLOCK, + GOLD_INGOT, + GOLD_NUGGET, + GOLD_ORE, + GRANITE(1, "STONE"), + GRANITE_SLAB, + GRANITE_STAIRS, + GRANITE_WALL, + GRASS(1, "LONG_GRASS"), + GRASS_BLOCK("GRASS"), + GRAVEL, + GRAY_BANNER(8, "STANDING_BANNER", "BANNER"), + GRAY_BED(supports(12) ? 7 : 0, "BED_BLOCK", "BED"), + GRAY_CANDLE, + GRAY_CANDLE_CAKE, + GRAY_CARPET(7, "CARPET"), + GRAY_CONCRETE(7, "CONCRETE"), + GRAY_CONCRETE_POWDER(7, "CONCRETE_POWDER"), + GRAY_DYE(8, "INK_SACK"), + GRAY_GLAZED_TERRACOTTA, + GRAY_SHULKER_BOX, + GRAY_STAINED_GLASS(7, "STAINED_GLASS"), + GRAY_STAINED_GLASS_PANE(7, "THIN_GLASS", "STAINED_GLASS_PANE"), + GRAY_TERRACOTTA(7, "STAINED_CLAY"), + GRAY_WALL_BANNER(8, "WALL_BANNER"), + GRAY_WOOL(7, "WOOL"), + GREEN_BANNER(2, "STANDING_BANNER", "BANNER"), + GREEN_BED(supports(12) ? 13 : 0, "BED_BLOCK", "BED"), + GREEN_CANDLE, + GREEN_CANDLE_CAKE, + GREEN_CARPET(13, "CARPET"), + GREEN_CONCRETE(13, "CONCRETE"), + GREEN_CONCRETE_POWDER(13, "CONCRETE_POWDER"), + /** + * 1.13 renamed to CACTUS_GREEN + * 1.14 renamed to GREEN_DYE + */ + GREEN_DYE(2, "INK_SACK", "CACTUS_GREEN"), + GREEN_GLAZED_TERRACOTTA, + GREEN_SHULKER_BOX, + GREEN_STAINED_GLASS(13, "STAINED_GLASS"), + GREEN_STAINED_GLASS_PANE(13, "THIN_GLASS", "STAINED_GLASS_PANE"), + GREEN_TERRACOTTA(13, "STAINED_CLAY"), + GREEN_WALL_BANNER(2, "WALL_BANNER"), + GREEN_WOOL(13, "WOOL"), + GRINDSTONE, + GUARDIAN_SPAWN_EGG(68, "MONSTER_EGG"), + GUNPOWDER("SULPHUR"), + HANGING_ROOTS, + HAY_BLOCK, + HEART_OF_THE_SEA, + HEAVY_WEIGHTED_PRESSURE_PLATE("IRON_PLATE"), + HOGLIN_SPAWN_EGG("MONSTER_EGG"), + HONEYCOMB, + HONEYCOMB_BLOCK, + HONEY_BLOCK, + HONEY_BOTTLE, + HOPPER, + HOPPER_MINECART, + HORN_CORAL, + HORN_CORAL_BLOCK, + HORN_CORAL_FAN, + HORN_CORAL_WALL_FAN, + HORSE_SPAWN_EGG(100, "MONSTER_EGG"), + HUSK_SPAWN_EGG(23, "MONSTER_EGG"), + ICE, + INFESTED_CHISELED_STONE_BRICKS(5, "MONSTER_EGGS"), + INFESTED_COBBLESTONE(1, "MONSTER_EGGS"), + INFESTED_CRACKED_STONE_BRICKS(4, "MONSTER_EGGS"), + INFESTED_DEEPSLATE, + INFESTED_MOSSY_STONE_BRICKS(3, "MONSTER_EGGS"), + INFESTED_STONE("MONSTER_EGGS"), + INFESTED_STONE_BRICKS(2, "MONSTER_EGGS"), + /** + * We will only add "INK_SAC" for {@link #BLACK_DYE} since it's + * the only material (linked with this material) that is added + * after 1.13, which means it can use both INK_SACK and INK_SAC. + */ + INK_SAC("INK_SACK"), + IRON_AXE, + IRON_BARS("IRON_FENCE"), + IRON_BLOCK, + IRON_BOOTS, + IRON_CHESTPLATE, + IRON_DOOR("IRON_DOOR_BLOCK"), + IRON_HELMET, + IRON_HOE, + IRON_HORSE_ARMOR("IRON_BARDING"), + IRON_INGOT, + IRON_LEGGINGS, + IRON_NUGGET, + IRON_ORE, + IRON_PICKAXE, + IRON_SHOVEL("IRON_SPADE"), + IRON_SWORD, + IRON_TRAPDOOR, + ITEM_FRAME, + JACK_O_LANTERN, + JIGSAW, + JUKEBOX, + JUNGLE_BOAT("BOAT_JUNGLE"), + JUNGLE_BUTTON("WOOD_BUTTON"), + JUNGLE_CHEST_BOAT, + JUNGLE_DOOR("JUNGLE_DOOR", "JUNGLE_DOOR_ITEM"), + JUNGLE_FENCE, + JUNGLE_FENCE_GATE, + JUNGLE_LEAVES(3, "LEAVES"), + JUNGLE_LOG(3, "LOG"), + JUNGLE_PLANKS(3, "WOOD"), + JUNGLE_PRESSURE_PLATE("WOOD_PLATE"), + JUNGLE_SAPLING(3, "SAPLING"), + JUNGLE_SIGN("SIGN_POST", "SIGN"), + JUNGLE_SLAB(3, "WOOD_DOUBLE_STEP", "WOOD_STEP", "WOODEN_SLAB"), + JUNGLE_STAIRS("JUNGLE_WOOD_STAIRS"), + JUNGLE_TRAPDOOR("TRAP_DOOR"), + JUNGLE_WALL_SIGN("WALL_SIGN"), + JUNGLE_WOOD(3, "LOG"), + KELP, + KELP_PLANT, + KNOWLEDGE_BOOK("BOOK"), + LADDER, + LANTERN, + LAPIS_BLOCK, + LAPIS_LAZULI(4, "INK_SACK"), + LAPIS_ORE, + LARGE_AMETHYST_BUD, + LARGE_FERN(3, "DOUBLE_PLANT"), + LAVA("STATIONARY_LAVA"), + LAVA_BUCKET, + LAVA_CAULDRON, + LEAD("LEASH"), + LEATHER, + LEATHER_BOOTS, + LEATHER_CHESTPLATE, + LEATHER_HELMET, + LEATHER_HORSE_ARMOR("IRON_HORSE_ARMOR"), + LEATHER_LEGGINGS, + LECTERN, + LEVER, + LIGHT, + LIGHTNING_ROD, + LIGHT_BLUE_BANNER(12, "STANDING_BANNER", "BANNER"), + LIGHT_BLUE_BED(supports(12) ? 3 : 0, "BED_BLOCK", "BED"), + LIGHT_BLUE_CANDLE, + LIGHT_BLUE_CANDLE_CAKE, + LIGHT_BLUE_CARPET(3, "CARPET"), + LIGHT_BLUE_CONCRETE(3, "CONCRETE"), + LIGHT_BLUE_CONCRETE_POWDER(3, "CONCRETE_POWDER"), + LIGHT_BLUE_DYE(12, "INK_SACK"), + LIGHT_BLUE_GLAZED_TERRACOTTA, + LIGHT_BLUE_SHULKER_BOX, + LIGHT_BLUE_STAINED_GLASS(3, "STAINED_GLASS"), + LIGHT_BLUE_STAINED_GLASS_PANE(3, "THIN_GLASS", "STAINED_GLASS_PANE"), + LIGHT_BLUE_TERRACOTTA(3, "STAINED_CLAY"), + LIGHT_BLUE_WALL_BANNER(12, "WALL_BANNER", "STANDING_BANNER", "BANNER"), + LIGHT_BLUE_WOOL(3, "WOOL"), + LIGHT_GRAY_BANNER(7, "STANDING_BANNER", "BANNER"), + LIGHT_GRAY_BED(supports(12) ? 8 : 0, "BED_BLOCK", "BED"), + LIGHT_GRAY_CANDLE, + LIGHT_GRAY_CANDLE_CAKE, + LIGHT_GRAY_CARPET(8, "CARPET"), + LIGHT_GRAY_CONCRETE(8, "CONCRETE"), + LIGHT_GRAY_CONCRETE_POWDER(8, "CONCRETE_POWDER"), + LIGHT_GRAY_DYE(7, "INK_SACK"), + /** + * Renamed to SILVER_GLAZED_TERRACOTTA in 1.12 + * Renamed to LIGHT_GRAY_GLAZED_TERRACOTTA in 1.14 + */ + LIGHT_GRAY_GLAZED_TERRACOTTA("SILVER_GLAZED_TERRACOTTA"), + LIGHT_GRAY_SHULKER_BOX("SILVER_SHULKER_BOX"), + LIGHT_GRAY_STAINED_GLASS(8, "STAINED_GLASS"), + LIGHT_GRAY_STAINED_GLASS_PANE(8, "THIN_GLASS", "STAINED_GLASS_PANE"), + LIGHT_GRAY_TERRACOTTA(8, "STAINED_CLAY"), + LIGHT_GRAY_WALL_BANNER(7, "WALL_BANNER"), + LIGHT_GRAY_WOOL(8, "WOOL"), + LIGHT_WEIGHTED_PRESSURE_PLATE("GOLD_PLATE"), + LILAC(1, "DOUBLE_PLANT"), + LILY_OF_THE_VALLEY, + LILY_PAD("WATER_LILY"), + LIME_BANNER(10, "STANDING_BANNER", "BANNER"), + LIME_BED(supports(12) ? 5 : 0, "BED_BLOCK", "BED"), + LIME_CANDLE, + LIME_CANDLE_CAKE, + LIME_CARPET(5, "CARPET"), + LIME_CONCRETE(5, "CONCRETE"), + LIME_CONCRETE_POWDER(5, "CONCRETE_POWDER"), + LIME_DYE(10, "INK_SACK"), + LIME_GLAZED_TERRACOTTA, + LIME_SHULKER_BOX, + LIME_STAINED_GLASS(5, "STAINED_GLASS"), + LIME_STAINED_GLASS_PANE(5, "STAINED_GLASS_PANE"), + LIME_TERRACOTTA(5, "STAINED_CLAY"), + LIME_WALL_BANNER(10, "WALL_BANNER"), + LIME_WOOL(5, "WOOL"), + LINGERING_POTION, + LLAMA_SPAWN_EGG(103, "MONSTER_EGG"), + LODESTONE, + LOOM, + MAGENTA_BANNER(13, "STANDING_BANNER", "BANNER"), + MAGENTA_BED(supports(12) ? 2 : 0, "BED_BLOCK", "BED"), + MAGENTA_CANDLE, + MAGENTA_CANDLE_CAKE, + MAGENTA_CARPET(2, "CARPET"), + MAGENTA_CONCRETE(2, "CONCRETE"), + MAGENTA_CONCRETE_POWDER(2, "CONCRETE_POWDER"), + MAGENTA_DYE(13, "INK_SACK"), + MAGENTA_GLAZED_TERRACOTTA, + MAGENTA_SHULKER_BOX, + MAGENTA_STAINED_GLASS(2, "STAINED_GLASS"), + MAGENTA_STAINED_GLASS_PANE(2, "THIN_GLASS", "STAINED_GLASS_PANE"), + MAGENTA_TERRACOTTA(2, "STAINED_CLAY"), + MAGENTA_WALL_BANNER(13, "WALL_BANNER"), + MAGENTA_WOOL(2, "WOOL"), + MAGMA_BLOCK("MAGMA"), + MAGMA_CREAM, + MAGMA_CUBE_SPAWN_EGG(62, "MONSTER_EGG"), + MANGROVE_BOAT, + MANGROVE_BUTTON, + MANGROVE_CHEST_BOAT, + MANGROVE_DOOR, + MANGROVE_FENCE, + MANGROVE_FENCE_GATE, + MANGROVE_LEAVES, + MANGROVE_LOG, + MANGROVE_PLANKS, + MANGROVE_PRESSURE_PLATE, + MANGROVE_PROPAGULE, + MANGROVE_ROOTS, + MANGROVE_SIGN, + MANGROVE_SLAB, + MANGROVE_STAIRS, + MANGROVE_TRAPDOOR, + MANGROVE_WALL_SIGN, + MANGROVE_WOOD, + /** + * Adding this to the duplicated list will give you a filled map + * for 1.13+ versions and removing it from duplicated list will + * still give you a filled map in -1.12 versions. + * Since higher versions are our priority I'll keep 1.13+ support + * until I can come up with something to fix it. + */ + MAP("EMPTY_MAP"), + MEDIUM_AMETHYST_BUD, + MELON("MELON_BLOCK"), + MELON_SEEDS, + MELON_SLICE("MELON"), + MELON_STEM, + MILK_BUCKET, + MINECART, + MOJANG_BANNER_PATTERN, + MOOSHROOM_SPAWN_EGG(96, "MONSTER_EGG"), + MOSSY_COBBLESTONE, + MOSSY_COBBLESTONE_SLAB(), + MOSSY_COBBLESTONE_STAIRS, + MOSSY_COBBLESTONE_WALL(1, "COBBLE_WALL", "COBBLESTONE_WALL"), + MOSSY_STONE_BRICKS(1, "SMOOTH_BRICK"), + MOSSY_STONE_BRICK_SLAB, + MOSSY_STONE_BRICK_STAIRS, + MOSSY_STONE_BRICK_WALL, + MOSS_BLOCK, + MOSS_CARPET, + MOVING_PISTON("PISTON_MOVING_PIECE"), + MUD, + MUDDY_MANGROVE_ROOTS, + MUD_BRICKS, + MUD_BRICK_SLAB, + MUD_BRICK_STAIRS, + MUD_BRICK_WALL, + MULE_SPAWN_EGG(32, "MONSTER_EGG"), + MUSHROOM_STEM("BROWN_MUSHROOM"), + MUSHROOM_STEW("MUSHROOM_SOUP"), + MUSIC_DISC_11("RECORD_11"), + MUSIC_DISC_13("GOLD_RECORD"), + MUSIC_DISC_5, + MUSIC_DISC_BLOCKS("RECORD_3"), + MUSIC_DISC_CAT("GREEN_RECORD"), + MUSIC_DISC_CHIRP("RECORD_4"), + MUSIC_DISC_FAR("RECORD_5"), + MUSIC_DISC_MALL("RECORD_6"), + MUSIC_DISC_MELLOHI("RECORD_7"), + MUSIC_DISC_OTHERSIDE, + MUSIC_DISC_PIGSTEP, + MUSIC_DISC_STAL("RECORD_8"), + MUSIC_DISC_STRAD("RECORD_9"), + MUSIC_DISC_WAIT("RECORD_12"), + MUSIC_DISC_WARD("RECORD_10"), + MUTTON, + MYCELIUM("MYCEL"), + NAME_TAG, + NAUTILUS_SHELL, + NETHERITE_AXE, + NETHERITE_BLOCK, + NETHERITE_BOOTS, + NETHERITE_CHESTPLATE, + NETHERITE_HELMET, + NETHERITE_HOE, + NETHERITE_INGOT, + NETHERITE_LEGGINGS, + NETHERITE_PICKAXE, + NETHERITE_SCRAP, + NETHERITE_SHOVEL, + NETHERITE_SWORD, + NETHERRACK, + NETHER_BRICK("NETHER_BRICK_ITEM"), + NETHER_BRICKS("NETHER_BRICK"), + NETHER_BRICK_FENCE("NETHER_FENCE"), + NETHER_BRICK_SLAB(6, "STEP"), + NETHER_BRICK_STAIRS, + NETHER_BRICK_WALL, + NETHER_GOLD_ORE, + NETHER_PORTAL("PORTAL"), + NETHER_QUARTZ_ORE("QUARTZ_ORE"), + NETHER_SPROUTS, + NETHER_STAR, + /** + * Just like mentioned in https://minecraft.gamepedia.com/Nether_Wart + * Nether wart is also known as nether stalk in the code. + * NETHER_STALK is the planted state of nether warts. + */ + NETHER_WART("NETHER_WARTS", "NETHER_STALK"), + NETHER_WART_BLOCK, + NOTE_BLOCK, + OAK_BOAT("BOAT"), + OAK_BUTTON("WOOD_BUTTON"), + OAK_CHEST_BOAT, + OAK_DOOR("WOODEN_DOOR", "WOOD_DOOR"), + OAK_FENCE("FENCE"), + OAK_FENCE_GATE("FENCE_GATE"), + OAK_LEAVES("LEAVES"), + OAK_LOG("LOG"), + OAK_PLANKS("WOOD"), + OAK_PRESSURE_PLATE("WOOD_PLATE"), + OAK_SAPLING("SAPLING"), + OAK_SIGN("SIGN_POST", "SIGN"), + OAK_SLAB("WOOD_DOUBLE_STEP", "WOOD_STEP", "WOODEN_SLAB"), + OAK_STAIRS("WOOD_STAIRS"), + OAK_TRAPDOOR("TRAP_DOOR"), + OAK_WALL_SIGN("WALL_SIGN"), + OAK_WOOD("LOG"), + OBSERVER, + OBSIDIAN, + OCELOT_SPAWN_EGG(98, "MONSTER_EGG"), + OCHRE_FROGLIGHT, + ORANGE_BANNER(14, "STANDING_BANNER", "BANNER"), + ORANGE_BED(supports(12) ? 1 : 0, "BED_BLOCK", "BED"), + ORANGE_CANDLE, + ORANGE_CANDLE_CAKE, + ORANGE_CARPET(1, "CARPET"), + ORANGE_CONCRETE(1, "CONCRETE"), + ORANGE_CONCRETE_POWDER(1, "CONCRETE_POWDER"), + ORANGE_DYE(14, "INK_SACK"), + ORANGE_GLAZED_TERRACOTTA, + ORANGE_SHULKER_BOX, + ORANGE_STAINED_GLASS(1, "STAINED_GLASS"), + ORANGE_STAINED_GLASS_PANE(1, "STAINED_GLASS_PANE"), + ORANGE_TERRACOTTA(1, "STAINED_CLAY"), + ORANGE_TULIP(5, "RED_ROSE"), + ORANGE_WALL_BANNER(14, "WALL_BANNER"), + ORANGE_WOOL(1, "WOOL"), + OXEYE_DAISY(8, "RED_ROSE"), + OXIDIZED_COPPER, + OXIDIZED_CUT_COPPER, + OXIDIZED_CUT_COPPER_SLAB, + OXIDIZED_CUT_COPPER_STAIRS, + PACKED_ICE, + PACKED_MUD, + PAINTING, + PANDA_SPAWN_EGG, + PAPER, + PARROT_SPAWN_EGG(105, "MONSTER_EGG"), + PEARLESCENT_FROGLIGHT, + PEONY(5, "DOUBLE_PLANT"), + PETRIFIED_OAK_SLAB("WOOD_STEP"), + PHANTOM_MEMBRANE, + PHANTOM_SPAWN_EGG, + PIGLIN_BANNER_PATTERN, + PIGLIN_BRUTE_SPAWN_EGG, + PIGLIN_SPAWN_EGG(57, "MONSTER_EGG"), + PIG_SPAWN_EGG(90, "MONSTER_EGG"), + PILLAGER_SPAWN_EGG, + PINK_BANNER(9, "STANDING_BANNER", "BANNER"), + PINK_BED(supports(12) ? 6 : 0, "BED_BLOCK", "BED"), + PINK_CANDLE, + PINK_CANDLE_CAKE, + PINK_CARPET(6, "CARPET"), + PINK_CONCRETE(6, "CONCRETE"), + PINK_CONCRETE_POWDER(6, "CONCRETE_POWDER"), + PINK_DYE(9, "INK_SACK"), + PINK_GLAZED_TERRACOTTA, + PINK_SHULKER_BOX, + PINK_STAINED_GLASS(6, "STAINED_GLASS"), + PINK_STAINED_GLASS_PANE(6, "THIN_GLASS", "STAINED_GLASS_PANE"), + PINK_TERRACOTTA(6, "STAINED_CLAY"), + PINK_TULIP(7, "RED_ROSE"), + PINK_WALL_BANNER(9, "WALL_BANNER"), + PINK_WOOL(6, "WOOL"), + PISTON("PISTON_BASE"), + PISTON_HEAD("PISTON_EXTENSION"), + PLAYER_HEAD(3, "SKULL", "SKULL_ITEM"), + PLAYER_WALL_HEAD(3, "SKULL", "SKULL_ITEM"), + PODZOL(2, "DIRT"), + POINTED_DRIPSTONE, + POISONOUS_POTATO, + POLAR_BEAR_SPAWN_EGG(102, "MONSTER_EGG"), + POLISHED_ANDESITE(6, "STONE"), + POLISHED_ANDESITE_SLAB, + POLISHED_ANDESITE_STAIRS, + POLISHED_BASALT, + POLISHED_BLACKSTONE, + POLISHED_BLACKSTONE_BRICKS, + POLISHED_BLACKSTONE_BRICK_SLAB, + POLISHED_BLACKSTONE_BRICK_STAIRS, + POLISHED_BLACKSTONE_BRICK_WALL, + POLISHED_BLACKSTONE_BUTTON, + POLISHED_BLACKSTONE_PRESSURE_PLATE, + POLISHED_BLACKSTONE_SLAB, + POLISHED_BLACKSTONE_STAIRS, + POLISHED_BLACKSTONE_WALL, + POLISHED_DEEPSLATE, + POLISHED_DEEPSLATE_SLAB, + POLISHED_DEEPSLATE_STAIRS, + POLISHED_DEEPSLATE_WALL, + POLISHED_DIORITE(4, "STONE"), + POLISHED_DIORITE_SLAB, + POLISHED_DIORITE_STAIRS, + POLISHED_GRANITE(2, "STONE"), + POLISHED_GRANITE_SLAB, + POLISHED_GRANITE_STAIRS, + POPPED_CHORUS_FRUIT("CHORUS_FRUIT_POPPED"), + POPPY("RED_ROSE"), + PORKCHOP("PORK"), + POTATO("POTATO_ITEM"), + POTATOES("POTATO"), + POTION, + POTTED_ACACIA_SAPLING(4, "FLOWER_POT"), + POTTED_ALLIUM(2, "RED_ROSE", "FLOWER_POT"), + POTTED_AZALEA_BUSH, + POTTED_AZURE_BLUET(3, "RED_ROSE", "FLOWER_POT"), + POTTED_BAMBOO, + POTTED_BIRCH_SAPLING(2, "FLOWER_POT"), + POTTED_BLUE_ORCHID(1, "RED_ROSE", "FLOWER_POT"), + POTTED_BROWN_MUSHROOM("FLOWER_POT"), + POTTED_CACTUS("FLOWER_POT"), + POTTED_CORNFLOWER, + POTTED_CRIMSON_FUNGUS, + POTTED_CRIMSON_ROOTS, + POTTED_DANDELION("YELLOW_FLOWER", "FLOWER_POT"), + POTTED_DARK_OAK_SAPLING(5, "FLOWER_POT"), + POTTED_DEAD_BUSH("FLOWER_POT"), + POTTED_FERN(2, "LONG_GRASS", "FLOWER_POT"), + POTTED_FLOWERING_AZALEA_BUSH, + POTTED_JUNGLE_SAPLING(3, "FLOWER_POT"), + POTTED_LILY_OF_THE_VALLEY, + POTTED_MANGROVE_PROPAGULE, + POTTED_OAK_SAPLING("FLOWER_POT"), + POTTED_ORANGE_TULIP(5, "RED_ROSE", "FLOWER_POT"), + POTTED_OXEYE_DAISY(8, "RED_ROSE", "FLOWER_POT"), + POTTED_PINK_TULIP(7, "RED_ROSE", "FLOWER_POT"), + POTTED_POPPY("RED_ROSE", "FLOWER_POT"), + POTTED_RED_MUSHROOM("FLOWER_POT"), + POTTED_RED_TULIP(4, "RED_ROSE", "FLOWER_POT"), + POTTED_SPRUCE_SAPLING(1, "FLOWER_POT"), + POTTED_WARPED_FUNGUS, + POTTED_WARPED_ROOTS, + POTTED_WHITE_TULIP(6, "RED_ROSE", "FLOWER_POT"), + POTTED_WITHER_ROSE, + POWDER_SNOW, + POWDER_SNOW_BUCKET, + POWDER_SNOW_CAULDRON, + POWERED_RAIL, + PRISMARINE, + PRISMARINE_BRICKS(1, "PRISMARINE"), + PRISMARINE_BRICK_SLAB, + PRISMARINE_BRICK_STAIRS, + PRISMARINE_CRYSTALS, + PRISMARINE_SHARD, + PRISMARINE_SLAB, + PRISMARINE_STAIRS, + PRISMARINE_WALL, + PUFFERFISH(3, "RAW_FISH"), + PUFFERFISH_BUCKET, + PUFFERFISH_SPAWN_EGG, + PUMPKIN, + PUMPKIN_PIE, + PUMPKIN_SEEDS, + PUMPKIN_STEM, + PURPLE_BANNER(5, "STANDING_BANNER", "BANNER"), + PURPLE_BED(supports(12) ? 10 : 0, "BED_BLOCK", "BED"), + PURPLE_CANDLE, + PURPLE_CANDLE_CAKE, + PURPLE_CARPET(10, "CARPET"), + PURPLE_CONCRETE(10, "CONCRETE"), + PURPLE_CONCRETE_POWDER(10, "CONCRETE_POWDER"), + PURPLE_DYE(5, "INK_SACK"), + PURPLE_GLAZED_TERRACOTTA, + PURPLE_SHULKER_BOX, + PURPLE_STAINED_GLASS(10, "STAINED_GLASS"), + PURPLE_STAINED_GLASS_PANE(10, "THIN_GLASS", "STAINED_GLASS_PANE"), + PURPLE_TERRACOTTA(10, "STAINED_CLAY"), + PURPLE_WALL_BANNER(5, "WALL_BANNER"), + PURPLE_WOOL(10, "WOOL"), + PURPUR_BLOCK, + PURPUR_PILLAR, + PURPUR_SLAB("PURPUR_DOUBLE_SLAB"), + PURPUR_STAIRS, + QUARTZ, + QUARTZ_BLOCK, + QUARTZ_BRICKS, + QUARTZ_PILLAR(2, "QUARTZ_BLOCK"), + QUARTZ_SLAB(7, "STEP"), + QUARTZ_STAIRS, + RABBIT, + RABBIT_FOOT, + RABBIT_HIDE, + RABBIT_SPAWN_EGG(101, "MONSTER_EGG"), + RABBIT_STEW, + RAIL("RAILS"), + RAVAGER_SPAWN_EGG, + RAW_COPPER, + RAW_COPPER_BLOCK, + RAW_GOLD, + RAW_GOLD_BLOCK, + RAW_IRON, + RAW_IRON_BLOCK, + RECOVERY_COMPASS, + REDSTONE, + REDSTONE_BLOCK, + /** + * Unlike redstone torch, REDSTONE_LAMP_ON isn't an item. + * The name is just here on the list for matching. + * + * @see #REDSTONE_TORCH + */ + REDSTONE_LAMP("REDSTONE_LAMP_ON", "REDSTONE_LAMP_OFF"), + REDSTONE_ORE("GLOWING_REDSTONE_ORE"), + /** + * REDSTONE_TORCH_OFF isn't an item, but a block. + * But REDSTONE_TORCH_ON is the item. + * The name is just here on the list for matching. + */ + REDSTONE_TORCH("REDSTONE_TORCH_OFF", "REDSTONE_TORCH_ON"), + REDSTONE_WALL_TORCH, + REDSTONE_WIRE, + RED_BANNER(1, "STANDING_BANNER", "BANNER"), + /** + * Data value 14 or 0 + */ + RED_BED(supports(12) ? 14 : 0, "BED_BLOCK", "BED"), + RED_CANDLE, + RED_CANDLE_CAKE, + RED_CARPET(14, "CARPET"), + RED_CONCRETE(14, "CONCRETE"), + RED_CONCRETE_POWDER(14, "CONCRETE_POWDER"), + RED_DYE(1, "INK_SACK", "ROSE_RED"), + RED_GLAZED_TERRACOTTA, + RED_MUSHROOM, + RED_MUSHROOM_BLOCK("RED_MUSHROOM", "HUGE_MUSHROOM_2"), + RED_NETHER_BRICKS("RED_NETHER_BRICK"), + RED_NETHER_BRICK_SLAB, + RED_NETHER_BRICK_STAIRS, + RED_NETHER_BRICK_WALL, + RED_SAND(1, "SAND"), + RED_SANDSTONE, + RED_SANDSTONE_SLAB("DOUBLE_STONE_SLAB2", "STONE_SLAB2"), + RED_SANDSTONE_STAIRS, + RED_SANDSTONE_WALL, + RED_SHULKER_BOX, + RED_STAINED_GLASS(14, "STAINED_GLASS"), + RED_STAINED_GLASS_PANE(14, "THIN_GLASS", "STAINED_GLASS_PANE"), + RED_TERRACOTTA(14, "STAINED_CLAY"), + RED_TULIP(4, "RED_ROSE"), + RED_WALL_BANNER(1, "WALL_BANNER"), + RED_WOOL(14, "WOOL"), + REINFORCED_DEEPSLATE, + REPEATER("DIODE_BLOCK_ON", "DIODE_BLOCK_OFF", "DIODE"), + REPEATING_COMMAND_BLOCK("COMMAND", "COMMAND_REPEATING"), + RESPAWN_ANCHOR, + ROOTED_DIRT, + ROSE_BUSH(4, "DOUBLE_PLANT"), + ROTTEN_FLESH, + SADDLE, + SALMON(1, "RAW_FISH"), + SALMON_BUCKET, + SALMON_SPAWN_EGG, + SAND, + SANDSTONE, + SANDSTONE_SLAB(1, "DOUBLE_STEP", "STEP", "STONE_SLAB"), + SANDSTONE_STAIRS, + SANDSTONE_WALL, + SCAFFOLDING, + SCULK, + SCULK_CATALYST, + SCULK_SENSOR, + SCULK_SHRIEKER, + SCULK_VEIN, + SCUTE, + SEAGRASS, + SEA_LANTERN, + SEA_PICKLE, + SHEARS, + SHEEP_SPAWN_EGG(91, "MONSTER_EGG"), + SHIELD, + SHROOMLIGHT, + SHULKER_BOX("PURPLE_SHULKER_BOX"), + SHULKER_SHELL, + SHULKER_SPAWN_EGG(69, "MONSTER_EGG"), + SILVERFISH_SPAWN_EGG(60, "MONSTER_EGG"), + SKELETON_HORSE_SPAWN_EGG(28, "MONSTER_EGG"), + SKELETON_SKULL("SKULL", "SKULL_ITEM"), + SKELETON_SPAWN_EGG(51, "MONSTER_EGG"), + SKELETON_WALL_SKULL("SKULL", "SKULL_ITEM"), + SKULL_BANNER_PATTERN, + SLIME_BALL, + SLIME_BLOCK, + SLIME_SPAWN_EGG(55, "MONSTER_EGG"), + SMALL_AMETHYST_BUD, + SMALL_DRIPLEAF, + SMITHING_TABLE, + SMOKER, + SMOOTH_BASALT, + SMOOTH_QUARTZ, + SMOOTH_QUARTZ_SLAB, + SMOOTH_QUARTZ_STAIRS, + SMOOTH_RED_SANDSTONE(2, "RED_SANDSTONE"), + SMOOTH_RED_SANDSTONE_SLAB("STONE_SLAB2"), + SMOOTH_RED_SANDSTONE_STAIRS, + SMOOTH_SANDSTONE(2, "SANDSTONE"), + SMOOTH_SANDSTONE_SLAB, + SMOOTH_SANDSTONE_STAIRS, + SMOOTH_STONE, + SMOOTH_STONE_SLAB, + SNOW, + SNOWBALL("SNOW_BALL"), + SNOW_BLOCK, + SOUL_CAMPFIRE, + SOUL_FIRE, + SOUL_LANTERN, + SOUL_SAND, + SOUL_SOIL, + SOUL_TORCH, + SOUL_WALL_TORCH, + SPAWNER("MOB_SPAWNER"), + SPECTRAL_ARROW, + SPIDER_EYE, + SPIDER_SPAWN_EGG(52, "MONSTER_EGG"), + SPLASH_POTION, + SPONGE, + SPORE_BLOSSOM, + SPRUCE_BOAT("BOAT_SPRUCE"), + SPRUCE_BUTTON("WOOD_BUTTON"), + SPRUCE_CHEST_BOAT, + SPRUCE_DOOR("SPRUCE_DOOR", "SPRUCE_DOOR_ITEM"), + SPRUCE_FENCE, + SPRUCE_FENCE_GATE, + SPRUCE_LEAVES(1, "LEAVES"), + SPRUCE_LOG(1, "LOG"), + SPRUCE_PLANKS(1, "WOOD"), + SPRUCE_PRESSURE_PLATE("WOOD_PLATE"), + SPRUCE_SAPLING(1, "SAPLING"), + SPRUCE_SIGN("SIGN_POST", "SIGN"), + SPRUCE_SLAB(1, "WOOD_DOUBLE_STEP", "WOOD_STEP", "WOODEN_SLAB"), + SPRUCE_STAIRS("SPRUCE_WOOD_STAIRS"), + SPRUCE_TRAPDOOR("TRAP_DOOR"), + SPRUCE_WALL_SIGN("WALL_SIGN"), + SPRUCE_WOOD(1, "LOG"), + SPYGLASS, + SQUID_SPAWN_EGG(94, "MONSTER_EGG"), + STICK, + STICKY_PISTON("PISTON_BASE", "PISTON_STICKY_BASE"), + STONE, + STONECUTTER, + STONE_AXE, + STONE_BRICKS("SMOOTH_BRICK"), + STONE_BRICK_SLAB(5, "DOUBLE_STEP", "STEP", "STONE_SLAB"), + STONE_BRICK_STAIRS("SMOOTH_STAIRS"), + STONE_BRICK_WALL, + STONE_BUTTON, + STONE_HOE, + STONE_PICKAXE, + STONE_PRESSURE_PLATE("STONE_PLATE"), + STONE_SHOVEL("STONE_SPADE"), + STONE_SLAB("DOUBLE_STEP", "STEP"), + STONE_STAIRS, + STONE_SWORD, + STRAY_SPAWN_EGG(6, "MONSTER_EGG"), + STRIDER_SPAWN_EGG, + STRING, + STRIPPED_ACACIA_LOG, + STRIPPED_ACACIA_WOOD, + STRIPPED_BIRCH_LOG, + STRIPPED_BIRCH_WOOD, + STRIPPED_CRIMSON_HYPHAE, + STRIPPED_CRIMSON_STEM, + STRIPPED_DARK_OAK_LOG, + STRIPPED_DARK_OAK_WOOD, + STRIPPED_JUNGLE_LOG, + STRIPPED_JUNGLE_WOOD, + STRIPPED_MANGROVE_LOG, + STRIPPED_MANGROVE_WOOD, + STRIPPED_OAK_LOG, + STRIPPED_OAK_WOOD, + STRIPPED_SPRUCE_LOG, + STRIPPED_SPRUCE_WOOD, + STRIPPED_WARPED_HYPHAE, + STRIPPED_WARPED_STEM, + STRUCTURE_BLOCK, + /** + * Originally developers used barrier blocks for its purpose. + * So technically this isn't really considered as a suggested material. + */ + STRUCTURE_VOID(10, "BARRIER"), + SUGAR, + /** + * Sugar Cane is a known material in pre-1.13 + */ + SUGAR_CANE("SUGAR_CANE_BLOCK"), + SUNFLOWER("DOUBLE_PLANT"), + SUSPICIOUS_STEW, + SWEET_BERRIES, + SWEET_BERRY_BUSH, + TADPOLE_BUCKET, + TADPOLE_SPAWN_EGG, + TALL_GRASS(2, "DOUBLE_PLANT"), + TALL_SEAGRASS, + TARGET, + TERRACOTTA("HARD_CLAY"), + TINTED_GLASS, + TIPPED_ARROW, + TNT, + TNT_MINECART("EXPLOSIVE_MINECART"), + TORCH, + TOTEM_OF_UNDYING("TOTEM"), + TRADER_LLAMA_SPAWN_EGG, + TRAPPED_CHEST, + TRIDENT, + TRIPWIRE, + TRIPWIRE_HOOK, + TROPICAL_FISH(2, "RAW_FISH"), + TROPICAL_FISH_BUCKET("BUCKET", "WATER_BUCKET"), + TROPICAL_FISH_SPAWN_EGG("MONSTER_EGG"), + TUBE_CORAL, + TUBE_CORAL_BLOCK, + TUBE_CORAL_FAN, + TUBE_CORAL_WALL_FAN, + TUFF, + TURTLE_EGG, + TURTLE_HELMET, + TURTLE_SPAWN_EGG, + TWISTING_VINES, + TWISTING_VINES_PLANT, + VERDANT_FROGLIGHT, + VEX_SPAWN_EGG(35, "MONSTER_EGG"), + VILLAGER_SPAWN_EGG(120, "MONSTER_EGG"), + VINDICATOR_SPAWN_EGG(36, "MONSTER_EGG"), + VINE, + /** + * 1.13 tag is not added because it's the same thing as {@link #AIR} + * + * @see #CAVE_AIR + */ + VOID_AIR("AIR"), + WALL_TORCH("TORCH"), + WANDERING_TRADER_SPAWN_EGG, + WARDEN_SPAWN_EGG, + WARPED_BUTTON, + WARPED_DOOR, + WARPED_FENCE, + WARPED_FENCE_GATE, + WARPED_FUNGUS, + WARPED_FUNGUS_ON_A_STICK, + WARPED_HYPHAE, + WARPED_NYLIUM, + WARPED_PLANKS, + WARPED_PRESSURE_PLATE, + WARPED_ROOTS, + WARPED_SIGN("SIGN_POST"), + WARPED_SLAB, + WARPED_STAIRS, + WARPED_STEM, + WARPED_TRAPDOOR, + WARPED_WALL_SIGN("WALL_SIGN"), + WARPED_WART_BLOCK, + /** + * This is used for blocks only. + * In 1.13- WATER will turn into STATIONARY_WATER after it finished spreading. + * After 1.13+ this uses + * https://hub.spigotmc.org/javadocs/spigot/org/bukkit/block/data/Levelled.html water flowing system. + */ + WATER("STATIONARY_WATER"), + WATER_BUCKET, + WATER_CAULDRON, + WAXED_COPPER_BLOCK, + WAXED_CUT_COPPER, + WAXED_CUT_COPPER_SLAB, + WAXED_CUT_COPPER_STAIRS, + WAXED_EXPOSED_COPPER, + WAXED_EXPOSED_CUT_COPPER, + WAXED_EXPOSED_CUT_COPPER_SLAB, + WAXED_EXPOSED_CUT_COPPER_STAIRS, + WAXED_OXIDIZED_COPPER, + WAXED_OXIDIZED_CUT_COPPER, + WAXED_OXIDIZED_CUT_COPPER_SLAB, + WAXED_OXIDIZED_CUT_COPPER_STAIRS, + WAXED_WEATHERED_COPPER, + WAXED_WEATHERED_CUT_COPPER, + WAXED_WEATHERED_CUT_COPPER_SLAB, + WAXED_WEATHERED_CUT_COPPER_STAIRS, + WEATHERED_COPPER, + WEATHERED_CUT_COPPER, + WEATHERED_CUT_COPPER_SLAB, + WEATHERED_CUT_COPPER_STAIRS, + WEEPING_VINES, + WEEPING_VINES_PLANT, + WET_SPONGE(1, "SPONGE"), + /** + * Wheat is a known material in pre-1.13 + */ + WHEAT("CROPS"), + WHEAT_SEEDS("SEEDS"), + WHITE_BANNER(15, "STANDING_BANNER", "BANNER"), + WHITE_BED("BED_BLOCK", "BED"), + WHITE_CANDLE, + WHITE_CANDLE_CAKE, + WHITE_CARPET("CARPET"), + WHITE_CONCRETE("CONCRETE"), + WHITE_CONCRETE_POWDER("CONCRETE_POWDER"), + WHITE_DYE(15, "INK_SACK", "BONE_MEAL"), + WHITE_GLAZED_TERRACOTTA, + WHITE_SHULKER_BOX, + WHITE_STAINED_GLASS("STAINED_GLASS"), + WHITE_STAINED_GLASS_PANE("THIN_GLASS", "STAINED_GLASS_PANE"), + WHITE_TERRACOTTA("STAINED_CLAY"), + WHITE_TULIP(6, "RED_ROSE"), + WHITE_WALL_BANNER(15, "WALL_BANNER"), + WHITE_WOOL("WOOL"), + WITCH_SPAWN_EGG(66, "MONSTER_EGG"), + WITHER_ROSE, + WITHER_SKELETON_SKULL(1, "SKULL", "SKULL_ITEM"), + WITHER_SKELETON_SPAWN_EGG(5, "MONSTER_EGG"), + WITHER_SKELETON_WALL_SKULL(1, "SKULL", "SKULL_ITEM"), + WOLF_SPAWN_EGG(95, "MONSTER_EGG"), + WOODEN_AXE("WOOD_AXE"), + WOODEN_HOE("WOOD_HOE"), + WOODEN_PICKAXE("WOOD_PICKAXE"), + WOODEN_SHOVEL("WOOD_SPADE"), + WOODEN_SWORD("WOOD_SWORD"), + WRITABLE_BOOK("BOOK_AND_QUILL"), + WRITTEN_BOOK, + YELLOW_BANNER(11, "STANDING_BANNER", "BANNER"), + YELLOW_BED(supports(12) ? 4 : 0, "BED_BLOCK", "BED"), + YELLOW_CANDLE, + YELLOW_CANDLE_CAKE, + YELLOW_CARPET(4, "CARPET"), + YELLOW_CONCRETE(4, "CONCRETE"), + YELLOW_CONCRETE_POWDER(4, "CONCRETE_POWDER"), + YELLOW_DYE(11, "INK_SACK", "DANDELION_YELLOW"), + YELLOW_GLAZED_TERRACOTTA, + YELLOW_SHULKER_BOX, + YELLOW_STAINED_GLASS(4, "STAINED_GLASS"), + YELLOW_STAINED_GLASS_PANE(4, "THIN_GLASS", "STAINED_GLASS_PANE"), + YELLOW_TERRACOTTA(4, "STAINED_CLAY"), + YELLOW_WALL_BANNER(11, "WALL_BANNER"), + YELLOW_WOOL(4, "WOOL"), + ZOGLIN_SPAWN_EGG, + ZOMBIE_HEAD(2, "SKULL", "SKULL_ITEM"), + ZOMBIE_HORSE_SPAWN_EGG(29, "MONSTER_EGG"), + ZOMBIE_SPAWN_EGG(54, "MONSTER_EGG"), + ZOMBIE_VILLAGER_SPAWN_EGG(27, "MONSTER_EGG"), + ZOMBIE_WALL_HEAD(2, "SKULL", "SKULL_ITEM"), + ZOMBIFIED_PIGLIN_SPAWN_EGG(57, "MONSTER_EGG", "ZOMBIE_PIGMAN_SPAWN_EGG"); + + + /** + * Cached array of {@link XMaterial#values()} to avoid allocating memory for + * calling the method every time. + * + * @since 2.0.0 + */ + public static final XMaterial[] VALUES = values(); + + /** + * We don't want to use {@link Enums#getIfPresent(Class, String)} to avoid a few checks. + * + * @since 5.1.0 + */ + private static final Map NAMES = new HashMap<>(); + + /** + * Guava (Google Core Libraries for Java)'s cache for performance and timed caches. + * For strings that match a certain XMaterial. Mostly cached for configs. + * + * @since 1.0.0 + */ + private static final Cache NAME_CACHE = CacheBuilder.newBuilder() + .expireAfterAccess(1, TimeUnit.HOURS) + .build(); + /** + * This is used for {@link #isOneOf(Collection)} + * + * @since 3.4.0 + */ + private static final Cache CACHED_REGEX = CacheBuilder.newBuilder() + .expireAfterAccess(3, TimeUnit.HOURS) + .build(); + /** + * The maximum data value in the pre-flattening update which belongs to {@link #VILLAGER_SPAWN_EGG}
+ * https://minecraftitemids.com/types/spawn-egg + * + * @see #matchXMaterialWithData(String) + * @since 8.0.0 + */ + private static final byte MAX_DATA_VALUE = 120; + /** + * Used to tell the system that the passed object's (name or material) data value + * is not provided or is invalid. + * + * @since 8.0.0 + */ + private static final byte UNKNOWN_DATA_VALUE = -1; + /** + * The maximum material ID before the pre-flattening update which belongs to {@link #MUSIC_DISC_WAIT} + * + * @since 8.1.0 + */ + private static final short MAX_ID = 2267; + /** + * XMaterial Paradox (Duplication Check) + *

+ * A set of duplicated material names in 1.13 and 1.12 that will conflict with the legacy names. + * Values are the new material names. This map also contains illegal elements. Check the static initializer for more info. + *

+ * Duplications are not useful at all in versions above the flattening update {@link Data#ISFLAT} + * This set is only used for matching materials, for parsing refer to {@link #isDuplicated()} + * + * @since 3.0.0 + */ + private static final Set DUPLICATED; + + static { + for (XMaterial material : VALUES) NAMES.put(material.name(), material); + } + + static { + if (Data.ISFLAT) { + // It's not needed at all if it's the newer version. We can save some memory. + DUPLICATED = null; + } else { + // MELON_SLICE, CARROTS, POTATOES, BEETROOTS, GRASS_BLOCK, BRICKS, NETHER_BRICKS, BROWN_MUSHROOM + // Using the constructor to add elements will decide to allocate more size which we don't need. + DUPLICATED = new HashSet<>(4); + DUPLICATED.add(GRASS.name()); + DUPLICATED.add(MELON.name()); + DUPLICATED.add(BRICK.name()); + DUPLICATED.add(NETHER_BRICK.name()); + } + } + + /** + * The data value of this material https://minecraft.gamepedia.com/Java_Edition_data_values/Pre-flattening + * It's never a negative number. + * + * @see #getData() + */ + private final byte data; + /** + * A list of material names that was being used for older verions. + * + * @see #getLegacy() + */ + @Nonnull + private final String[] legacy; + /** + * The cached Bukkit parsed material. + * + * @see #parseMaterial() + * @since 9.0.0 + */ + @Nullable + private final Material material; + + XMaterial(int data, @Nonnull String... legacy) { + this.data = (byte) data; + this.legacy = legacy; + + Material mat = null; + if ((!Data.ISFLAT && this.isDuplicated()) || (mat = Material.getMaterial(this.name())) == null) { + for (int i = legacy.length - 1; i >= 0; i--) { + mat = Material.getMaterial(legacy[i]); + if (mat != null) break; + } + } + this.material = mat; + } + + XMaterial(String... legacy) {this(0, legacy);} + + /** + * Checks if the version is 1.13 Aquatic Update or higher. + * An invocation of this method yields the cached result from the expression: + *

+ *

+ * {@link #supports(int) 13}} + *
+ * + * @return true if 1.13 or higher. + * @see #getVersion() + * @see #supports(int) + * @since 1.0.0 + * @deprecated Use {@code XMaterial.supports(13)} instead. This method name can be confusing. + */ + @Deprecated + public static boolean isNewVersion() { + return Data.ISFLAT; + } + + /** + * This is just an extra method that can be used for many cases. + * It can be used in {@link org.bukkit.event.player.PlayerInteractEvent} + * or when accessing {@link org.bukkit.entity.Player#getMainHand()}, + * or other compatibility related methods. + *

+ * An invocation of this method yields exactly the same result as the expression: + *

+ *

+ * !{@link #supports(int)} 9 + *
+ * + * @since 2.0.0 + * @deprecated Use {@code !XMaterial.supports(9)} instead. + */ + @Deprecated + public static boolean isOneEight() { + return !supports(9); + } + + /** + * Gets the XMaterial with this name similar to {@link #valueOf(String)} + * without throwing an exception. + * + * @param name the name of the material. + * + * @return an optional that can be empty. + * @since 5.1.0 + */ + @Nonnull + private static Optional getIfPresent(@Nonnull String name) { + return Optional.ofNullable(NAMES.get(name)); + } + + /** + * The current version of the server. + * + * @return the current server version minor number. + * @see #supports(int) + * @since 2.0.0 + */ + public static int getVersion() { + return Data.VERSION; + } + + /** + * When using 1.13+, this helps to find the old material name + * with its data value using a cached search for optimization. + * + * @see #matchDefinedXMaterial(String, byte) + * @since 1.0.0 + */ + @Nullable + private static XMaterial requestOldXMaterial(@Nonnull String name, byte data) { + String holder = name + data; + XMaterial cache = NAME_CACHE.getIfPresent(holder); + if (cache != null) return cache; + + for (XMaterial material : VALUES) { + // Not using material.name().equals(name) check is intended. + if ((data == UNKNOWN_DATA_VALUE || data == material.data) && material.anyMatchLegacy(name)) { + NAME_CACHE.put(holder, material); + return material; + } + } + + return null; + } + + /** + * Parses the given material name as an XMaterial with a given data + * value in the string if attached. Check {@link #matchXMaterialWithData(String)} for more info. + * + * @see #matchXMaterialWithData(String) + * @see #matchDefinedXMaterial(String, byte) + * @since 2.0.0 + */ + @Nonnull + public static Optional matchXMaterial(@Nonnull String name) { + if (name == null || name.isEmpty()) throw new IllegalArgumentException("Cannot match a material with null or empty material name"); + Optional oldMatch = matchXMaterialWithData(name); + return oldMatch.isPresent() ? oldMatch : matchDefinedXMaterial(format(name), UNKNOWN_DATA_VALUE); + } + + /** + * Parses material name and data value from the specified string. + * The separator for the material name and its data value is {@code :} + * Spaces are allowed. Mostly used when getting materials from config for old school minecrafters. + *

+ * Examples + *

+     *     {@code INK_SACK:1 -> RED_DYE}
+     *     {@code WOOL: 14  -> RED_WOOL}
+     * 
+ * + * @param name the material string that consists of the material name, data and separator character. + * + * @return the parsed XMaterial. + * @see #matchXMaterial(String) + * @since 3.0.0 + */ + @Nonnull + private static Optional matchXMaterialWithData(@Nonnull String name) { + int index = name.indexOf(':'); + if (index != -1) { + String mat = format(name.substring(0, index)); + try { + // We don't use Byte.parseByte because we have our own range check. + byte data = (byte) Integer.parseInt(name.substring(index + 1).replace(" ", "")); + return data >= 0 && data < MAX_DATA_VALUE ? matchDefinedXMaterial(mat, data) : matchDefinedXMaterial(mat, UNKNOWN_DATA_VALUE); + } catch (NumberFormatException ignored) { + return matchDefinedXMaterial(mat, UNKNOWN_DATA_VALUE); + } + } + + return Optional.empty(); + } + + /** + * Parses the given material as an XMaterial. + * + * @throws IllegalArgumentException may be thrown as an unexpected exception. + * @see #matchDefinedXMaterial(String, byte) + * @see #matchXMaterial(ItemStack) + * @since 2.0.0 + */ + @Nonnull + public static XMaterial matchXMaterial(@Nonnull Material material) { + Objects.requireNonNull(material, "Cannot match null material"); + return matchDefinedXMaterial(material.name(), UNKNOWN_DATA_VALUE) + .orElseThrow(() -> new IllegalArgumentException("Unsupported material with no data value: " + material.name())); + } + + /** + * Parses the given item as an XMaterial using its material and data value (durability) + * if not a damageable item {@link ItemStack#getDurability()}. + * + * @param item the ItemStack to match. + * + * @return an XMaterial if matched any. + * @throws IllegalArgumentException may be thrown as an unexpected exception. + * @see #matchXMaterial(Material) + * @since 2.0.0 + */ + @Nonnull + @SuppressWarnings("deprecation") + public static XMaterial matchXMaterial(@Nonnull ItemStack item) { + Objects.requireNonNull(item, "Cannot match null ItemStack"); + String material = item.getType().name(); + byte data = (byte) (Data.ISFLAT || item.getType().getMaxDurability() > 0 ? 0 : item.getDurability()); + + // Versions 1.9-1.12 didn't really use the items data value. + if (supports(9) && !supports(13) && item.hasItemMeta() && material.equals("MONSTER_EGG")) { + ItemMeta meta = item.getItemMeta(); + if (meta instanceof SpawnEggMeta) { + SpawnEggMeta egg = (SpawnEggMeta) meta; + material = egg.getSpawnedType().name() + "_SPAWN_EGG"; + } + } + + // Potions used the items data value to store + // information about the type of potion in 1.8 + if (!supports(9) && material.endsWith("ION")) { + // There's also 16000+ data value technique, but this is more reliable. + return Potion.fromItemStack(item).isSplash() ? SPLASH_POTION : POTION; + } + + // Refer to the enum for info. + // Currently this is the only material with a non-zero data value + // that has been renamed after the flattening update. + // If this happens to more materials in the future, + // I might have to change then system. + if (Data.ISFLAT && !supports(14) && material.equals("CACTUS_GREEN")) return GREEN_DYE; + + // Check FILLED_MAP enum for more info. + // if (!Data.ISFLAT && item.hasItemMeta() && item.getItemMeta() instanceof org.bukkit.inventory.meta.MapMeta) return FILLED_MAP; + + // No orElseThrow, I don't want to deal with Java's final variable bullshit. + Optional result = matchDefinedXMaterial(material, data); + if (result.isPresent()) return result.get(); + throw new IllegalArgumentException("Unsupported material from item: " + material + " (" + data + ')'); + } + + /** + * The main method that parses the given material name and data value as an XMaterial. + * All the values passed to this method will not be null or empty and are formatted correctly. + * + * @param name the formatted name of the material. + * @param data the data value of the material. Is always 0 or {@link #UNKNOWN_DATA_VALUE} when {@link Data#ISFLAT} + * + * @return an XMaterial (with the same data value if specified) + * @see #matchXMaterial(Material) + * @see #matchXMaterial(int, byte) + * @see #matchXMaterial(ItemStack) + * @since 3.0.0 + */ + @Nonnull + protected static Optional matchDefinedXMaterial(@Nonnull String name, byte data) { + // if (!Boolean.valueOf(Boolean.getBoolean(Boolean.TRUE.toString())).equals(Boolean.FALSE.booleanValue())) return null; + Boolean duplicated = null; + boolean isAMap = name.equalsIgnoreCase("MAP"); + + // Do basic number and boolean checks before accessing more complex enum stuff. + if (Data.ISFLAT || (!isAMap && data <= 0 && !(duplicated = isDuplicated(name)))) { + Optional xMaterial = getIfPresent(name); + if (xMaterial.isPresent()) return xMaterial; + } + // Usually flat versions wouldn't pass this point, but some special materials do. + + XMaterial oldXMaterial = requestOldXMaterial(name, data); + if (oldXMaterial == null) { + // Special case. Refer to FILLED_MAP for more info. + return (data >= 0 && isAMap) ? Optional.of(FILLED_MAP) : Optional.empty(); + } + + if (!Data.ISFLAT && oldXMaterial.isPlural() && (duplicated == null ? isDuplicated(name) : duplicated)) return getIfPresent(name); + return Optional.of(oldXMaterial); + } + + /** + * XMaterial Paradox (Duplication Check) + * Checks if the material has any duplicates. + *

+ * Example: + *

{@code MELON, CARROT, POTATO, BEETROOT -> true} + * + * @param name the name of the material to check. + * + * @return true if there's a duplicated material for this material, otherwise false. + * @since 2.0.0 + */ + private static boolean isDuplicated(@Nonnull String name) { + // Don't use matchXMaterial() since this method is being called from matchXMaterial() itself and will cause a StackOverflowError. + return DUPLICATED.contains(name); + } + + /** + * Gets the XMaterial based on the material's ID (Magic Value) and data value.
+ * You should avoid using this for performance issues. + * + * @param id the ID (Magic value) of the material. + * @param data the data value of the material. + * + * @return a parsed XMaterial with the same ID and data value. + * @see #matchXMaterial(ItemStack) + * @since 2.0.0 + * @deprecated this method loops through all the available materials and matches their ID using {@link #getId()} + * which takes a really long time. Plugins should no longer support IDs. If you want, you can make a {@link Map} cache yourself. + * This method obviously doesn't work for 1.13+ and will not be supported. This is only here for debugging purposes. + */ + @Nonnull + @Deprecated + public static Optional matchXMaterial(int id, byte data) { + if (id < 0 || id > MAX_ID || data < 0) return Optional.empty(); + for (XMaterial materials : VALUES) { + if (materials.data == data && materials.getId() == id) return Optional.of(materials); + } + return Optional.empty(); + } + + /** + * Attempts to build the string like an enum name. + * Removes all the spaces, and extra non-English characters. Also removes some config/in-game based strings. + * While this method is hard to maintain, it's extremely efficient. It's approximately more than x5 times faster than + * the normal RegEx + String Methods approach for both formatted and unformatted material names. + * + * @param name the material name to modify. + * + * @return an enum name. + * @since 2.0.0 + */ + @Nonnull + protected static String format(@Nonnull String name) { + int len = name.length(); + char[] chs = new char[len]; + int count = 0; + boolean appendUnderline = false; + + for (int i = 0; i < len; i++) { + char ch = name.charAt(i); + + if (!appendUnderline && count != 0 && (ch == '-' || ch == ' ' || ch == '_') && chs[count] != '_') + appendUnderline = true; + else { + boolean number = false; + // Old materials have numbers in them. + if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (number = (ch >= '0' && ch <= '9'))) { + if (appendUnderline) { + chs[count++] = '_'; + appendUnderline = false; + } + + if (number) chs[count++] = ch; + else chs[count++] = (char) (ch & 0x5f); + } + } + } + + return new String(chs, 0, count); + } + + /** + * Checks if the specified version is the same version or higher than the current server version. + * + * @param version the major version to be checked. "1." is ignored. E.g. 1.12 = 12 | 1.9 = 9 + * + * @return true of the version is equal or higher than the current version. + * @since 2.0.0 + */ + public static boolean supports(int version) { + return Data.VERSION >= version; + } + + public String[] getLegacy() { + return this.legacy; + } + + /** + * XMaterial Paradox (Duplication Check) + * I've concluded that this is just an infinite loop that keeps + * going around the Singular Form and the Plural Form materials. A waste of brain cells and a waste of time. + * This solution works just fine anyway. + *

+ * A solution for XMaterial Paradox. + * Manually parses the duplicated materials to find the exact material based on the server version. + * If the name ends with "S" -> Plural Form Material. + * Plural methods are only plural if they're also {@link #DUPLICATED} + *

+ * The only special exceptions are {@link #BRICKS} and {@link #NETHER_BRICKS} + * + * @return true if this material is a plural form material, otherwise false. + * @since 8.0.0 + */ + private boolean isPlural() { + // this.name().charAt(this.name().length() - 1) == 'S' + return this == CARROTS || this == POTATOES; + } + + /** + * Checks if the list of given material names matches the given base material. + * Mostly used for configs. + *

+ * Supports {@link String#contains} {@code CONTAINS:NAME} and Regular Expression {@code REGEX:PATTERN} formats. + *

+ * Example: + *

+     *     XMaterial material = {@link #matchXMaterial(ItemStack)};
+     *     if (material.isOneOf(plugin.getConfig().getStringList("disabled-items")) return;
+     * 
+ *
+ * {@code CONTAINS} Examples: + *
+     *     {@code "CONTAINS:CHEST" -> CHEST, ENDERCHEST, TRAPPED_CHEST -> true}
+     *     {@code "cOnTaINS:dYe" -> GREEN_DYE, YELLOW_DYE, BLUE_DYE, INK_SACK -> true}
+     * 
+ *

+ * {@code REGEX} Examples + *

+     *     {@code "REGEX:^.+_.+_.+$" -> Every Material with 3 underlines or more: SHULKER_SPAWN_EGG, SILVERFISH_SPAWN_EGG, SKELETON_HORSE_SPAWN_EGG}
+     *     {@code "REGEX:^.{1,3}$" -> Material names that have 3 letters only: BED, MAP, AIR}
+     * 
+ *

+ * The reason that there are tags for {@code CONTAINS} and {@code REGEX} is for the performance. + * Although RegEx patterns are cached in this method, + * please avoid using the {@code REGEX} tag if you can use the {@code CONTAINS} tag instead. + * It'll have a huge impact on performance. + * Please avoid using {@code (capturing groups)} there's no use for them in this case. + * If you want to use groups, use {@code (?: non-capturing groups)}. It's faster. + *

+ * Want to learn RegEx? You can mess around in RegExr website. + * + * @param materials the material names to check base material on. + * + * @return true if one of the given material names is similar to the base material. + * @since 3.1.1 + */ + public boolean isOneOf(@Nullable Collection materials) { + if (materials == null || materials.isEmpty()) return false; + String name = this.name(); - for (String comp : materials) { - String checker = comp.toUpperCase(Locale.ENGLISH); - if (checker.startsWith("CONTAINS:")) { - comp = format(checker.substring(9)); - if (name.contains(comp)) return true; - continue; - } - if (checker.startsWith("REGEX:")) { - comp = comp.substring(6); - Pattern pattern = CACHED_REGEX.getIfPresent(comp); - if (pattern == null) { - try { - pattern = Pattern.compile(comp); - CACHED_REGEX.put(comp, pattern); - }catch (PatternSyntaxException ex) { - ex.printStackTrace(); - } - } - if (pattern != null && pattern.matcher(name).matches()) return true; - continue; - } + for (String comp : materials) { + String checker = comp.toUpperCase(Locale.ENGLISH); + if (checker.startsWith("CONTAINS:")) { + comp = format(checker.substring(9)); + if (name.contains(comp)) return true; + continue; + } + if (checker.startsWith("REGEX:")) { + comp = comp.substring(6); + Pattern pattern = CACHED_REGEX.getIfPresent(comp); + if (pattern == null) { + try { + pattern = Pattern.compile(comp); + CACHED_REGEX.put(comp, pattern); + } catch (PatternSyntaxException ex) { + ex.printStackTrace(); + } + } + if (pattern != null && pattern.matcher(name).matches()) return true; + continue; + } - // Direct Object Equals - Optional xMat = matchXMaterial(comp); - if (xMat.isPresent() && xMat.get() == this) return true; - } - return false; - } + // Direct Object Equals + Optional xMat = matchXMaterial(comp); + if (xMat.isPresent() && xMat.get() == this) return true; + } + return false; + } - /** - * Sets the {@link Material} (and data value on older versions) of an item. - * Damageable materials will not have their durability changed. - *

- * Use {@link #parseItem()} instead when creating new ItemStacks. - * - * @param item the item to change its type. - * - * @see #parseItem() - * @since 3.0.0 - */ - @Nonnull - @SuppressWarnings ("deprecation") - public ItemStack setType(@Nonnull ItemStack item) { - Objects.requireNonNull(item, "Cannot set material for null ItemStack"); - Material material = this.parseMaterial(); - Objects.requireNonNull(material, () -> "Unsupported material: " + this.name()); + /** + * Sets the {@link Material} (and data value on older versions) of an item. + * Damageable materials will not have their durability changed. + *

+ * Use {@link #parseItem()} instead when creating new ItemStacks. + * + * @param item the item to change its type. + * + * @see #parseItem() + * @since 3.0.0 + */ + @Nonnull + @SuppressWarnings("deprecation") + public ItemStack setType(@Nonnull ItemStack item) { + Objects.requireNonNull(item, "Cannot set material for null ItemStack"); + Material material = this.parseMaterial(); + Objects.requireNonNull(material, () -> "Unsupported material: " + this.name()); - item.setType(material); - if (!Data.ISFLAT && material.getMaxDurability() <= 0) item.setDurability(this.data); - return item; - } + item.setType(material); + if (!Data.ISFLAT && material.getMaxDurability() <= 0) item.setDurability(this.data); + return item; + } - /** - * Checks if the given material name matches any of this xmaterial's legacy material names. - * All the values passed to this method will not be null or empty and are formatted correctly. - * - * @param name the material name to check. - * - * @return true if it's one of the legacy names, otherwise false. - * @since 2.0.0 - */ - private boolean anyMatchLegacy(@Nonnull String name) { - for (int i = this.legacy.length - 1; i >= 0; i--) { - if (name.equals(this.legacy[i])) return true; - } - return false; - } + /** + * Checks if the given material name matches any of this xmaterial's legacy material names. + * All the values passed to this method will not be null or empty and are formatted correctly. + * + * @param name the material name to check. + * + * @return true if it's one of the legacy names, otherwise false. + * @since 2.0.0 + */ + private boolean anyMatchLegacy(@Nonnull String name) { + for (int i = this.legacy.length - 1; i >= 0; i--) { + if (name.equals(this.legacy[i])) return true; + } + return false; + } - /** - * Parses an enum name to a user-friendly name. - * These names will have underlines removed and with each word capitalized. - *

- * Examples: - *

-	 *     {@literal EMERALD                 -> Emerald}
-	 *     {@literal EMERALD_BLOCK           -> Emerald Block}
-	 *     {@literal ENCHANTED_GOLDEN_APPLE  -> Enchanted Golden Apple}
-	 * 
- * - * @return a more user-friendly enum name. - * @since 3.0.0 - */ - @Override - @Nonnull - public String toString() { - return WordUtils.capitalize(this.name().replace('_', ' ').toLowerCase(Locale.ENGLISH)); - } + /** + * Parses an enum name to a user-friendly name. + * These names will have underlines removed and with each word capitalized. + *

+ * Examples: + *

+     *     {@literal EMERALD                 -> Emerald}
+     *     {@literal EMERALD_BLOCK           -> Emerald Block}
+     *     {@literal ENCHANTED_GOLDEN_APPLE  -> Enchanted Golden Apple}
+     * 
+ * + * @return a more user-friendly enum name. + * @since 3.0.0 + */ + @Override + @Nonnull + public String toString() { + return Arrays.stream(name().split("_")) + .map(t -> t.charAt(0) + t.substring(1).toLowerCase()) + .collect(Collectors.joining(" ")); + } - /** - * Gets the ID (Magic value) of the material. - * https://www.minecraftinfo.com/idlist.htm - *

- * Spigot added material ID support back in 1.16+ - * - * @return the ID of the material or -1 if it's not a legacy material or the server doesn't support the material. - * @see #matchXMaterial(int, byte) - * @since 2.2.0 - */ - @SuppressWarnings ("deprecation") - public int getId() { - // https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/diff/src/main/java/org/bukkit/Material.java?until=1cb03826ebde4ef887519ce37b0a2a341494a183 - // Should start working again in 1.16+ - Material material = this.parseMaterial(); - if (material == null) return -1; - try { - return material.getId(); - }catch (IllegalArgumentException ignored) { - return -1; - } - } + /** + * Gets the ID (Magic value) of the material. + * https://www.minecraftinfo.com/idlist.htm + *

+ * Spigot added material ID support back in 1.16+ + * + * @return the ID of the material or -1 if it's not a legacy material or the server doesn't support the material. + * @see #matchXMaterial(int, byte) + * @since 2.2.0 + */ + @SuppressWarnings("deprecation") + public int getId() { + // https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/diff/src/main/java/org/bukkit/Material.java?until=1cb03826ebde4ef887519ce37b0a2a341494a183 + // Should start working again in 1.16+ + Material material = this.parseMaterial(); + if (material == null) return -1; + try { + return material.getId(); + } catch (IllegalArgumentException ignored) { + return -1; + } + } - /** - * The data value of this material pre-flattening. - *

- * Can be accessed with {@link ItemStack#getData()} then {@code MaterialData#getData()} - * or {@link ItemStack#getDurability()} if not damageable. - * - * @return data of this material, or 0 if none. - * @since 1.0.0 - */ - @SuppressWarnings ("deprecation") - public byte getData() { - return data; - } + /** + * The data value of this material pre-flattening. + *

+ * Can be accessed with {@link ItemStack#getData()} then {@code MaterialData#getData()} + * or {@link ItemStack#getDurability()} if not damageable. + * + * @return data of this material, or 0 if none. + * @since 1.0.0 + */ + @SuppressWarnings("deprecation") + public byte getData() { + return data; + } - /** - * Parses an item from this XMaterial. - * Uses data values on older versions. - * - * @return an ItemStack with the same material (and data value if in older versions.) - * @see #setType(ItemStack) - * @since 2.0.0 - */ - @Nullable - @SuppressWarnings ("deprecation") - public ItemStack parseItem() { - Material material = this.parseMaterial(); - if (material == null) return null; - return Data.ISFLAT ? new ItemStack(material) : new ItemStack(material, 1, this.data); - } + /** + * Parses an item from this XMaterial. + * Uses data values on older versions. + * + * @return an ItemStack with the same material (and data value if in older versions.) + * @see #setType(ItemStack) + * @since 2.0.0 + */ + @Nullable + @SuppressWarnings("deprecation") + public ItemStack parseItem() { + Material material = this.parseMaterial(); + if (material == null) return null; + return Data.ISFLAT ? new ItemStack(material) : new ItemStack(material, 1, this.data); + } - /** - * Parses the material of this XMaterial. - * - * @return the material related to this XMaterial based on the server version. - * @since 1.0.0 - */ - @Nullable - public Material parseMaterial() { - return this.material; - } + /** + * Parses the material of this XMaterial. + * + * @return the material related to this XMaterial based on the server version. + * @since 1.0.0 + */ + @Nullable + public Material parseMaterial() { + return this.material; + } - /** - * Checks if an item has the same material (and data value on older versions). - * - * @param item item to check. - * - * @return true if the material is the same as the item's material (and data value if on older versions), otherwise false. - * @since 1.0.0 - */ - @SuppressWarnings ("deprecation") - public boolean isSimilar(@Nonnull ItemStack item) { - Objects.requireNonNull(item, "Cannot compare with null ItemStack"); - if (item.getType() != this.parseMaterial()) return false; - return Data.ISFLAT || item.getDurability() == this.data || item.getType().getMaxDurability() > 0; - } + /** + * Checks if an item has the same material (and data value on older versions). + * + * @param item item to check. + * + * @return true if the material is the same as the item's material (and data value if on older versions), otherwise false. + * @since 1.0.0 + */ + @SuppressWarnings("deprecation") + public boolean isSimilar(@Nonnull ItemStack item) { + Objects.requireNonNull(item, "Cannot compare with null ItemStack"); + if (item.getType() != this.parseMaterial()) return false; + return Data.ISFLAT || item.getDurability() == this.data || item.getType().getMaxDurability() > 0; + } - /** - * Checks if this material is supported in the current version. - * Suggested materials will be ignored. - *

- * Note that you should use {@link #parseMaterial()} or {@link #parseItem()} and check if it's null - * if you're going to parse and use the material/item later. - * - * @return true if the material exists in {@link Material} list. - * @since 2.0.0 - */ - public boolean isSupported() { - return this.material != null; - } + /** + * Checks if this material is supported in the current version. + * Suggested materials will be ignored. + *

+ * Note that you should use {@link #parseMaterial()} or {@link #parseItem()} and check if it's null + * if you're going to parse and use the material/item later. + * + * @return true if the material exists in {@link Material} list. + * @since 2.0.0 + */ + public boolean isSupported() { + return this.material != null; + } - /** - * This method is needed due to Java enum initialization limitations. - * It's really inefficient yes, but it's only used for initialization. - *

- * Yes there are many other ways like comparing the hardcoded ordinal or using a boolean in the enum constructor, - * but it's not really a big deal. - *

- * This method should not be called if the version is after the flattening update {@link Data#ISFLAT} - * and is only used for parsing materials, not matching, for matching check {@link #DUPLICATED} - */ - private boolean isDuplicated() { - switch (this.name()) { - case "MELON": - case "CARROT": - case "POTATO": - case "GRASS": - case "BRICK": - case "NETHER_BRICK": + /** + * This method is needed due to Java enum initialization limitations. + * It's really inefficient yes, but it's only used for initialization. + *

+ * Yes there are many other ways like comparing the hardcoded ordinal or using a boolean in the enum constructor, + * but it's not really a big deal. + *

+ * This method should not be called if the version is after the flattening update {@link Data#ISFLAT} + * and is only used for parsing materials, not matching, for matching check {@link #DUPLICATED} + */ + private boolean isDuplicated() { + switch (this.name()) { + case "MELON": + case "CARROT": + case "POTATO": + case "GRASS": + case "BRICK": + case "NETHER_BRICK": - // Illegal Elements - // Since both 1.12 and 1.13 have _DOOR XMaterial will use it - // for 1.12 to parse the material, but it needs _DOOR_ITEM. - // We'll trick XMaterial into thinking this needs to be parsed - // using the old methods. - // Some of these materials have their enum name added to the legacy list as well. - case "DARK_OAK_DOOR": - case "ACACIA_DOOR": - case "BIRCH_DOOR": - case "JUNGLE_DOOR": - case "SPRUCE_DOOR": - case "MAP": - case "CAULDRON": - case "BREWING_STAND": - case "FLOWER_POT": - return true; - default: - return false; - } - } + // Illegal Elements + // Since both 1.12 and 1.13 have _DOOR XMaterial will use it + // for 1.12 to parse the material, but it needs _DOOR_ITEM. + // We'll trick XMaterial into thinking this needs to be parsed + // using the old methods. + // Some of these materials have their enum name added to the legacy list as well. + case "DARK_OAK_DOOR": + case "ACACIA_DOOR": + case "BIRCH_DOOR": + case "JUNGLE_DOOR": + case "SPRUCE_DOOR": + case "MAP": + case "CAULDRON": + case "BREWING_STAND": + case "FLOWER_POT": + return true; + default: + return false; + } + } - /** - * Used for data that need to be accessed during enum initialization. - * - * @since 9.0.0 - */ - private static final class Data { - /** - * The current version of the server in the form of a major version. - * If the static initialization for this fails, you know something's wrong with the server software. - * - * @since 1.0.0 - */ - private static final int VERSION; + /** + * Used for data that need to be accessed during enum initialization. + * + * @since 9.0.0 + */ + private static final class Data { + /** + * The current version of the server in the form of a major version. + * If the static initialization for this fails, you know something's wrong with the server software. + * + * @since 1.0.0 + */ + private static final int VERSION; - static { // This needs to be right below VERSION because of initialization order. - String version = Bukkit.getVersion(); - Matcher matcher = Pattern.compile("MC: \\d\\.(\\d+)").matcher(version); + static { // This needs to be right below VERSION because of initialization order. + String version = Bukkit.getVersion(); + Matcher matcher = Pattern.compile("MC: \\d\\.(\\d+)").matcher(version); - if (matcher.find()) - VERSION = Integer.parseInt(matcher.group(1)); - else throw new IllegalArgumentException("Failed to parse server version from: " + version); - } + if (matcher.find()) VERSION = Integer.parseInt(matcher.group(1)); + else throw new IllegalArgumentException("Failed to parse server version from: " + version); + } - /** - * Cached result if the server version is after the v1.13 flattening update. - * - * @since 3.0.0 - */ - private static final boolean ISFLAT = supports(13); - } - - /* CUSTOM */ - - public static ItemStack playerSkullItem() { - Material mat = PLAYER_HEAD.parseMaterial(); - if (isNewVersion()) return new ItemStack(mat); - return new ItemStack(Material.getMaterial("SKULL_ITEM"), 1, (short) SkullType.PLAYER.ordinal()); - } - - public static XMaterial mobItem(EntityType type) { - if (type == null) return SPONGE; - Optional material = XMaterial.matchXMaterial(type.name() + "_SPAWN_EGG"); - if (material.isPresent()) return material.get(); - if (type == EntityType.WITHER) return WITHER_SKELETON_SKULL; - if (type == EntityType.IRON_GOLEM) return IRON_BLOCK; - if (type == EntityType.SNOWMAN) return SNOW_BLOCK; - if (type == EntityType.MUSHROOM_COW) return MOOSHROOM_SPAWN_EGG; - if (type == EntityType.GIANT) return ZOMBIE_SPAWN_EGG; - if (type == EntityType.ARMOR_STAND) return ARMOR_STAND; - if (type == EntityType.PLAYER) return PLAYER_HEAD; - if (type == EntityType.ENDER_DRAGON) return DRAGON_HEAD; - if (type.name().equals("PIG_ZOMBIE") || type.name().equals("ZOMBIFIED_PIGLIN")) return ZOMBIFIED_PIGLIN_SPAWN_EGG; - if (type.name().equals("ILLUSIONER")) return BLAZE_POWDER; - return SPONGE; - } - + /** + * Cached result if the server version is after the v1.13 flattening update. + * + * @since 3.0.0 + */ + private static final boolean ISFLAT = supports(13); + } } \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/mobs/BQBoss.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/mobs/BQBoss.java index c5e05b07..00692a6f 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/compatibility/mobs/BQBoss.java +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/mobs/BQBoss.java @@ -20,6 +20,7 @@ import fr.skytasul.quests.gui.templates.PagedGUI; import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.MinecraftNames; +import fr.skytasul.quests.utils.Utils; import fr.skytasul.quests.utils.XMaterial; public class BQBoss implements MobFactory { @@ -40,7 +41,7 @@ public void itemClick(Player p, Consumer run) { new PagedGUI("List of Bosses", DyeColor.ORANGE, org.mineacademy.boss.api.BossAPI.getBosses(), null, x -> x.getName()) { @Override public ItemStack getItemStack(Boss object) { - return ItemUtils.item(XMaterial.mobItem(object.getType()), object.getName()); + return ItemUtils.item(Utils.mobItem(object.getType()), object.getName()); } @Override diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/mobs/MythicMobs.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/mobs/MythicMobs.java index 1cbcff98..9da602c8 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/compatibility/mobs/MythicMobs.java +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/mobs/MythicMobs.java @@ -46,7 +46,7 @@ public void itemClick(Player p, Consumer run) { public ItemStack getItemStack(MythicMob object) { XMaterial mobItem; try { - mobItem = XMaterial.mobItem(getEntityType(object)); + mobItem = Utils.mobItem(getEntityType(object)); }catch (Exception ex) { mobItem = XMaterial.SPONGE; BeautyQuests.logger.warning("Unknow entity type for MythicMob " + object.getInternalName(), ex); diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/mobs/MythicMobs5.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/mobs/MythicMobs5.java index 73528157..f7865d3a 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/compatibility/mobs/MythicMobs5.java +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/mobs/MythicMobs5.java @@ -47,7 +47,7 @@ public void itemClick(Player p, Consumer run) { public ItemStack getItemStack(MythicMob object) { XMaterial mobItem; try { - mobItem = XMaterial.mobItem(getEntityType(object)); + mobItem = Utils.mobItem(getEntityType(object)); }catch (Exception ex) { mobItem = XMaterial.SPONGE; BeautyQuests.logger.warning("Unknow entity type for MythicMob " + object.getInternalName(), ex); From f5fd94b094bc5cd177200dedba31e63fd9041d60 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Wed, 6 Jul 2022 21:50:20 +0200 Subject: [PATCH 028/111] Now using Paper API * if paper is enabled, the plugin will now check for quest items when player dies and make it keep them --- core/pom.xml | 16 ++++++++--- .../java/fr/skytasul/quests/BeautyQuests.java | 18 +++++++++++++ .../fr/skytasul/quests/QuestsListener.java | 7 +++++ .../fr/skytasul/quests/gui/ItemUtils.java | 2 +- .../skytasul/quests/utils/MinecraftNames.java | 7 +++-- .../quests/utils/compatibility/Paper.java | 27 +++++++++++++++++++ 6 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 core/src/main/java/fr/skytasul/quests/utils/compatibility/Paper.java diff --git a/core/pom.xml b/core/pom.xml index 0cfa6642..14365377 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -61,6 +61,10 @@ + + papermc + https://repo.papermc.io/repository/maven-public/ + placeholderapi https://repo.extendedclip.com/content/repositories/placeholderapi/ @@ -121,9 +125,15 @@ - org.spigotmc - spigot - 1.18.2-R0.1-SNAPSHOT + mysql + mysql-connector-java + 8.0.29 + provided + + + io.papermc.paper + paper-api + 1.19-R0.1-SNAPSHOT provided diff --git a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java index 75f479c9..d4936fad 100644 --- a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java +++ b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java @@ -68,6 +68,7 @@ public class BeautyQuests extends JavaPlugin { public static LoggerExpanded logger; private static BeautyQuests instance; private BukkitRunnable saveTask; + private boolean isPaper; /* --------- Storage --------- */ @@ -119,6 +120,8 @@ public void onEnable(){ try { logger.info("------------ BeautyQuests ------------"); + checkPaper(); + dependencies.testCompatibilities(); Bukkit.getPluginManager().registerEvents(dependencies, this); @@ -217,6 +220,17 @@ public void onDisable(){ } /* ---------- Various init ---------- */ + + private void checkPaper() { + try { + isPaper = Class.forName("com.destroystokyo.paper.ParticleBuilder") != null; + DebugUtils.logMessage("Paper detected."); + }catch (ClassNotFoundException ex) { + isPaper = false; + logger.warning("You are not running the Paper software.\n" + + "It is highly recommended to use it for extended features and more stability."); + } + } private void registerCommands(){ CommandsManager questCommand = new CommandsManager((sender) -> { @@ -618,6 +632,10 @@ public QuestPoolsManager getPoolsManager() { public ILoggerHandler getLoggerHandler() { return loggerHandler == null ? ILoggerHandler.EMPTY_LOGGER : loggerHandler; } + + public boolean isRunningPaper() { + return isPaper; + } public static BeautyQuests getInstance(){ diff --git a/core/src/main/java/fr/skytasul/quests/QuestsListener.java b/core/src/main/java/fr/skytasul/quests/QuestsListener.java index 804b6896..d2a3abd6 100644 --- a/core/src/main/java/fr/skytasul/quests/QuestsListener.java +++ b/core/src/main/java/fr/skytasul/quests/QuestsListener.java @@ -13,6 +13,7 @@ import org.bukkit.event.Listener; import org.bukkit.event.block.BlockBreakEvent; import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.event.entity.PlayerDeathEvent; import org.bukkit.event.inventory.CraftItemEvent; import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; @@ -44,6 +45,7 @@ import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.Utils; import fr.skytasul.quests.utils.XMaterial; +import fr.skytasul.quests.utils.compatibility.Paper; public class QuestsListener implements Listener{ @@ -198,6 +200,11 @@ public void onEat(PlayerItemConsumeEvent e) { } } + @EventHandler (priority = EventPriority.HIGH) + public void onDeath(PlayerDeathEvent e) { + if (BeautyQuests.getInstance().isRunningPaper()) Paper.handleDeathItems(e, Utils::isQuestItem); + } + @EventHandler (priority = EventPriority.MONITOR) public void onBreak(BlockBreakEvent e) { if (e.isCancelled()) return; diff --git a/core/src/main/java/fr/skytasul/quests/gui/ItemUtils.java b/core/src/main/java/fr/skytasul/quests/gui/ItemUtils.java index 5b1ec6dc..e24f0a1f 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/ItemUtils.java +++ b/core/src/main/java/fr/skytasul/quests/gui/ItemUtils.java @@ -30,7 +30,7 @@ public class ItemUtils { private static final int LORE_LINE_LENGTH = 40; - private static final int LORE_LINE_LENGTH_CRITICAL = 100; + private static final int LORE_LINE_LENGTH_CRITICAL = 1000; /** * Create an ItemStack instance from a generic XMaterial diff --git a/core/src/main/java/fr/skytasul/quests/utils/MinecraftNames.java b/core/src/main/java/fr/skytasul/quests/utils/MinecraftNames.java index 8400864b..36f7db51 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/MinecraftNames.java +++ b/core/src/main/java/fr/skytasul/quests/utils/MinecraftNames.java @@ -7,7 +7,6 @@ import java.util.Map.Entry; import org.apache.commons.lang.WordUtils; -import org.apache.logging.log4j.core.util.FileUtils; import org.bukkit.entity.EntityType; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.PotionMeta; @@ -34,7 +33,11 @@ public static boolean intialize(String fileName){ BeautyQuests.logger.warning("File " + fileName + " not found for loading translations."); return false; } - if (!FileUtils.getFileExtension(file).toLowerCase().equals("json")) { + + int lastPoint = file.getName().lastIndexOf('.'); + String extension = lastPoint == -1 ? "" : file.getName().substring(lastPoint + 1); + + if (!extension.equalsIgnoreCase("json")) { BeautyQuests.logger.warning("File " + fileName + " is not a JSON file."); return false; } diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/Paper.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/Paper.java new file mode 100644 index 00000000..6a53d5ce --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/Paper.java @@ -0,0 +1,27 @@ +package fr.skytasul.quests.utils.compatibility; + +import java.util.Iterator; +import java.util.function.Predicate; + +import org.bukkit.event.entity.PlayerDeathEvent; +import org.bukkit.inventory.ItemStack; + +import fr.skytasul.quests.utils.nms.NMS; + +public final class Paper { + + private Paper() {} + + public static void handleDeathItems(PlayerDeathEvent event, Predicate predicate) { + if (NMS.getMCVersion() < 13) return; + + for (Iterator iterator = event.getDrops().iterator(); iterator.hasNext();) { + ItemStack item = iterator.next(); + if (predicate.test(item)) { + iterator.remove(); + event.getItemsToKeep().add(item); + } + } + } + +} From ff7c6b64114ca2d90f329d9140d9abf7dffeba31 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Wed, 6 Jul 2022 23:16:57 +0200 Subject: [PATCH 029/111] Fixed some data migration --- .../java/fr/skytasul/quests/BeautyQuests.java | 14 ++++++++++++-- .../quests/api/objects/QuestObject.java | 3 ++- .../api/serializable/SerializableObject.java | 5 ++--- .../quests/api/stages/AbstractStage.java | 2 +- .../stages/types/AbstractCountableStage.java | 7 ++----- .../java/fr/skytasul/quests/utils/Utils.java | 17 +++++++++++++++++ 6 files changed, 36 insertions(+), 12 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java index d4936fad..b578f6a3 100644 --- a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java +++ b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java @@ -541,7 +541,12 @@ public boolean createDataBackup() { if (!QuestsConfiguration.backups) return false; logger.info("Creating data backup..."); try{ - logger.info("Datas backup created in " + Files.copy(dataFile.toPath(), backupDir().resolve("data.yml")).getParent().getFileName()); + Path target = backupDir().resolve("data.yml"); + if (Files.exists(target)) { + logger.warning("File " + target.toString() + " already exist. This should not happen."); + }else { + logger.info("Datas backup created in " + Files.copy(dataFile.toPath(), target).getParent().getFileName()); + } return true; }catch (Exception e) { logger.severe("An error occured while creating the backup.", e); @@ -553,7 +558,12 @@ public boolean createQuestBackup(Path file, String msg) { if (!QuestsConfiguration.backups) return false; logger.info("Creating single quest backup..."); try{ - logger.info("Quest backup created at " + Files.copy(file, Paths.get(file.toString() + "-backup" + format.format(new Date()) + ".yml")).getFileName()); + Path target = Paths.get(file.toString() + "-backup" + format.format(new Date()) + ".yml"); + if (Files.exists(target)) { + logger.warning("File " + target.toString() + " already exist. This should not happen."); + }else { + logger.info("Quest backup created at " + Files.copy(file, target).getFileName()); + } return true; }catch (Exception e) { logger.severe("An error occured while creating the backup.", e); diff --git a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java index d44d2895..acaddd4c 100644 --- a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java +++ b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java @@ -11,6 +11,7 @@ import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.structure.Quest; import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.Utils; public abstract class QuestObject extends SerializableObject implements Cloneable { @@ -54,7 +55,7 @@ protected void load(Map savedDatas) {} public void save(ConfigurationSection section) { Map datas = new HashMap<>(); save(datas); - datas.forEach(section::set); + Utils.setConfigurationSectionContent(section, datas); } @Override diff --git a/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableObject.java b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableObject.java index d5998820..6ebcc93c 100644 --- a/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableObject.java +++ b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableObject.java @@ -10,6 +10,7 @@ import org.bukkit.configuration.MemoryConfiguration; import fr.skytasul.quests.BeautyQuests; +import fr.skytasul.quests.utils.Utils; public abstract class SerializableObject { @@ -46,9 +47,7 @@ public final void serialize(ConfigurationSection section) { } public static > T deserialize(Map map, SerializableRegistry registry) { - MemoryConfiguration section = new MemoryConfiguration(); - map.forEach(section::set); - return deserialize(section, registry); + return deserialize(Utils.createConfigurationSection(map), registry); } public static > T deserialize(ConfigurationSection section, SerializableRegistry registry) { diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java index 889b2488..37eeead0 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java @@ -304,7 +304,7 @@ protected void serialize(Map map) {} protected void serialize(ConfigurationSection section) { Map map = new HashMap<>(); serialize(map); - map.forEach(section::set); + Utils.setConfigurationSectionContent(section, map); } public final void save(ConfigurationSection section) { diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableStage.java index 68885c39..58ebec10 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableStage.java @@ -10,7 +10,6 @@ import org.bukkit.boss.BarColor; import org.bukkit.boss.BarStyle; import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.MemoryConfiguration; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitTask; @@ -219,7 +218,7 @@ protected void serialize(ConfigurationSection section) { } Map serialized = new HashMap<>(); serialize(serialized); - serialized.forEach(section::set); + Utils.setConfigurationSectionContent(section, serialized); } /** @@ -227,9 +226,7 @@ protected void serialize(ConfigurationSection section) { */ @Deprecated protected void deserialize(Map serializedDatas) { - MemoryConfiguration configuration = new MemoryConfiguration(); - serializedDatas.forEach(configuration::set); - deserialize(configuration); + deserialize(Utils.createConfigurationSection(serializedDatas)); } protected void deserialize(ConfigurationSection section) { diff --git a/core/src/main/java/fr/skytasul/quests/utils/Utils.java b/core/src/main/java/fr/skytasul/quests/utils/Utils.java index 741a2e0d..e9f2ab4e 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Utils.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Utils.java @@ -24,6 +24,7 @@ import org.bukkit.World; import org.bukkit.command.CommandSender; import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.MemoryConfiguration; import org.bukkit.entity.EntityType; import org.bukkit.entity.Firework; import org.bukkit.entity.LivingEntity; @@ -374,6 +375,22 @@ public static Map mapFromConfigurationSection(ConfigurationSecti return map; } + public static ConfigurationSection createConfigurationSection(Map content) { + MemoryConfiguration section = new MemoryConfiguration(); + setConfigurationSectionContent(section, content); + return section; + } + + public static void setConfigurationSectionContent(ConfigurationSection section, Map content) { + content.forEach((key, value) -> { + if (value instanceof Map) { + section.createSection(key, (Map) value); + }else { + section.set(key, value); + } + }); + } + public static List combineItems(List items) { ArrayList newItems = new ArrayList<>(items.size()); items: for (ItemStack original : items) { From 28f3a3b3c4a08b5c21c4924d7b2f31ef8986fe1f Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Thu, 7 Jul 2022 13:02:58 +0200 Subject: [PATCH 030/111] :sparkles: Added location tracking for region stage --- .../fr/skytasul/quests/stages/StageArea.java | 32 ++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageArea.java b/core/src/main/java/fr/skytasul/quests/stages/StageArea.java index ae085330..37aa8be8 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageArea.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageArea.java @@ -2,16 +2,22 @@ import org.apache.commons.lang.Validate; import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.World; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.player.PlayerMoveEvent; +import com.sk89q.worldedit.bukkit.BukkitAdapter; +import com.sk89q.worldguard.protection.regions.GlobalProtectedRegion; import com.sk89q.worldguard.protection.regions.ProtectedRegion; import fr.skytasul.quests.api.stages.AbstractStage; import fr.skytasul.quests.api.stages.StageCreation; +import fr.skytasul.quests.api.stages.types.Locatable; +import fr.skytasul.quests.api.stages.types.Locatable.LocatableType; +import fr.skytasul.quests.api.stages.types.Locatable.LocatedType; import fr.skytasul.quests.editors.TextEditor; import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.gui.creation.stages.Line; @@ -25,12 +31,18 @@ import fr.skytasul.quests.utils.compatibility.worldguard.BQWorldGuard; import fr.skytasul.quests.utils.compatibility.worldguard.WorldGuardEntryEvent; -public class StageArea extends AbstractStage{ +@LocatableType (types = LocatedType.OTHER) +public class StageArea extends AbstractStage implements Locatable.PreciseLocatable { + + private static final long REFRESH_CENTER = 60 * 1000L; private final ProtectedRegion region; private final boolean exit; private final World world; + private Locatable.Located center = null; + private long lastCenter = 0; + public StageArea(QuestBranch branch, String regionName, String worldName, boolean exit) { super(branch); @@ -76,6 +88,24 @@ public String descriptionLine(PlayerAccount acc, Source source){ protected Object[] descriptionFormat(PlayerAccount acc, Source source){ return new String[]{region.getId()}; } + + @Override + public Located getLocated() { + if (region instanceof GlobalProtectedRegion) return null; + + if (System.currentTimeMillis() - lastCenter > REFRESH_CENTER) { + Location centerLoc = BukkitAdapter.adapt(world, + region.getMaximumPoint() + .subtract(region.getMinimumPoint()) + .divide(2) + .add(region.getMinimumPoint())) // midpoint + .add(0.5, 0.5, 0.5); + + center = Locatable.Located.create(centerLoc); + lastCenter = System.currentTimeMillis(); + } + return center; + } public ProtectedRegion getRegion(){ return region; From db36a9db9be75f23ed115afcf3bf08b83385fce3 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Fri, 8 Jul 2022 20:09:31 +0200 Subject: [PATCH 031/111] :arrow_up: Updated MythicMobs dependency --- core/pom.xml | 2 +- .../quests/utils/compatibility/DependenciesManager.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index 14365377..43106f5e 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -223,7 +223,7 @@ io.lumine Mythic-Dist - 5.0.1 + 5.0.4 provided diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/DependenciesManager.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/DependenciesManager.java index bf2acc93..b09373ec 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/compatibility/DependenciesManager.java +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/DependenciesManager.java @@ -86,10 +86,10 @@ public class DependenciesManager implements Listener { public static final BQDependency mm = new BQDependency("MythicMobs", () -> { try { - Class.forName("io.lumine.xikage.mythicmobs.mobs.MythicMob"); - QuestsAPI.registerMobFactory(new MythicMobs()); - }catch (ClassNotFoundException ex) { + Class.forName("io.lumine.mythic.api.MythicPlugin"); QuestsAPI.registerMobFactory(new MythicMobs5()); + }catch (ClassNotFoundException ex) { + QuestsAPI.registerMobFactory(new MythicMobs()); } }); From 51310f1b9574da9c4f7a57b32a90136975995b77 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sat, 9 Jul 2022 19:09:33 +0200 Subject: [PATCH 032/111] :card_file_box: Change player data structure to allow external datas --- .../quests/players/PlayerQuestDatas.java | 55 ++++++++------ .../quests/players/PlayersManagerDB.java | 73 ++++++++++++------- .../utils/CustomizedObjectTypeAdapter.java | 18 ++++- 3 files changed, 95 insertions(+), 51 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/players/PlayerQuestDatas.java b/core/src/main/java/fr/skytasul/quests/players/PlayerQuestDatas.java index 14e02308..b1a884cc 100644 --- a/core/src/main/java/fr/skytasul/quests/players/PlayerQuestDatas.java +++ b/core/src/main/java/fr/skytasul/quests/players/PlayerQuestDatas.java @@ -17,31 +17,33 @@ public class PlayerQuestDatas { protected final PlayerAccount acc; protected final int questID; - private int finished = 0; - private long timer = 0; - private int branch = -1, stage = -1; - private Map[] stageDatas = new Map[5]; - private StringJoiner questFlow = new StringJoiner(";"); + private int finished; + private long timer; + private int branch; + private int stage; + protected Map additionalDatas; + protected StringJoiner questFlow = new StringJoiner(";"); private Boolean hasDialogsCached = null; public PlayerQuestDatas(PlayerAccount acc, int questID) { this.acc = acc; this.questID = questID; + this.finished = 0; + this.timer = 0; + this.branch = -1; + this.stage = -1; + this.additionalDatas = new HashMap<>(); } - public PlayerQuestDatas(PlayerAccount acc, int questID, long timer, int finished, int branch, int stage, Map stage0datas, Map stage1datas, Map stage2datas, Map stage3datas, Map stage4datas, String questFlow) { + public PlayerQuestDatas(PlayerAccount acc, int questID, long timer, int finished, int branch, int stage, Map additionalDatas, String questFlow) { this.acc = acc; this.questID = questID; this.finished = finished; this.timer = timer; this.branch = branch; this.stage = stage; - this.stageDatas[0] = stage0datas; - this.stageDatas[1] = stage1datas; - this.stageDatas[2] = stage2datas; - this.stageDatas[3] = stage3datas; - this.stageDatas[4] = stage4datas; + this.additionalDatas = additionalDatas == null ? new HashMap<>() : additionalDatas; if (questFlow != null) this.questFlow.add(questFlow); if (branch != -1 && stage == -1) BeautyQuests.logger.warning("Incorrect quest " + questID + " datas for " + acc.debugName()); } @@ -110,12 +112,20 @@ public void setInEndingStages() { setStage(-2); } - public Map getStageDatas(int stage) { - return stageDatas[stage]; + public T getAdditionalData(String key) { + return (T) additionalDatas.get(key); + } + + public T setAdditionalData(String key, T value) { + return (T) additionalDatas.put(key, value); } - public void setStageDatas(int stage, Map stageDatas) { - this.stageDatas[stage] = stageDatas; + public Map getStageDatas(int stage) { + return getAdditionalData("stage" + stage); + } + + public void setStageDatas(int stage, Map datas) { + setAdditionalData("stage" + stage, datas); } public String getQuestFlow() { @@ -158,9 +168,7 @@ public Map serialize() { if (timer != 0) map.put("timer", timer); if (branch != -1) map.put("currentBranch", branch); if (stage != -1) map.put("currentStage", stage); - for (int i = 0; i < stageDatas.length; i++) { - if (stageDatas[i] != null) map.put("stage" + i + "datas", stageDatas[i]); - } + if (!additionalDatas.isEmpty()) map.put("datas", additionalDatas); if (questFlow.length() > 0) map.put("questFlow", questFlow.toString()); return map; @@ -173,10 +181,15 @@ public static PlayerQuestDatas deserialize(PlayerAccount acc, Map) map.get("stage" + i + "datas"); - } + if (map.containsKey("datas")) datas.additionalDatas = (Map) map.get("datas"); if (map.containsKey("questFlow")) datas.questFlow.add((String) map.get("questFlow")); + + for (int i = 0; i < 5; i++) { // TODO remove ; migration purpose ; added on 0.20 + if (map.containsKey("stage" + i + "datas")) { + datas.additionalDatas.put("stage" + i, map.get("stage" + i + "datas")); + } + } + return datas; } diff --git a/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java b/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java index 3c1acdfa..f3fd66ba 100644 --- a/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java +++ b/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java @@ -56,7 +56,7 @@ public class PlayersManagerDB extends PlayersManager { private String updateTimer; private String updateBranch; private String updateStage; - private String[] updateDatas = new String[5]; + private String updateDatas; private String updateFlow; /* Pool datas statements */ @@ -157,12 +157,6 @@ protected synchronized void removeAccount(PlayerAccount acc) { } } - private static Map extractStageDatas(ResultSet result, int index) throws SQLException { - String json = result.getString("stage_" + index + "_datas"); - if (json == null) return null; - return CustomizedObjectTypeAdapter.GSON.fromJson(json, Map.class); - } - @Override public PlayerQuestDatas createPlayerQuestDatas(PlayerAccount acc, Quest quest) { return new PlayerQuestDatasDB(acc, quest.getID()); @@ -250,9 +244,7 @@ public void load() { updateTimer = prepareDatasStatement("timer"); updateBranch = prepareDatasStatement("current_branch"); updateStage = prepareDatasStatement("current_stage"); - for (int i = 0; i < 5; i++) { - updateDatas[i] = prepareDatasStatement("stage_" + i + "_datas"); - } + updateDatas = prepareDatasStatement("additional_datas"); updateFlow = prepareDatasStatement("quest_flow"); insertPoolData = "INSERT INTO " + POOLS_DATAS_TABLE + " (`account_id`, `pool_id`) VALUES (?, ?)"; @@ -292,11 +284,7 @@ private void createTables() throws SQLException { " `timer` bigint(20) DEFAULT NULL," + " `current_branch` tinyint(4) DEFAULT NULL," + " `current_stage` tinyint(4) DEFAULT NULL," + - " `stage_0_datas` longtext DEFAULT NULL," + - " `stage_1_datas` longtext DEFAULT NULL," + - " `stage_2_datas` longtext DEFAULT NULL," + - " `stage_3_datas` longtext DEFAULT NULL," + - " `stage_4_datas` longtext DEFAULT NULL," + + " `additional_datas` longtext DEFAULT NULL," + " `quest_flow` VARCHAR(8000) DEFAULT NULL," + " PRIMARY KEY (`id`)" + ")"); @@ -317,9 +305,43 @@ private void createTables() throws SQLException { } if (columns.isEmpty()) { BeautyQuests.logger.severe("Cannot check integrity of SQL table " + QUESTS_DATAS_TABLE); - }else if (!columns.contains("quest_flow")) { - statement.execute("ALTER TABLE " + QUESTS_DATAS_TABLE + " ADD COLUMN quest_flow VARCHAR(8000) DEFAULT NULL"); - BeautyQuests.logger.info("Updated database with quest_flow column."); + }else { + if (!columns.contains("quest_flow")) { + statement.execute("ALTER TABLE " + QUESTS_DATAS_TABLE + + " ADD COLUMN quest_flow VARCHAR(8000) DEFAULT NULL"); + BeautyQuests.logger.info("Updated database with quest_flow column."); + } + if (!columns.contains("additional_datas")) { + statement.execute("ALTER TABLE " + QUESTS_DATAS_TABLE + + " ADD COLUMN `additional_datas` longtext DEFAULT NULL AFTER `current_stage`"); + BeautyQuests.logger.info("Updated database with additional_datas column."); + + BeautyQuests.logger.info("Migrating old datas..."); + PreparedStatement migration = connection.prepareStatement("UPDATE " + QUESTS_DATAS_TABLE + " SET `additional_datas` = ? WHERE `id` = ?"); + ResultSet result = statement.executeQuery("SELECT `id`, `stage_0_datas`, `stage_1_datas`, `stage_2_datas`, `stage_3_datas`, `stage_4_datas` FROM " + QUESTS_DATAS_TABLE); + while (result.next()) { + Map datas = new HashMap<>(); + for (int i = 0; i < 5; i++) { + String stageDatas = result.getString("stage_" + i + "_datas"); + if (stageDatas != null && !"{}".equals(stageDatas)) datas.put("stage" + i, stageDatas); + } + + if (datas.isEmpty()) continue; + migration.setString(1, CustomizedObjectTypeAdapter.serializeNullable(datas)); + migration.setInt(2, result.getInt("id")); + migration.addBatch(); + } + int migrated = migration.executeBatch().length; + BeautyQuests.logger.info("Migrated " + migrated + " quest datas."); + + statement.execute("ALTER TABLE " + QUESTS_DATAS_TABLE + + " DROP COLUMN `stage_0_datas`," + + " DROP COLUMN `stage_1_datas`," + + " DROP COLUMN `stage_2_datas`," + + " DROP COLUMN `stage_3_datas`," + + " DROP COLUMN `stage_4_datas`;"); + BeautyQuests.logger.info("Updated database by deleting old stage_[0::4]_datas columns."); + } } statement.execute("ALTER TABLE " + QUESTS_DATAS_TABLE + " MODIFY COLUMN finished INT(11) DEFAULT 0"); } @@ -430,11 +452,7 @@ public PlayerQuestDatasDB(PlayerAccount acc, int questID, ResultSet result) thro result.getInt("finished"), result.getInt("current_branch"), result.getInt("current_stage"), - extractStageDatas(result, 0), - extractStageDatas(result, 1), - extractStageDatas(result, 2), - extractStageDatas(result, 3), - extractStageDatas(result, 4), + CustomizedObjectTypeAdapter.deserializeNullable(result.getString("additional_datas"), Map.class), result.getString("quest_flow")); } @@ -461,11 +479,12 @@ public void setStage(int stage) { super.setStage(stage); setDataStatement(updateStage, stage, false); } - + @Override - public void setStageDatas(int stage, Map stageDatas) { - super.setStageDatas(stage, stageDatas); - setDataStatement(updateDatas[stage], stageDatas == null ? null : CustomizedObjectTypeAdapter.GSON.toJson(stageDatas), true); + public T setAdditionalData(String key, T value) { + T additionalData = super.setAdditionalData(key, value); + setDataStatement(updateDatas, super.additionalDatas.isEmpty() ? null : CustomizedObjectTypeAdapter.serializeNullable(super.additionalDatas), true); + return additionalData; } @Override diff --git a/core/src/main/java/fr/skytasul/quests/utils/CustomizedObjectTypeAdapter.java b/core/src/main/java/fr/skytasul/quests/utils/CustomizedObjectTypeAdapter.java index 13960119..5f8b0c04 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/CustomizedObjectTypeAdapter.java +++ b/core/src/main/java/fr/skytasul/quests/utils/CustomizedObjectTypeAdapter.java @@ -15,10 +15,22 @@ public class CustomizedObjectTypeAdapter extends TypeAdapter { - private static final CustomizedObjectTypeAdapter adapter = new CustomizedObjectTypeAdapter(); - public static final GsonBuilder GSON_BUILDER = new GsonBuilder().registerTypeAdapter(Map.class, adapter).registerTypeAdapter(List.class, adapter); - public static final Gson GSON = GSON_BUILDER.create(); + public static final CustomizedObjectTypeAdapter CUSTOM_ADAPTER = new CustomizedObjectTypeAdapter(); + public static final Gson GSON = new GsonBuilder() + .registerTypeAdapter(Map.class, CUSTOM_ADAPTER) + .registerTypeAdapter(List.class, CUSTOM_ADAPTER) + .create(); + public static T deserializeNullable(String json, Class classOfT) { + if (json == null) return null; + return GSON.fromJson(json, classOfT); + } + + public static String serializeNullable(Object object) { + if (object == null) return null; + return GSON.toJson(object); + } + private final TypeAdapter delegate = new Gson().getAdapter(Object.class); @Override From d65c60ecd835d06a9951fa8a597af7c3f719fd6f Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sat, 9 Jul 2022 23:57:40 +0200 Subject: [PATCH 033/111] :technologist: Quest options can now be listeners * when a QuestOption object implements the Listener interface, BeautyQuests will automatically register its handlers on quest attachment, and unregister on detach * added a data to track starting time of a quest --- .../skytasul/quests/api/options/QuestOption.java | 15 +++++++++++++++ .../quests/api/options/QuestOptionObject.java | 2 -- .../quests/options/OptionFailOnDeath.java | 16 ---------------- .../quests/players/PlayerQuestDatas.java | 10 +++++++++- .../quests/structure/BranchesManager.java | 5 ++++- .../java/fr/skytasul/quests/structure/Quest.java | 1 + 6 files changed, 29 insertions(+), 20 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/api/options/QuestOption.java b/core/src/main/java/fr/skytasul/quests/api/options/QuestOption.java index d95632a4..a6754a09 100644 --- a/core/src/main/java/fr/skytasul/quests/api/options/QuestOption.java +++ b/core/src/main/java/fr/skytasul/quests/api/options/QuestOption.java @@ -2,11 +2,16 @@ import java.util.Objects; +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.Listener; import org.bukkit.event.inventory.ClickType; import org.bukkit.inventory.ItemStack; +import fr.skytasul.quests.BeautyQuests; import fr.skytasul.quests.gui.creation.FinishGUI; import fr.skytasul.quests.structure.Quest; import fr.skytasul.quests.utils.Lang; @@ -68,11 +73,21 @@ public Quest getAttachedQuest() { } public void attach(Quest quest) { + Validate.notNull(quest, "Attached quest cannot be null"); + if (this.attachedQuest != null) throw new IllegalStateException("This option is already attached to " + attachedQuest.getID()); this.attachedQuest = quest; + + if (this instanceof Listener) { + Bukkit.getPluginManager().registerEvents((Listener) this, BeautyQuests.getInstance()); + } } public void detach() { this.attachedQuest = null; + + if (this instanceof Listener) { + HandlerList.unregisterAll((Listener) this); + } } public abstract Object save(); diff --git a/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionObject.java b/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionObject.java index 91174d71..b31de3f1 100644 --- a/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionObject.java +++ b/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionObject.java @@ -4,7 +4,6 @@ import java.util.List; import java.util.Map; -import org.apache.commons.lang.Validate; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; import org.bukkit.event.inventory.ClickType; @@ -24,7 +23,6 @@ public abstract class QuestOptionObject T getAdditionalData(String key) { } public T setAdditionalData(String key, T value) { - return (T) additionalDatas.put(key, value); + return (T) (value == null ? additionalDatas.remove(key) : additionalDatas.put(key, value)); } public Map getStageDatas(int stage) { @@ -128,6 +128,14 @@ public void setStageDatas(int stage, Map datas) { setAdditionalData("stage" + stage, datas); } + public long getStartingTime() { + return getAdditionalData("starting_time"); + } + + public void setStartingTime(long time) { + setAdditionalData("starting_time", time == 0 ? null : time); + } + public String getQuestFlow() { return questFlow.toString(); } diff --git a/core/src/main/java/fr/skytasul/quests/structure/BranchesManager.java b/core/src/main/java/fr/skytasul/quests/structure/BranchesManager.java index a0b7d652..b65fb97e 100644 --- a/core/src/main/java/fr/skytasul/quests/structure/BranchesManager.java +++ b/core/src/main/java/fr/skytasul/quests/structure/BranchesManager.java @@ -15,6 +15,7 @@ import fr.skytasul.quests.BeautyQuests; import fr.skytasul.quests.api.QuestsAPI; import fr.skytasul.quests.players.PlayerAccount; +import fr.skytasul.quests.players.PlayerQuestDatas; public class BranchesManager{ @@ -76,7 +77,9 @@ public final void objectiveUpdated(Player p, PlayerAccount acc) { } public void startPlayer(PlayerAccount acc){ - acc.getQuestDatas(getQuest()).resetQuestFlow(); + PlayerQuestDatas datas = acc.getQuestDatas(getQuest()); + datas.resetQuestFlow(); + datas.setStartingTime(System.currentTimeMillis()); branches.get(0).start(acc); } diff --git a/core/src/main/java/fr/skytasul/quests/structure/Quest.java b/core/src/main/java/fr/skytasul/quests/structure/Quest.java index 6302371f..44cf9531 100644 --- a/core/src/main/java/fr/skytasul/quests/structure/Quest.java +++ b/core/src/main/java/fr/skytasul/quests/structure/Quest.java @@ -344,6 +344,7 @@ public void finish(Player p){ manager.remove(acc); questDatas.setBranch(-1); questDatas.incrementFinished(); + questDatas.setStartingTime(0); if (hasOption(OptionQuestPool.class)) getOptionValueOrDef(OptionQuestPool.class).questCompleted(acc, Quest.this); if (isRepeatable()) { Calendar cal = Calendar.getInstance(); From f2be36c264a74d2aa6697c65dd7453072158a43b Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 10 Jul 2022 13:17:17 +0200 Subject: [PATCH 034/111] :card_file_box: Fixed SQL rows duplication and migration to new player data --- .../fr/skytasul/quests/commands/Commands.java | 2 +- .../quests/players/PlayersManagerDB.java | 22 +++++++++++++++---- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/commands/Commands.java b/core/src/main/java/fr/skytasul/quests/commands/Commands.java index 9787f445..dc0f2a4c 100644 --- a/core/src/main/java/fr/skytasul/quests/commands/Commands.java +++ b/core/src/main/java/fr/skytasul/quests/commands/Commands.java @@ -227,7 +227,7 @@ public void setStage(CommandContext cmd){ return; } } - Lang.COMMAND_SETSTAGE_SET.send(cmd.sender, branchID); + Lang.COMMAND_SETSTAGE_SET.send(cmd.sender, stageID); if (currentBranch != null) { if (datas.isInEndingStages()) { for (AbstractStage stage : currentBranch.getEndingStages().keySet()) stage.end(acc); diff --git a/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java b/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java index f3fd66ba..aa267f10 100644 --- a/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java +++ b/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java @@ -306,12 +306,13 @@ private void createTables() throws SQLException { if (columns.isEmpty()) { BeautyQuests.logger.severe("Cannot check integrity of SQL table " + QUESTS_DATAS_TABLE); }else { - if (!columns.contains("quest_flow")) { + if (!columns.contains("quest_flow")) { // 0.19 statement.execute("ALTER TABLE " + QUESTS_DATAS_TABLE + " ADD COLUMN quest_flow VARCHAR(8000) DEFAULT NULL"); BeautyQuests.logger.info("Updated database with quest_flow column."); } - if (!columns.contains("additional_datas")) { + + if (!columns.contains("additional_datas")) { // 0.20 statement.execute("ALTER TABLE " + QUESTS_DATAS_TABLE + " ADD COLUMN `additional_datas` longtext DEFAULT NULL AFTER `current_stage`"); BeautyQuests.logger.info("Updated database with additional_datas column."); @@ -320,10 +321,10 @@ private void createTables() throws SQLException { PreparedStatement migration = connection.prepareStatement("UPDATE " + QUESTS_DATAS_TABLE + " SET `additional_datas` = ? WHERE `id` = ?"); ResultSet result = statement.executeQuery("SELECT `id`, `stage_0_datas`, `stage_1_datas`, `stage_2_datas`, `stage_3_datas`, `stage_4_datas` FROM " + QUESTS_DATAS_TABLE); while (result.next()) { - Map datas = new HashMap<>(); + Map datas = new HashMap<>(); for (int i = 0; i < 5; i++) { String stageDatas = result.getString("stage_" + i + "_datas"); - if (stageDatas != null && !"{}".equals(stageDatas)) datas.put("stage" + i, stageDatas); + if (stageDatas != null && !"{}".equals(stageDatas)) datas.put("stage" + i, CustomizedObjectTypeAdapter.deserializeNullable(stageDatas, Map.class)); } if (datas.isEmpty()) continue; @@ -341,6 +342,14 @@ private void createTables() throws SQLException { + " DROP COLUMN `stage_3_datas`," + " DROP COLUMN `stage_4_datas`;"); BeautyQuests.logger.info("Updated database by deleting old stage_[0::4]_datas columns."); + + int deletedDuplicates = + statement.executeUpdate("DELETE R1 FROM " + QUESTS_DATAS_TABLE + " R1" + + " JOIN " + QUESTS_DATAS_TABLE + " R2" + + " ON R1.account_id = R2.account_id" + + " AND R1.quest_id = R2.quest_id" + + " AND R1.id < R2.id;"); + if (deletedDuplicates > 0) BeautyQuests.logger.info("Deleted " + deletedDuplicates + " duplicated rows in the " + QUESTS_DATAS_TABLE + " table."); } } statement.execute("ALTER TABLE " + QUESTS_DATAS_TABLE + " MODIFY COLUMN finished INT(11) DEFAULT 0"); @@ -438,6 +447,7 @@ public class PlayerQuestDatasDB extends PlayerQuestDatas { private Map> cachedDatas = new HashMap<>(5); private Lock datasLock = new ReentrantLock(); + private Lock dbLock = new ReentrantLock(); private boolean disabled = false; public PlayerQuestDatasDB(PlayerAccount acc, int questID) { @@ -523,6 +533,7 @@ public void run() { datasLock.unlock(); } if (entry != null) { + dbLock.lock(); try (Connection connection = db.getConnection()) { try (PreparedStatement statement = connection.prepareStatement(getQuestAccountData)) { statement.setInt(1, acc.index); @@ -532,6 +543,7 @@ public void run() { insertStatement.setInt(1, acc.index); insertStatement.setInt(2, questID); insertStatement.executeUpdate(); + DebugUtils.logMessage("Inserting DB row of quest " + questID + " for account " + acc.index); } } } @@ -546,6 +558,8 @@ public void run() { } }catch (SQLException e) { e.printStackTrace(); + }finally { + dbLock.unlock(); } } } From 3ef903b9a574a0079a20b8905741473da26a656c Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 10 Jul 2022 15:31:38 +0200 Subject: [PATCH 035/111] :sparkles: Backups are now created for player datas as well --- .../java/fr/skytasul/quests/BeautyQuests.java | 49 +++++++++++++++---- .../fr/skytasul/quests/commands/Commands.java | 6 ++- .../quests/players/PlayersManagerYAML.java | 4 ++ 3 files changed, 48 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java index b578f6a3..80f61f42 100644 --- a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java +++ b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java @@ -83,6 +83,8 @@ public class BeautyQuests extends JavaPlugin { private File dataFile; private File saveFolder; + private Path backupDir = null; + /* --------- Datas --------- */ private ScoreboardManager scoreboards; @@ -407,8 +409,9 @@ private void loadDataFile() throws LoadingException { lastVersion = data.getString("version"); if (!lastVersion.equals(getDescription().getVersion())){ logger.info("You are using a new version for the first time. (last version: " + lastVersion + ")"); - createFolderBackup(); - createDataBackup(); + backupDir = backupDir(); + createFolderBackup(backupDir); + createDataBackup(backupDir); } }else lastVersion = getDescription().getVersion(); data.options().header("Do not edit ANYTHING here."); @@ -428,9 +431,12 @@ private void loadAllDatas() throws Throwable { try{ PlayersManager.manager = db == null ? new PlayersManagerYAML() : new PlayersManagerDB(db); + + if (db == null && backupDir != null) createPlayerDatasBackup(backupDir, (PlayersManagerYAML) PlayersManager.manager); + PlayersManager.manager.load(); }catch (Exception ex) { - createDataBackup(); + if (backupDir == null) createDataBackup(backupDir()); logger.severe("Error while loading player datas.", ex); } @@ -493,7 +499,6 @@ public void saveAllConfig(boolean unload) throws Exception { try { PlayersManager.manager.save(); }catch (Exception ex) { - createDataBackup(); logger.severe("Error when saving player datas.", ex); } data.save(dataFile); @@ -516,13 +521,15 @@ private void resetDatas(){ /* ---------- Backups ---------- */ - public boolean createFolderBackup() { + public boolean createFolderBackup(Path backup) { if (!QuestsConfiguration.backups) return false; logger.info("Creating quests backup..."); - Path backupDir = backupDir(); + Path backupDir = backup.resolve("quests"); Path saveFolderPath = saveFolder.toPath(); try (Stream stream = Files.walk(saveFolderPath)) { + Files.createDirectories(backupDir); stream.forEach(path -> { + if (path.equals(saveFolderPath)) return; try { Files.copy(path, backupDir.resolve(saveFolderPath.relativize(path))); }catch (IOException ex) { @@ -537,11 +544,11 @@ public boolean createFolderBackup() { } } - public boolean createDataBackup() { + public boolean createDataBackup(Path backup) { if (!QuestsConfiguration.backups) return false; logger.info("Creating data backup..."); try{ - Path target = backupDir().resolve("data.yml"); + Path target = backup.resolve("data.yml"); if (Files.exists(target)) { logger.warning("File " + target.toString() + " already exist. This should not happen."); }else { @@ -553,6 +560,30 @@ public boolean createDataBackup() { return false; } } + + public boolean createPlayerDatasBackup(Path backup, PlayersManagerYAML yamlManager) { + if (!QuestsConfiguration.backups) return false; + + logger.info("Creating player datas backup..."); + Path backupDir = backup.resolve("players"); + Path playersFolderPath = yamlManager.getDirectory().toPath(); + try (Stream stream = Files.walk(playersFolderPath)) { + Files.createDirectories(backupDir); + stream.forEach(path -> { + if (path.equals(playersFolderPath)) return; + try { + Files.copy(path, backupDir.resolve(playersFolderPath.relativize(path))); + }catch (IOException ex) { + throw new RuntimeException(ex); + } + }); + logger.info("Player datas backup created in " + backupDir.getFileName().toString()); + return true; + }catch (Exception e) { + logger.severe("An error occured while creating the backup.", e); + return false; + } + } public boolean createQuestBackup(Path file, String msg) { if (!QuestsConfiguration.backups) return false; @@ -573,7 +604,7 @@ public boolean createQuestBackup(Path file, String msg) { private SimpleDateFormat format = new SimpleDateFormat("yyyy'-'MM'-'dd'-'hh'-'mm'-'ss"); - private Path backupDir() { + public Path backupDir() { return getDataFolder().toPath().resolve("backup-" + format.format(new Date())); } diff --git a/core/src/main/java/fr/skytasul/quests/commands/Commands.java b/core/src/main/java/fr/skytasul/quests/commands/Commands.java index dc0f2a4c..f1bcabda 100644 --- a/core/src/main/java/fr/skytasul/quests/commands/Commands.java +++ b/core/src/main/java/fr/skytasul/quests/commands/Commands.java @@ -7,6 +7,7 @@ import java.net.URL; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; +import java.nio.file.Path; import java.sql.SQLException; import java.util.ArrayList; import java.util.Optional; @@ -454,11 +455,12 @@ public void backup(CommandContext cmd){ boolean success = true; BeautyQuests.logger.info("Creating backup due to " + cmd.sender.getName() + "'s manual command."); - if (!BeautyQuests.getInstance().createFolderBackup()) { + Path backup = BeautyQuests.getInstance().backupDir(); + if (!BeautyQuests.getInstance().createFolderBackup(backup)) { Lang.BACKUP_QUESTS_FAILED.send(cmd.sender); success = false; } - if (!BeautyQuests.getInstance().createDataBackup()) { + if (!BeautyQuests.getInstance().createDataBackup(backup)) { Lang.BACKUP_PLAYERS_FAILED.send(cmd.sender); success = false; } diff --git a/core/src/main/java/fr/skytasul/quests/players/PlayersManagerYAML.java b/core/src/main/java/fr/skytasul/quests/players/PlayersManagerYAML.java index 185c6fec..37a2205b 100644 --- a/core/src/main/java/fr/skytasul/quests/players/PlayersManagerYAML.java +++ b/core/src/main/java/fr/skytasul/quests/players/PlayersManagerYAML.java @@ -43,6 +43,10 @@ public class PlayersManagerYAML extends PlayersManager { private File directory = new File(BeautyQuests.getInstance().getDataFolder(), "players"); + public File getDirectory() { + return directory; + } + @Override protected Entry load(Player player, long joinTimestamp) { String identifier = super.getIdentifier(player); From d01d683ce7f1222257aa0930c7bd82173ad66f5d Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 10 Jul 2022 19:47:05 +0200 Subject: [PATCH 036/111] :recycle: Made quest description modulable --- .../skytasul/quests/QuestsConfiguration.java | 2 +- .../options/description/QuestDescription.java | 71 ++++++++++++++++ .../description/QuestDescriptionContext.java | 27 +++++++ .../description/QuestDescriptionProvider.java | 18 +++++ .../quests/options/OptionDescription.java | 26 +++++- .../quests/options/OptionEndRewards.java | 35 +++++++- .../quests/options/OptionRequirements.java | 32 +++++++- .../fr/skytasul/quests/structure/Quest.java | 2 +- .../quests/structure/QuestDescription.java | 81 ------------------- 9 files changed, 208 insertions(+), 86 deletions(-) create mode 100644 core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescription.java create mode 100644 core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionContext.java create mode 100644 core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionProvider.java delete mode 100644 core/src/main/java/fr/skytasul/quests/structure/QuestDescription.java diff --git a/core/src/main/java/fr/skytasul/quests/QuestsConfiguration.java b/core/src/main/java/fr/skytasul/quests/QuestsConfiguration.java index 04190097..881e56d9 100644 --- a/core/src/main/java/fr/skytasul/quests/QuestsConfiguration.java +++ b/core/src/main/java/fr/skytasul/quests/QuestsConfiguration.java @@ -18,10 +18,10 @@ import com.google.common.collect.Sets; import fr.skytasul.quests.api.QuestsAPI; +import fr.skytasul.quests.api.options.description.QuestDescription; import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.gui.quests.PlayerListGUI.Category; import fr.skytasul.quests.structure.QuestBranch.Source; -import fr.skytasul.quests.structure.QuestDescription; import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.MinecraftNames; import fr.skytasul.quests.utils.ParticleEffect; diff --git a/core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescription.java b/core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescription.java new file mode 100644 index 00000000..1c776a69 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescription.java @@ -0,0 +1,71 @@ +package fr.skytasul.quests.api.options.description; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.StreamSupport; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; + +import fr.skytasul.quests.structure.Quest; + +public class QuestDescription { + + private boolean requirements; + private String requirementsValid; + private String requirementsInvalid; + + private boolean rewards; + private String rewardsFormat; + + public QuestDescription(ConfigurationSection config) { + requirements = config.getBoolean("requirements.display"); + requirementsValid = config.getString("requirements.valid"); + requirementsInvalid = config.getString("requirements.invalid"); + + rewards = config.getBoolean("rewards.display"); + rewardsFormat = config.getString("rewards.format"); + } + + public boolean showRewards() { + return rewards; + } + + public String getRewardsFormat() { + return rewardsFormat; + } + + public boolean showRequirements() { + return requirements; + } + + public String getRequirementsValid() { + return requirementsValid; + } + + public String getRequirementsInvalid() { + return requirementsInvalid; + } + + public List formatDescription(Quest quest, Player p) { + List list = new ArrayList<>(); + + QuestDescriptionContext context = new QuestDescriptionContext(this, p); + + StreamSupport + .stream(quest.spliterator(), false) + .filter(QuestDescriptionProvider.class::isInstance) + .map(QuestDescriptionProvider.class::cast) + .sorted(QuestDescriptionProvider.COMPARATOR) + .forEach(provider -> { + List description = provider.provideDescription(context); + if (description == null || description.isEmpty()) return; + + if (!list.isEmpty() && provider.prefixDescriptionWithNewLine()) list.add(""); + list.addAll(description); + }); + + return list; + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionContext.java b/core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionContext.java new file mode 100644 index 00000000..43fe4b02 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionContext.java @@ -0,0 +1,27 @@ +package fr.skytasul.quests.api.options.description; + +import org.bukkit.entity.Player; + +public class QuestDescriptionContext { + + private final QuestDescription descriptionOptions; + private final Player player; + + public QuestDescriptionContext(QuestDescription descriptionOptions, Player player) { + this.descriptionOptions = descriptionOptions; + this.player = player; + } + + public QuestDescription getDescriptionOptions() { + return descriptionOptions; + } + + public boolean hasPlayer() { + return player != null; + } + + public Player getPlayer() { + return player; + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionProvider.java b/core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionProvider.java new file mode 100644 index 00000000..3331dd40 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionProvider.java @@ -0,0 +1,18 @@ +package fr.skytasul.quests.api.options.description; + +import java.util.Comparator; +import java.util.List; + +public interface QuestDescriptionProvider { + + public static final Comparator COMPARATOR = Comparator.comparingDouble(QuestDescriptionProvider::getDescriptionPriority); + + List provideDescription(QuestDescriptionContext context); + + double getDescriptionPriority(); + + default boolean prefixDescriptionWithNewLine() { + return true; + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/options/OptionDescription.java b/core/src/main/java/fr/skytasul/quests/options/OptionDescription.java index 63c0d52d..f8d427f1 100644 --- a/core/src/main/java/fr/skytasul/quests/options/OptionDescription.java +++ b/core/src/main/java/fr/skytasul/quests/options/OptionDescription.java @@ -1,12 +1,25 @@ package fr.skytasul.quests.options; +import java.util.Arrays; +import java.util.List; + import org.bukkit.entity.Player; import fr.skytasul.quests.api.options.QuestOptionString; +import fr.skytasul.quests.api.options.description.QuestDescriptionProvider; +import fr.skytasul.quests.api.options.description.QuestDescriptionContext; import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.XMaterial; -public class OptionDescription extends QuestOptionString { +public class OptionDescription extends QuestOptionString implements QuestDescriptionProvider { + + private List cachedDescription; + + @Override + public void setValue(String value) { + super.setValue(value); + cachedDescription = null; + } @Override public void sendIndication(Player p) { @@ -33,4 +46,15 @@ public boolean isMultiline() { return true; } + @Override + public List provideDescription(QuestDescriptionContext context) { + if (cachedDescription == null) cachedDescription = Arrays.asList("§7" + getValue()); + return cachedDescription; + } + + @Override + public double getDescriptionPriority() { + return 0; + } + } diff --git a/core/src/main/java/fr/skytasul/quests/options/OptionEndRewards.java b/core/src/main/java/fr/skytasul/quests/options/OptionEndRewards.java index 53338c90..f4d6ea81 100644 --- a/core/src/main/java/fr/skytasul/quests/options/OptionEndRewards.java +++ b/core/src/main/java/fr/skytasul/quests/options/OptionEndRewards.java @@ -1,11 +1,21 @@ package fr.skytasul.quests.options; +import java.util.List; +import java.util.Objects; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + import fr.skytasul.quests.api.options.QuestOptionRewards; +import fr.skytasul.quests.api.options.description.QuestDescriptionProvider; +import fr.skytasul.quests.api.options.description.QuestDescriptionContext; import fr.skytasul.quests.api.rewards.AbstractReward; import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.Utils; import fr.skytasul.quests.utils.XMaterial; -public class OptionEndRewards extends QuestOptionRewards { +public class OptionEndRewards extends QuestOptionRewards implements QuestDescriptionProvider { + + private static final Pattern SPLIT_PATTERN = Pattern.compile("\\{JOIN\\}"); @Override protected void attachedAsyncReward(AbstractReward reward) { @@ -26,5 +36,28 @@ public String getItemName() { public String getItemDescription() { return Lang.rewardsLore.toString(); } + + @Override + public List provideDescription(QuestDescriptionContext context) { + if (!context.hasPlayer()) return null; + if (!context.getDescriptionOptions().showRewards()) return null; + + List rewards = getValue().stream() + .map(x -> x.getDescription(context.getPlayer())) + .filter(Objects::nonNull) + .flatMap(SPLIT_PATTERN::splitAsStream) + .filter(x -> !x.isEmpty()) + .map(x -> Utils.format(context.getDescriptionOptions().getRewardsFormat(), x)) + .collect(Collectors.toList()); + if (rewards.isEmpty()) return null; + + rewards.add(0, Lang.RWDTitle.toString()); + return rewards; + } + + @Override + public double getDescriptionPriority() { + return 10; + } } diff --git a/core/src/main/java/fr/skytasul/quests/options/OptionRequirements.java b/core/src/main/java/fr/skytasul/quests/options/OptionRequirements.java index a91bec3d..37e1f349 100644 --- a/core/src/main/java/fr/skytasul/quests/options/OptionRequirements.java +++ b/core/src/main/java/fr/skytasul/quests/options/OptionRequirements.java @@ -1,16 +1,22 @@ package fr.skytasul.quests.options; +import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; import fr.skytasul.quests.api.QuestsAPI; import fr.skytasul.quests.api.objects.QuestObjectsRegistry; import fr.skytasul.quests.api.options.QuestOptionObject; +import fr.skytasul.quests.api.options.description.QuestDescriptionProvider; +import fr.skytasul.quests.api.options.description.QuestDescriptionContext; import fr.skytasul.quests.api.requirements.AbstractRequirement; import fr.skytasul.quests.api.requirements.RequirementCreator; import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.Utils; import fr.skytasul.quests.utils.XMaterial; -public class OptionRequirements extends QuestOptionObject { +public class OptionRequirements extends QuestOptionObject implements QuestDescriptionProvider { @Override protected AbstractRequirement deserialize(Map map) { @@ -42,4 +48,28 @@ public String getItemDescription() { return Lang.editRequirementsLore.toString(); } + @Override + public List provideDescription(QuestDescriptionContext context) { + if (!context.hasPlayer()) return null; + if (!context.getDescriptionOptions().showRequirements()) return null; + + List requirements = getValue().stream() + .map(x -> { + String description = x.getDescription(context.getPlayer()); + if (description != null) description = Utils.format(x.test(context.getPlayer()) ? context.getDescriptionOptions().getRequirementsValid() : context.getDescriptionOptions().getRequirementsInvalid(), description); + return description; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (requirements.isEmpty()) return null; + + requirements.add(0, Lang.RDTitle.toString()); + return requirements; + } + + @Override + public double getDescriptionPriority() { + return 30; + } + } diff --git a/core/src/main/java/fr/skytasul/quests/structure/Quest.java b/core/src/main/java/fr/skytasul/quests/structure/Quest.java index 44cf9531..e6d31646 100644 --- a/core/src/main/java/fr/skytasul/quests/structure/Quest.java +++ b/core/src/main/java/fr/skytasul/quests/structure/Quest.java @@ -68,7 +68,7 @@ public void load() { @Override public Iterator iterator() { - return (Iterator) options; + return (Iterator) options.iterator(); } public D getOptionValueOrDef(Class> clazz) { diff --git a/core/src/main/java/fr/skytasul/quests/structure/QuestDescription.java b/core/src/main/java/fr/skytasul/quests/structure/QuestDescription.java deleted file mode 100644 index 7b782a5c..00000000 --- a/core/src/main/java/fr/skytasul/quests/structure/QuestDescription.java +++ /dev/null @@ -1,81 +0,0 @@ -package fr.skytasul.quests.structure; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; - -import fr.skytasul.quests.options.OptionEndRewards; -import fr.skytasul.quests.options.OptionRequirements; -import fr.skytasul.quests.utils.Lang; -import fr.skytasul.quests.utils.Utils; - -public class QuestDescription { - - private boolean requirements; - private String requirementsValid; - private String requirementsInvalid; - - private boolean rewards; - private String rewardsFormat; - - private Pattern splitPattern = Pattern.compile("\\{JOIN\\}"); - - public QuestDescription(ConfigurationSection config) { - requirements = config.getBoolean("requirements.display"); - requirementsValid = config.getString("requirements.valid"); - requirementsInvalid = config.getString("requirements.invalid"); - - rewards = config.getBoolean("rewards.display"); - rewardsFormat = config.getString("rewards.format"); - } - - public List formatDescription(Quest quest, Player p) { - List list = new ArrayList<>(); - - String desc = quest.getDescription(); - if (desc != null) list.add("§7" + desc); - - if (p == null) return list; - - if (this.rewards) { - List rewards = quest.getOptionValueOrDef(OptionEndRewards.class) - .stream() - .map(x -> x.getDescription(p)) - .filter(Objects::nonNull) - .flatMap(x -> splitPattern.splitAsStream(x)) - .filter(x -> !x.isEmpty()) - .map(x -> Utils.format(rewardsFormat, x)) - .collect(Collectors.toList()); - if (!rewards.isEmpty()) { - if (!list.isEmpty()) list.add(""); - list.add(Lang.RWDTitle.toString()); - list.addAll(rewards); - } - } - - if (this.requirements) { - List requirements = quest.getOptionValueOrDef(OptionRequirements.class) - .stream() - .map(x -> { - String description = x.getDescription(p); - if (description != null) description = Utils.format(x.test(p) ? requirementsValid : requirementsInvalid, description); - return description; - }) - .filter(Objects::nonNull) - .collect(Collectors.toList()); - if (!requirements.isEmpty()) { - if (!list.isEmpty()) list.add(""); - list.add(Lang.RDTitle.toString()); - list.addAll(requirements); - } - } - - return list; - } - -} From 0ccdb37a33769666df7d8a3791a344f60c873416 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 10 Jul 2022 22:29:02 +0200 Subject: [PATCH 037/111] :recycle: Made description more modulable! --- .../quests/api/options/QuestOption.java | 10 ++++ .../options/description/QuestDescription.java | 28 ---------- .../description/QuestDescriptionContext.java | 55 ++++++++++++++++--- .../quests/gui/quests/PlayerListGUI.java | 27 ++------- .../quests/options/OptionEndRewards.java | 8 ++- .../quests/options/OptionRepeatable.java | 28 +++++++++- .../quests/options/OptionRequirements.java | 10 ++-- .../quests/options/OptionStartable.java | 22 +++++++- .../fr/skytasul/quests/structure/Quest.java | 24 +++++++- .../java/fr/skytasul/quests/utils/Utils.java | 2 + 10 files changed, 146 insertions(+), 68 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/api/options/QuestOption.java b/core/src/main/java/fr/skytasul/quests/api/options/QuestOption.java index a6754a09..da651bb8 100644 --- a/core/src/main/java/fr/skytasul/quests/api/options/QuestOption.java +++ b/core/src/main/java/fr/skytasul/quests/api/options/QuestOption.java @@ -12,6 +12,7 @@ import org.bukkit.inventory.ItemStack; import fr.skytasul.quests.BeautyQuests; +import fr.skytasul.quests.api.options.description.QuestDescriptionProvider; import fr.skytasul.quests.gui.creation.FinishGUI; import fr.skytasul.quests.structure.Quest; import fr.skytasul.quests.utils.Lang; @@ -80,14 +81,23 @@ public void attach(Quest quest) { if (this instanceof Listener) { Bukkit.getPluginManager().registerEvents((Listener) this, BeautyQuests.getInstance()); } + + if (this instanceof QuestDescriptionProvider) { + quest.getDescriptions().add((QuestDescriptionProvider) this); + } } public void detach() { + Quest previous = this.attachedQuest; this.attachedQuest = null; if (this instanceof Listener) { HandlerList.unregisterAll((Listener) this); } + + if (previous != null && this instanceof QuestDescriptionProvider) { + previous.getDescriptions().remove((QuestDescriptionProvider) this); + } } public abstract Object save(); diff --git a/core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescription.java b/core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescription.java index 1c776a69..2154f23a 100644 --- a/core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescription.java +++ b/core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescription.java @@ -1,13 +1,6 @@ package fr.skytasul.quests.api.options.description; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.StreamSupport; - import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; - -import fr.skytasul.quests.structure.Quest; public class QuestDescription { @@ -47,25 +40,4 @@ public String getRequirementsInvalid() { return requirementsInvalid; } - public List formatDescription(Quest quest, Player p) { - List list = new ArrayList<>(); - - QuestDescriptionContext context = new QuestDescriptionContext(this, p); - - StreamSupport - .stream(quest.spliterator(), false) - .filter(QuestDescriptionProvider.class::isInstance) - .map(QuestDescriptionProvider.class::cast) - .sorted(QuestDescriptionProvider.COMPARATOR) - .forEach(provider -> { - List description = provider.provideDescription(context); - if (description == null || description.isEmpty()) return; - - if (!list.isEmpty() && provider.prefixDescriptionWithNewLine()) list.add(""); - list.addAll(description); - }); - - return list; - } - } diff --git a/core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionContext.java b/core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionContext.java index 43fe4b02..400b0b58 100644 --- a/core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionContext.java +++ b/core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionContext.java @@ -1,27 +1,66 @@ package fr.skytasul.quests.api.options.description; -import org.bukkit.entity.Player; +import java.util.ArrayList; +import java.util.List; + +import fr.skytasul.quests.gui.quests.PlayerListGUI; +import fr.skytasul.quests.gui.quests.PlayerListGUI.Category; +import fr.skytasul.quests.players.PlayerAccount; +import fr.skytasul.quests.players.PlayerQuestDatas; +import fr.skytasul.quests.structure.Quest; public class QuestDescriptionContext { private final QuestDescription descriptionOptions; - private final Player player; + private final Quest quest; + private final PlayerAccount acc; + private final Category category; + + private PlayerQuestDatas cachedDatas; - public QuestDescriptionContext(QuestDescription descriptionOptions, Player player) { + public QuestDescriptionContext(QuestDescription descriptionOptions, Quest quest, PlayerAccount acc, PlayerListGUI.Category category) { this.descriptionOptions = descriptionOptions; - this.player = player; + this.quest = quest; + this.acc = acc; + this.category = category; } public QuestDescription getDescriptionOptions() { return descriptionOptions; } - public boolean hasPlayer() { - return player != null; + public Quest getQuest() { + return quest; + } + + public PlayerAccount getPlayerAccount() { + return acc; + } + + public Category getCategory() { + return category; + } + + public PlayerQuestDatas getQuestDatas() { + if (cachedDatas == null) cachedDatas = acc.getQuestDatasIfPresent(quest); + return cachedDatas; } - public Player getPlayer() { - return player; + public List formatDescription() { + List list = new ArrayList<>(); + + quest.getDescriptions() + .stream() + .sorted(QuestDescriptionProvider.COMPARATOR) + .forEach(provider -> { + List description = provider.provideDescription(this); + if (description == null || description.isEmpty()) return; + + if (!list.isEmpty() && provider.prefixDescriptionWithNewLine()) list.add(""); + list.addAll(description); + }); + + return list; } } diff --git a/core/src/main/java/fr/skytasul/quests/gui/quests/PlayerListGUI.java b/core/src/main/java/fr/skytasul/quests/gui/quests/PlayerListGUI.java index 4b141d26..bd54be37 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/quests/PlayerListGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/quests/PlayerListGUI.java @@ -1,6 +1,5 @@ package fr.skytasul.quests.gui.quests; -import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -16,6 +15,7 @@ import fr.skytasul.quests.BeautyQuests; import fr.skytasul.quests.QuestsConfiguration; import fr.skytasul.quests.api.QuestsAPI; +import fr.skytasul.quests.api.options.description.QuestDescriptionContext; import fr.skytasul.quests.gui.CustomInventory; import fr.skytasul.quests.gui.Inventories; import fr.skytasul.quests.gui.ItemUtils; @@ -23,7 +23,6 @@ import fr.skytasul.quests.options.OptionStartable; import fr.skytasul.quests.players.PlayerAccount; import fr.skytasul.quests.structure.Quest; -import fr.skytasul.quests.structure.QuestBranch.Source; import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.Utils; import fr.skytasul.quests.utils.XMaterial; @@ -100,16 +99,7 @@ private void setItems(){ for (int i = page * 35; i < quests.size(); i++){ if (i == (page + 1) * 35) break; Quest qu = quests.get(i); - List lore = new ArrayList<>(4); - if (qu.isRepeatable()){ - if (qu.testTimer(acc, false)) { - lore.add(Lang.canRedo.toString()); - }else { - lore.add(Lang.timeWait.format(qu.getTimeLeft(acc))); - } - lore.add(null); - lore.add(Lang.timesFinished.format(acc.getQuestDatas(qu).getTimesFinished())); - } + List lore = new QuestDescriptionContext(QuestsConfiguration.getQuestDescription(), qu, acc, cat).formatDescription(); if (QuestsConfiguration.getDialogsConfig().isHistoryEnabled() && acc.getQuestDatas(qu).hasFlowDialogs()) { if (!lore.isEmpty()) lore.add(null); lore.add("§8" + Lang.ClickRight + " §8> " + Lang.dialogsHistoryLore); @@ -125,9 +115,8 @@ private void setItems(){ Quest qu = quests.get(i); ItemStack item; try { - String desc = qu.getDescriptionLine(acc, Source.MENU); - List lore = new ArrayList<>(4); - if (desc != null && !desc.isEmpty()) lore.add(desc); + List lore = new QuestDescriptionContext(QuestsConfiguration.getQuestDescription(), qu, acc, cat).formatDescription(); + boolean hasDialogs = QuestsConfiguration.getDialogsConfig().isHistoryEnabled() && acc.getQuestDatas(qu).hasFlowDialogs(); boolean cancellable = QuestsConfiguration.getMenuConfig().allowPlayerCancelQuest() && qu.isCancellable(); if (cancellable || hasDialogs) { @@ -149,13 +138,7 @@ private void setItems(){ for (int i = page * 35; i < quests.size(); i++){ if (i == (page + 1) * 35) break; Quest qu = quests.get(i); - List lore = new ArrayList<>(5); - lore.addAll(QuestsConfiguration.getQuestDescription().formatDescription(qu, acc.getPlayer())); - if (qu.getOptionValueOrDef(OptionStartable.class) && acc.isCurrent()) { - lore.add(""); - lore.add(qu.isLauncheable(acc.getPlayer(), acc, false) ? Lang.startLore.toString() : Lang.startImpossibleLore.toString()); - } - setMainItem(i - page * 35, createQuestItem(qu, lore)); + setMainItem(i - page * 35, createQuestItem(qu, new QuestDescriptionContext(QuestsConfiguration.getQuestDescription(), qu, acc, cat).formatDescription())); } break; diff --git a/core/src/main/java/fr/skytasul/quests/options/OptionEndRewards.java b/core/src/main/java/fr/skytasul/quests/options/OptionEndRewards.java index f4d6ea81..f29843bd 100644 --- a/core/src/main/java/fr/skytasul/quests/options/OptionEndRewards.java +++ b/core/src/main/java/fr/skytasul/quests/options/OptionEndRewards.java @@ -6,9 +6,10 @@ import java.util.stream.Collectors; import fr.skytasul.quests.api.options.QuestOptionRewards; -import fr.skytasul.quests.api.options.description.QuestDescriptionProvider; import fr.skytasul.quests.api.options.description.QuestDescriptionContext; +import fr.skytasul.quests.api.options.description.QuestDescriptionProvider; import fr.skytasul.quests.api.rewards.AbstractReward; +import fr.skytasul.quests.gui.quests.PlayerListGUI.Category; import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.Utils; import fr.skytasul.quests.utils.XMaterial; @@ -39,11 +40,12 @@ public String getItemDescription() { @Override public List provideDescription(QuestDescriptionContext context) { - if (!context.hasPlayer()) return null; + if (!context.getPlayerAccount().isCurrent()) return null; if (!context.getDescriptionOptions().showRewards()) return null; + if (context.getCategory() == Category.FINISHED) return null; List rewards = getValue().stream() - .map(x -> x.getDescription(context.getPlayer())) + .map(x -> x.getDescription(context.getPlayerAccount().getPlayer())) .filter(Objects::nonNull) .flatMap(SPLIT_PATTERN::splitAsStream) .filter(x -> !x.isEmpty()) diff --git a/core/src/main/java/fr/skytasul/quests/options/OptionRepeatable.java b/core/src/main/java/fr/skytasul/quests/options/OptionRepeatable.java index 24c5f234..f08b8860 100644 --- a/core/src/main/java/fr/skytasul/quests/options/OptionRepeatable.java +++ b/core/src/main/java/fr/skytasul/quests/options/OptionRepeatable.java @@ -1,9 +1,15 @@ package fr.skytasul.quests.options; +import java.util.ArrayList; +import java.util.List; + import fr.skytasul.quests.api.options.QuestOptionBoolean; +import fr.skytasul.quests.api.options.description.QuestDescriptionContext; +import fr.skytasul.quests.api.options.description.QuestDescriptionProvider; +import fr.skytasul.quests.gui.quests.PlayerListGUI.Category; import fr.skytasul.quests.utils.Lang; -public class OptionRepeatable extends QuestOptionBoolean { +public class OptionRepeatable extends QuestOptionBoolean implements QuestDescriptionProvider { @Override public String getName() { @@ -15,4 +21,24 @@ public String getDescription() { return Lang.multipleLore.toString(); } + @Override + public List provideDescription(QuestDescriptionContext context) { + if (context.getCategory() != Category.FINISHED) return null; + + List lore = new ArrayList<>(4); + if (context.getQuest().testTimer(context.getPlayerAccount(), false)) { + lore.add(Lang.canRedo.toString()); + }else { + lore.add(Lang.timeWait.format(context.getQuest().getTimeLeft(context.getPlayerAccount()))); + } + lore.add(null); + lore.add(Lang.timesFinished.format(context.getQuestDatas().getTimesFinished())); + return lore; + } + + @Override + public double getDescriptionPriority() { + return 100; + } + } diff --git a/core/src/main/java/fr/skytasul/quests/options/OptionRequirements.java b/core/src/main/java/fr/skytasul/quests/options/OptionRequirements.java index 37e1f349..ecbaa9db 100644 --- a/core/src/main/java/fr/skytasul/quests/options/OptionRequirements.java +++ b/core/src/main/java/fr/skytasul/quests/options/OptionRequirements.java @@ -8,10 +8,11 @@ import fr.skytasul.quests.api.QuestsAPI; import fr.skytasul.quests.api.objects.QuestObjectsRegistry; import fr.skytasul.quests.api.options.QuestOptionObject; -import fr.skytasul.quests.api.options.description.QuestDescriptionProvider; import fr.skytasul.quests.api.options.description.QuestDescriptionContext; +import fr.skytasul.quests.api.options.description.QuestDescriptionProvider; import fr.skytasul.quests.api.requirements.AbstractRequirement; import fr.skytasul.quests.api.requirements.RequirementCreator; +import fr.skytasul.quests.gui.quests.PlayerListGUI.Category; import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.Utils; import fr.skytasul.quests.utils.XMaterial; @@ -50,13 +51,14 @@ public String getItemDescription() { @Override public List provideDescription(QuestDescriptionContext context) { - if (!context.hasPlayer()) return null; + if (!context.getPlayerAccount().isCurrent()) return null; if (!context.getDescriptionOptions().showRequirements()) return null; + if (context.getCategory() != Category.NOT_STARTED) return null; List requirements = getValue().stream() .map(x -> { - String description = x.getDescription(context.getPlayer()); - if (description != null) description = Utils.format(x.test(context.getPlayer()) ? context.getDescriptionOptions().getRequirementsValid() : context.getDescriptionOptions().getRequirementsInvalid(), description); + String description = x.getDescription(context.getPlayerAccount().getPlayer()); + if (description != null) description = Utils.format(x.test(context.getPlayerAccount().getPlayer()) ? context.getDescriptionOptions().getRequirementsValid() : context.getDescriptionOptions().getRequirementsInvalid(), description); return description; }) .filter(Objects::nonNull) diff --git a/core/src/main/java/fr/skytasul/quests/options/OptionStartable.java b/core/src/main/java/fr/skytasul/quests/options/OptionStartable.java index bd434a6a..089b7ba3 100644 --- a/core/src/main/java/fr/skytasul/quests/options/OptionStartable.java +++ b/core/src/main/java/fr/skytasul/quests/options/OptionStartable.java @@ -1,9 +1,18 @@ package fr.skytasul.quests.options; +import java.util.Arrays; +import java.util.List; + import fr.skytasul.quests.api.options.QuestOptionBoolean; +import fr.skytasul.quests.api.options.description.QuestDescriptionContext; +import fr.skytasul.quests.api.options.description.QuestDescriptionProvider; +import fr.skytasul.quests.gui.quests.PlayerListGUI.Category; import fr.skytasul.quests.utils.Lang; -public class OptionStartable extends QuestOptionBoolean { +public class OptionStartable extends QuestOptionBoolean implements QuestDescriptionProvider { + + private static final List STARTABLE = Arrays.asList(Lang.startLore.toString()); + private static final List NOT_STARTABLE = Arrays.asList(Lang.startImpossibleLore.toString()); @Override public String getName() { @@ -15,4 +24,15 @@ public String getDescription() { return Lang.startableFromGUILore.toString(); } + @Override + public List provideDescription(QuestDescriptionContext context) { + if (context.getCategory() != Category.NOT_STARTED || !context.getPlayerAccount().isCurrent()) return null; + return context.getQuest().isLauncheable(context.getPlayerAccount().getPlayer(), context.getPlayerAccount(), false) ? STARTABLE : NOT_STARTABLE; + } + + @Override + public double getDescriptionPriority() { + return 100; + } + } diff --git a/core/src/main/java/fr/skytasul/quests/structure/Quest.java b/core/src/main/java/fr/skytasul/quests/structure/Quest.java index e6d31646..a2ad56e3 100644 --- a/core/src/main/java/fr/skytasul/quests/structure/Quest.java +++ b/core/src/main/java/fr/skytasul/quests/structure/Quest.java @@ -4,6 +4,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.ArrayList; +import java.util.Arrays; import java.util.Calendar; import java.util.Iterator; import java.util.List; @@ -25,10 +26,13 @@ import fr.skytasul.quests.api.options.OptionSet; import fr.skytasul.quests.api.options.QuestOption; import fr.skytasul.quests.api.options.QuestOptionCreator; +import fr.skytasul.quests.api.options.description.QuestDescriptionContext; +import fr.skytasul.quests.api.options.description.QuestDescriptionProvider; import fr.skytasul.quests.api.requirements.AbstractRequirement; import fr.skytasul.quests.api.requirements.Actionnable; import fr.skytasul.quests.gui.Inventories; import fr.skytasul.quests.gui.misc.ConfirmGUI; +import fr.skytasul.quests.gui.quests.PlayerListGUI.Category; import fr.skytasul.quests.options.*; import fr.skytasul.quests.players.AdminMode; import fr.skytasul.quests.players.PlayerAccount; @@ -40,13 +44,14 @@ import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.Utils; -public class Quest implements Comparable, OptionSet { +public class Quest implements Comparable, OptionSet, QuestDescriptionProvider { private final int id; private final File file; private BranchesManager manager; private List> options = new ArrayList<>(); + private List descriptions = new ArrayList<>(); private boolean removed = false; public boolean asyncEnd = false; @@ -60,12 +65,17 @@ public Quest(int id, File file) { this.id = id; this.file = file; this.manager = new BranchesManager(this); + this.descriptions.add(this); } public void load() { QuestsAPI.propagateQuestsHandlers(handler -> handler.questLoaded(this)); } + public List getDescriptions() { + return descriptions; + } + @Override public Iterator iterator() { return (Iterator) options.iterator(); @@ -271,6 +281,18 @@ public String getDescriptionLine(PlayerAccount acc, Source source) { return branch.getDescriptionLine(acc, source); } + @Override + public List provideDescription(QuestDescriptionContext context) { + if (!context.getPlayerAccount().isCurrent()) return null; + if (context.getCategory() != Category.IN_PROGRESS) return null; + return Arrays.asList(getDescriptionLine(context.getPlayerAccount(), Source.MENU)); + } + + @Override + public double getDescriptionPriority() { + return 15; + } + public void attemptStart(Player p, Runnable atStart) { String confirm; if (QuestsConfiguration.questConfirmGUI() && !"none".equals(confirm = getOptionValueOrDef(OptionConfirmMessage.class))) { diff --git a/core/src/main/java/fr/skytasul/quests/utils/Utils.java b/core/src/main/java/fr/skytasul/quests/utils/Utils.java index e9f2ab4e..57230c7d 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Utils.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Utils.java @@ -109,6 +109,8 @@ public static String ticksToElapsedTime(int ticks) { } public static String millisToHumanString(long time) { + if (time == 0) return "x"; + StringBuilder sb = new StringBuilder(); long weeks = time / 604_800_000; From 230c630dc7062d209e855b003ca2a05d633be123 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Tue, 12 Jul 2022 16:27:21 +0200 Subject: [PATCH 038/111] :sparkles: Added ability to remove the pool from a quest --- .../java/fr/skytasul/quests/BeautyQuests.java | 13 ++-- .../quests/options/OptionQuestPool.java | 61 ++++++++++++------- 2 files changed, 48 insertions(+), 26 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java index 80f61f42..21de2465 100644 --- a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java +++ b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java @@ -99,6 +99,7 @@ public class BeautyQuests extends JavaPlugin { public static boolean loaded = false; public DependenciesManager dependencies = new DependenciesManager(); + private CommandsManager command; private LoggerHandler loggerHandler; @@ -235,7 +236,7 @@ private void checkPaper() { } private void registerCommands(){ - CommandsManager questCommand = new CommandsManager((sender) -> { + command = new CommandsManager((sender) -> { if (!(sender instanceof Player)) return; Player p = (Player) sender; if (!p.hasPermission("beautyquests.command.listPlayer")){ @@ -250,9 +251,9 @@ private void registerCommands(){ }); PluginCommand cmd = getCommand("beautyquests"); cmd.setPermission("beautyquests.command"); - cmd.setExecutor(questCommand); - cmd.setTabCompleter(questCommand); - questCommand.registerCommandsClass(new Commands()); + cmd.setExecutor(command); + cmd.setTabCompleter(command); + command.registerCommandsClass(new Commands()); } private void launchSaveCycle(){ @@ -646,6 +647,10 @@ public void run() { }.runTaskLater(BeautyQuests.getInstance(), 20L); } + public CommandsManager getCommand() { + return command; + } + public QuestsConfiguration getConfiguration() { return config; } diff --git a/core/src/main/java/fr/skytasul/quests/options/OptionQuestPool.java b/core/src/main/java/fr/skytasul/quests/options/OptionQuestPool.java index 594f57ec..d4b2e6cd 100644 --- a/core/src/main/java/fr/skytasul/quests/options/OptionQuestPool.java +++ b/core/src/main/java/fr/skytasul/quests/options/OptionQuestPool.java @@ -1,5 +1,8 @@ package fr.skytasul.quests.options; +import java.util.ArrayList; +import java.util.List; + import org.bukkit.Bukkit; import org.bukkit.DyeColor; import org.bukkit.configuration.ConfigurationSection; @@ -48,8 +51,16 @@ public QuestPool cloneValue(QuestPool value) { return value; } - private String[] getLore() { - return new String[] { formatDescription(Lang.questPoolLore.toString()), "", formatValue(getValue() == null ? null : "#" + getValue().getID()) }; + private List getLore() { + List lore = new ArrayList<>(5); + lore.add(formatDescription(Lang.questPoolLore.toString())); + lore.add(""); + lore.add(formatValue(getValue() == null ? null : "#" + getValue().getID())); + if (hasCustomValue()) { + lore.add(""); + lore.add("§8" + Lang.ClickShiftRight.toString() + " > §d" + Lang.Reset.toString()); + } + return lore; } @Override @@ -59,26 +70,32 @@ public ItemStack getItemStack(OptionSet options) { @Override public void click(FinishGUI gui, Player p, ItemStack item, int slot, ClickType click) { - new PagedGUI(Lang.INVENTORY_POOLS_LIST.toString(), DyeColor.CYAN, BeautyQuests.getInstance().getPoolsManager().getPools(), list -> gui.reopen(p), null) { - - @Override - public ItemStack getItemStack(QuestPool object) { - return object.getItemStack(Lang.poolChoose.toString()); - } - - @Override - public void click(QuestPool existing, ItemStack poolItem, ClickType click) { - setValue(existing); - ItemUtils.lore(item, getLore()); - gui.reopen(p); - } - - @Override - public CloseBehavior onClose(Player p, Inventory inv) { - Bukkit.getScheduler().runTask(BeautyQuests.getInstance(), () -> gui.reopen(p)); - return CloseBehavior.NOTHING; - } - }.create(p); + if (click == ClickType.SHIFT_RIGHT) { + setValue(null); + ItemUtils.lore(item, getLore()); + gui.reopen(p); + }else { + new PagedGUI(Lang.INVENTORY_POOLS_LIST.toString(), DyeColor.CYAN, BeautyQuests.getInstance().getPoolsManager().getPools(), list -> gui.reopen(p), null) { + + @Override + public ItemStack getItemStack(QuestPool object) { + return object.getItemStack(Lang.poolChoose.toString()); + } + + @Override + public void click(QuestPool existing, ItemStack poolItem, ClickType click) { + setValue(existing); + ItemUtils.lore(item, getLore()); + gui.reopen(p); + } + + @Override + public CloseBehavior onClose(Player p, Inventory inv) { + Bukkit.getScheduler().runTask(BeautyQuests.getInstance(), () -> gui.reopen(p)); + return CloseBehavior.NOTHING; + } + }.create(p); + } } } From 2a08b86e5e188e55927ed8ce38b8e62654e19d60 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Thu, 21 Jul 2022 11:49:00 +0200 Subject: [PATCH 039/111] :technologist: Added subhandlers for custom logger --- .../fr/skytasul/quests/utils/DebugUtils.java | 2 +- .../quests/utils/logger/ILoggerHandler.java | 14 +++++- .../quests/utils/logger/LoggerHandler.java | 44 ++++++++++++++++--- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/utils/DebugUtils.java b/core/src/main/java/fr/skytasul/quests/utils/DebugUtils.java index e23601f8..3ed9de98 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/DebugUtils.java +++ b/core/src/main/java/fr/skytasul/quests/utils/DebugUtils.java @@ -12,7 +12,7 @@ private DebugUtils() {} private static Map errors = new HashMap<>(); public static void logMessage(String msg){ - BeautyQuests.getInstance().getLoggerHandler().write("[DEBUG]: " + msg); + BeautyQuests.getInstance().getLoggerHandler().write(msg, "DEBUG"); } public static String stackTraces(int from, int to){ diff --git a/core/src/main/java/fr/skytasul/quests/utils/logger/ILoggerHandler.java b/core/src/main/java/fr/skytasul/quests/utils/logger/ILoggerHandler.java index 1dd3e497..25889652 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/logger/ILoggerHandler.java +++ b/core/src/main/java/fr/skytasul/quests/utils/logger/ILoggerHandler.java @@ -1,17 +1,27 @@ package fr.skytasul.quests.utils.logger; +import java.util.logging.Handler; + public interface ILoggerHandler { public static final ILoggerHandler EMPTY_LOGGER = new LoggerHandlerEmpty(); - void write(String msg); + void write(String msg, String... prefixes); + + Handler getSubhandler(String prefix); class LoggerHandlerEmpty implements ILoggerHandler { private LoggerHandlerEmpty() {} @Override - public void write(String msg) {} + public void write(String msg, String... prefixes) {} + + @Override + public Handler getSubhandler(String prefix) { + return null; + } + } } \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/utils/logger/LoggerHandler.java b/core/src/main/java/fr/skytasul/quests/utils/logger/LoggerHandler.java index b1ab086f..b4493c41 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/logger/LoggerHandler.java +++ b/core/src/main/java/fr/skytasul/quests/utils/logger/LoggerHandler.java @@ -60,9 +60,13 @@ public boolean isEnabled() { @Override public void publish(LogRecord logRecord) { + log(logRecord, null); + } + + private void log(LogRecord logRecord, String prefix) { try { if (logRecord != null) { - write("[" + (logRecord.getLevel() == null ? "NONE" : logRecord.getLevel().getName()) + "]: " + getFormatter().format(logRecord)); + write(getFormatter().format(logRecord), prefix, logRecord.getLevel() == null ? "NONE" : logRecord.getLevel().getName()); if (logRecord.getThrown() != null) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); @@ -72,9 +76,9 @@ public void publish(LogRecord logRecord) { int index = errors.indexOf(throwable); if (index == -1) { index = errors.size(); - write("[ERROR] new #" + index + ": " + throwable); + write("new #" + index + ": " + throwable, "ERROR", prefix); errors.add(throwable); - }else write("[ERROR] existing #" + index); + }else write("existing #" + index, "ERROR", prefix); } } }catch (Exception ex) { @@ -83,10 +87,14 @@ public void publish(LogRecord logRecord) { } @Override - public void write(String msg){ + public synchronized void write(String msg, String... prefixes) { if (!isEnabled()) return; date.setTime(System.currentTimeMillis()); - stream.println(format.format(date) + msg); + stream.print(format.format(date)); + for (String prefix : prefixes) { + if (prefix != null && !prefix.isEmpty()) stream.print("[" + prefix + "] "); + } + stream.println(msg); something = true; } @@ -124,4 +132,30 @@ public void flush() { run.run(); } + @Override + public Handler getSubhandler(String prefix) { + return new Subhandler(prefix); + } + + private class Subhandler extends Handler { + + private String prefix; + + private Subhandler(String prefix) { + this.prefix = prefix; + } + + @Override + public void publish(LogRecord logRecord) { + log(logRecord, prefix); + } + + @Override + public void flush() {} + + @Override + public void close() throws SecurityException {} + + } + } From fde27167b9ceee44e8fb33eb52dbe6ca50e8fbff Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Thu, 21 Jul 2022 12:03:08 +0200 Subject: [PATCH 040/111] :children_crossing: Made some list GUIs sorted by names --- .../skytasul/quests/editors/checkers/ColorParser.java | 2 +- .../quests/editors/checkers/MaterialParser.java | 10 +++++++++- .../fr/skytasul/quests/gui/blocks/SelectBlockGUI.java | 2 +- .../fr/skytasul/quests/gui/misc/DamageCausesGUI.java | 4 ++-- .../fr/skytasul/quests/gui/misc/ItemCreatorGUI.java | 2 +- .../fr/skytasul/quests/gui/mobs/EntityTypeGUI.java | 6 +++++- .../skytasul/quests/gui/particles/ParticleListGUI.java | 2 +- .../fr/skytasul/quests/options/OptionQuestItem.java | 2 +- 8 files changed, 21 insertions(+), 9 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/editors/checkers/ColorParser.java b/core/src/main/java/fr/skytasul/quests/editors/checkers/ColorParser.java index 24389f71..18c877c0 100644 --- a/core/src/main/java/fr/skytasul/quests/editors/checkers/ColorParser.java +++ b/core/src/main/java/fr/skytasul/quests/editors/checkers/ColorParser.java @@ -41,7 +41,7 @@ public Color parse(Player p, String msg) throws Throwable { red = awtColor.getRed(); green = awtColor.getGreen(); blue = awtColor.getBlue(); - }catch (IllegalArgumentException ex) { + }catch (IllegalArgumentException | NullPointerException ex) { Lang.INVALID_COLOR.send(p); return null; } diff --git a/core/src/main/java/fr/skytasul/quests/editors/checkers/MaterialParser.java b/core/src/main/java/fr/skytasul/quests/editors/checkers/MaterialParser.java index 65830cf1..7d9fa47c 100644 --- a/core/src/main/java/fr/skytasul/quests/editors/checkers/MaterialParser.java +++ b/core/src/main/java/fr/skytasul/quests/editors/checkers/MaterialParser.java @@ -10,13 +10,19 @@ public class MaterialParser implements AbstractParser { + public static final MaterialParser ITEM_PARSER = new MaterialParser(true, false); + public static final MaterialParser BLOCK_PARSER = new MaterialParser(false, true); + public static final MaterialParser ANY_PARSER = new MaterialParser(false, false); + private boolean item, block; + @Deprecated public MaterialParser(boolean item, boolean block) { this.item = item; this.block = block; } + @Override public XMaterial parse(Player p, String msg) throws Throwable { XMaterial tmp = XMaterial.matchXMaterial(msg).orElse(null); if (tmp == null){ @@ -26,8 +32,10 @@ public XMaterial parse(Player p, String msg) throws Throwable { if (block) { Lang.UNKNOWN_BLOCK_TYPE.send(p); }else Lang.UNKNOWN_ITEM_TYPE.send(p); + return null; } - }else if (item) { + } + if (item) { if (NMS.getMCVersion() >= 13 && !Post1_13.isItem(tmp.parseMaterial())) { Lang.INVALID_ITEM_TYPE.send(p); return null; diff --git a/core/src/main/java/fr/skytasul/quests/gui/blocks/SelectBlockGUI.java b/core/src/main/java/fr/skytasul/quests/gui/blocks/SelectBlockGUI.java index 0fa11617..07b35ff3 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/blocks/SelectBlockGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/blocks/SelectBlockGUI.java @@ -126,7 +126,7 @@ public boolean onClick(Player p, Inventory inv, ItemStack current, int slot, Cli resetTag(); updateTypeItem(); openLastInv(p); - }, new MaterialParser(false, true)).enter(); + }, MaterialParser.BLOCK_PARSER).enter(); break; case DATA_SLOT: diff --git a/core/src/main/java/fr/skytasul/quests/gui/misc/DamageCausesGUI.java b/core/src/main/java/fr/skytasul/quests/gui/misc/DamageCausesGUI.java index f2abf747..f0035a23 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/misc/DamageCausesGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/misc/DamageCausesGUI.java @@ -58,7 +58,7 @@ public ItemStack getObjectItemStack(DamageCause object) { @Override public void createObject(Function callback) { - new PagedGUI(Lang.INVENTORY_DAMAGE_CAUSES_LIST.toString(), DyeColor.ORANGE, MAPPED_ITEMS.keySet()) { + new PagedGUI(Lang.INVENTORY_DAMAGE_CAUSES_LIST.toString(), DyeColor.ORANGE, MAPPED_ITEMS.keySet(), null, DamageCause::name) { @Override public ItemStack getItemStack(DamageCause object) { @@ -70,7 +70,7 @@ public void click(DamageCause existing, ItemStack item, ClickType clickType) { callback.apply(existing); } - }.create(p); + }.sortValues(DamageCause::name).create(p); } } diff --git a/core/src/main/java/fr/skytasul/quests/gui/misc/ItemCreatorGUI.java b/core/src/main/java/fr/skytasul/quests/gui/misc/ItemCreatorGUI.java index de1a1921..0246a7d9 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/misc/ItemCreatorGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/misc/ItemCreatorGUI.java @@ -82,7 +82,7 @@ public boolean onClick(Player p, Inventory inv, ItemStack current, int slot, Cli new TextEditor<>(p, () -> reopen(), obj -> { type = obj; reopen(); - }, new MaterialParser(true, false)).enter(); + }, MaterialParser.ITEM_PARSER).enter(); break; case 1: diff --git a/core/src/main/java/fr/skytasul/quests/gui/mobs/EntityTypeGUI.java b/core/src/main/java/fr/skytasul/quests/gui/mobs/EntityTypeGUI.java index 3e38e6b7..5be127a7 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/mobs/EntityTypeGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/mobs/EntityTypeGUI.java @@ -38,7 +38,11 @@ public class EntityTypeGUI extends PagedGUI{ private Consumer run; public EntityTypeGUI(Consumer run, Predicate typeFilter) { - super(Lang.INVENTORY_TYPE.toString(), DyeColor.PURPLE, entities.keySet().stream().filter(typeFilter).collect(Collectors.toList()), null, EntityTypeGUI::getName); + super(Lang.INVENTORY_TYPE.toString(), DyeColor.PURPLE, entities + .keySet() + .stream() + .filter(typeFilter == null ? __ -> true : typeFilter) + .collect(Collectors.toList()), null, EntityTypeGUI::getName); sortValues(EntityType::getName); this.run = run; } diff --git a/core/src/main/java/fr/skytasul/quests/gui/particles/ParticleListGUI.java b/core/src/main/java/fr/skytasul/quests/gui/particles/ParticleListGUI.java index b68ccf86..29114a95 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/particles/ParticleListGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/particles/ParticleListGUI.java @@ -21,7 +21,7 @@ public class ParticleListGUI extends PagedGUI { private Consumer end; public ParticleListGUI(Consumer end) { - super(Lang.INVENTORY_PARTICLE_LIST.toString(), DyeColor.MAGENTA, ParticleEffectGUI.PARTICLES); + super(Lang.INVENTORY_PARTICLE_LIST.toString(), DyeColor.MAGENTA, ParticleEffectGUI.PARTICLES, null, Particle::name); this.end = end; } diff --git a/core/src/main/java/fr/skytasul/quests/options/OptionQuestItem.java b/core/src/main/java/fr/skytasul/quests/options/OptionQuestItem.java index 95f0e2d1..bee8df4f 100644 --- a/core/src/main/java/fr/skytasul/quests/options/OptionQuestItem.java +++ b/core/src/main/java/fr/skytasul/quests/options/OptionQuestItem.java @@ -72,7 +72,7 @@ public void click(FinishGUI gui, Player p, ItemStack item, int slot, ClickType c gui.inv.setItem(slot, ItemUtils.nameAndLore(getValue().clone(), Lang.customMaterial.toString(), getLore())); } gui.reopen(p); - }, new MaterialParser(false, false)).passNullIntoEndConsumer().enter(); + }, MaterialParser.ANY_PARSER).passNullIntoEndConsumer().enter(); } @Override From a3f0e9ce8161fb9e317d43dc517c53940d235bee Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 24 Jul 2022 16:21:57 +0200 Subject: [PATCH 041/111] :sparkles: Allow shift clicks for NPCs --- .../skytasul/quests/QuestsConfiguration.java | 31 ++++++++++++++----- .../fr/skytasul/quests/QuestsListener.java | 2 +- .../quests/gui/quests/PlayerListGUI.java | 2 +- .../quests/gui/quests/QuestsListGUI.java | 2 +- .../quests/scoreboards/Scoreboard.java | 4 +-- .../fr/skytasul/quests/stages/StageNPC.java | 2 +- .../quests/structure/QuestsManager.java | 5 +-- .../compatibility/QuestsPlaceholders.java | 2 +- .../utils/compatibility/npcs/BQCitizens.java | 4 +-- .../compatibility/npcs/BQServerNPCs.java | 2 +- core/src/main/resources/config.yml | 7 +++-- 11 files changed, 40 insertions(+), 23 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/QuestsConfiguration.java b/core/src/main/java/fr/skytasul/quests/QuestsConfiguration.java index 881e56d9..e423e041 100644 --- a/core/src/main/java/fr/skytasul/quests/QuestsConfiguration.java +++ b/core/src/main/java/fr/skytasul/quests/QuestsConfiguration.java @@ -1,6 +1,8 @@ package fr.skytasul.quests; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -60,7 +62,7 @@ public class QuestsConfiguration { private static boolean sendUpdate = true; private static boolean stageStart = true; private static boolean questConfirmGUI = false; - private static ClickType npcClick = ClickType.RIGHT; + private static Collection npcClicks = Arrays.asList(ClickType.RIGHT, ClickType.SHIFT_RIGHT); private static String dSetName = "Quests"; private static String dIcon = "bookshelf"; private static int dMinZoom = 0; @@ -166,9 +168,18 @@ void init() { mobsProgressBar = config.getBoolean("mobsProgressBar"); progressBarTimeoutSeconds = config.getInt("progressBarTimeoutSeconds"); try { - npcClick = ClickType.valueOf(config.getString("npcClick").toUpperCase()); + if (config.isString("npcClick")) { + String click = config.getString("npcClick"); + npcClicks = Arrays.asList(click.equals("ANY") ? ClickType.values() : new ClickType[] { ClickType.valueOf(click.toUpperCase()) }); + }else { + npcClicks = config.getStringList("npcClick") + .stream() + .map(String::toUpperCase) + .map(ClickType::valueOf) + .collect(Collectors.toList()); + } }catch (IllegalArgumentException ex) { - BeautyQuests.logger.warning("Unknown click type " + config.getString("npcClick") + " for config entry \"npcClick\""); + BeautyQuests.logger.warning("Unknown click type " + config.get("npcClick") + " for config entry \"npcClick\""); } enablePrefix = config.getBoolean("enablePrefix"); disableTextHologram = config.getBoolean("disableTextHologram"); @@ -305,8 +316,8 @@ public static int getProgressBarTimeout(){ return progressBarTimeoutSeconds; } - public static ClickType getNPCClick() { - return npcClick; + public static Collection getNPCClicks() { + return npcClicks; } public static boolean handleGPS(){ @@ -476,10 +487,14 @@ public static QuestsMenuConfig getMenuConfig() { } public enum ClickType { - RIGHT, LEFT, ANY; + RIGHT, SHIFT_RIGHT, LEFT, SHIFT_LEFT; - public boolean applies(ClickType type) { - return (this == type) || (this == ANY) || (type == ANY); + public static ClickType of(boolean left, boolean shift) { + if (left) { + return shift ? SHIFT_LEFT : LEFT; + }else { + return shift ? SHIFT_RIGHT : RIGHT; + } } } diff --git a/core/src/main/java/fr/skytasul/quests/QuestsListener.java b/core/src/main/java/fr/skytasul/quests/QuestsListener.java index d2a3abd6..75f8d3ee 100644 --- a/core/src/main/java/fr/skytasul/quests/QuestsListener.java +++ b/core/src/main/java/fr/skytasul/quests/QuestsListener.java @@ -52,7 +52,7 @@ public class QuestsListener implements Listener{ @EventHandler (priority = EventPriority.HIGHEST, ignoreCancelled = true) public void onNPCClick(BQNPCClickEvent e) { if (e.isCancelled()) return; - if (!QuestsConfiguration.getNPCClick().applies(e.getClick())) return; + if (!QuestsConfiguration.getNPCClicks().contains(e.getClick())) return; Player p = e.getPlayer(); BQNPC npc = e.getNPC(); diff --git a/core/src/main/java/fr/skytasul/quests/gui/quests/PlayerListGUI.java b/core/src/main/java/fr/skytasul/quests/gui/quests/PlayerListGUI.java index bd54be37..60204ee1 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/quests/PlayerListGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/quests/PlayerListGUI.java @@ -109,7 +109,7 @@ private void setItems(){ break; case IN_PROGRESS: - setQuests(QuestsAPI.getQuests().getQuestsStarted(acc)); + setQuests(QuestsAPI.getQuests().getQuestsStarted(acc, true, false)); for (int i = page * 35; i < quests.size(); i++){ if (i == (page + 1) * 35) break; Quest qu = quests.get(i); diff --git a/core/src/main/java/fr/skytasul/quests/gui/quests/QuestsListGUI.java b/core/src/main/java/fr/skytasul/quests/gui/quests/QuestsListGUI.java index ebfd2e1c..4101f832 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/quests/QuestsListGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/quests/QuestsListGUI.java @@ -22,7 +22,7 @@ public class QuestsListGUI extends PagedGUI { public Consumer run; public QuestsListGUI(Consumer run, PlayerAccount acc, boolean started, boolean notStarted, boolean finished){ - super(Lang.INVENTORY_QUESTS_LIST.toString(), DyeColor.CYAN, new ArrayList<>(), null, x -> x.getName()); + super(Lang.INVENTORY_QUESTS_LIST.toString(), DyeColor.CYAN, new ArrayList<>(), null, Quest::getName); if (acc != null){ if (started) super.objects.addAll(QuestsAPI.getQuests().getQuestsStarted(acc)); if (notStarted) super.objects.addAll(QuestsAPI.getQuests().getQuestsNotStarted(acc, false, false)); diff --git a/core/src/main/java/fr/skytasul/quests/scoreboards/Scoreboard.java b/core/src/main/java/fr/skytasul/quests/scoreboards/Scoreboard.java index 00cd51f3..23c5c5ba 100644 --- a/core/src/main/java/fr/skytasul/quests/scoreboards/Scoreboard.java +++ b/core/src/main/java/fr/skytasul/quests/scoreboards/Scoreboard.java @@ -50,7 +50,7 @@ public class Scoreboard extends BukkitRunnable implements Listener { lines.add(new Line(line)); } - launched = QuestsAPI.getQuests().getQuestsStarted(acc, true); + launched = QuestsAPI.getQuests().getQuestsStarted(acc, false, true); hid = !manager.isWorldAllowed(p.getWorld().getName()); @@ -179,7 +179,7 @@ public void setShownQuest(Quest quest, boolean errorWhenUnknown) { if (!quest.isScoreboardEnabled()) return; if (!launched.contains(quest)) { if (errorWhenUnknown) { - launched = QuestsAPI.getQuests().getQuestsStarted(acc, true); + launched = QuestsAPI.getQuests().getQuestsStarted(acc, false, true); if (!launched.contains(quest)) throw new IllegalArgumentException("Quest is not running for player."); }else return; } diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java b/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java index 193555f0..cb41697d 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageNPC.java @@ -184,7 +184,7 @@ protected void initDialogRunner() { public void onClick(BQNPCClickEvent e) { if (e.isCancelled()) return; if (e.getNPC() != npc) return; - if (!QuestsConfiguration.getNPCClick().applies(e.getClick())) return; + if (!QuestsConfiguration.getNPCClicks().contains(e.getClick())) return; Player p = e.getPlayer(); e.setCancelled(dialogRunner.onClick(p).shouldCancel()); diff --git a/core/src/main/java/fr/skytasul/quests/structure/QuestsManager.java b/core/src/main/java/fr/skytasul/quests/structure/QuestsManager.java index 3440b51e..240a4645 100644 --- a/core/src/main/java/fr/skytasul/quests/structure/QuestsManager.java +++ b/core/src/main/java/fr/skytasul/quests/structure/QuestsManager.java @@ -122,15 +122,16 @@ public void addQuest(Quest quest) { } public List getQuestsStarted(PlayerAccount acc) { - return getQuestsStarted(acc, false); + return getQuestsStarted(acc, false, false); } - public List getQuestsStarted(PlayerAccount acc, boolean withoutScoreboard) { + public List getQuestsStarted(PlayerAccount acc, boolean hide, boolean withoutScoreboard) { return acc.getQuestsDatas() .stream() .filter(PlayerQuestDatas::hasStarted) .map(PlayerQuestDatas::getQuest) .filter(Objects::nonNull) + .filter(quest -> !hide || !quest.isHidden()) .filter(quest -> !withoutScoreboard || quest.isScoreboardEnabled()) .collect(Collectors.toList()); } diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/QuestsPlaceholders.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/QuestsPlaceholders.java index 9fdcfa97..79d82c28 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/compatibility/QuestsPlaceholders.java +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/QuestsPlaceholders.java @@ -145,7 +145,7 @@ public String onRequest(OfflinePlayer off, String identifier) { } if (data.left.isEmpty()) { - data.left = QuestsAPI.getQuests().getQuestsStarted(data.acc, true); + data.left = QuestsAPI.getQuests().getQuestsStarted(data.acc, false, true); }else QuestsAPI.getQuests().updateQuestsStarted(acc, true, data.left); try { diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/npcs/BQCitizens.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/npcs/BQCitizens.java index 1deafc00..d81a5960 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/compatibility/npcs/BQCitizens.java +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/npcs/BQCitizens.java @@ -51,13 +51,13 @@ public Collection getIDs() { @EventHandler (priority = EventPriority.HIGHEST) public void onNPCRightClick(NPCRightClickEvent e) { if (e.getNPC().getOwningRegistry() != CitizensAPI.getNPCRegistry()) return; - super.clickEvent(e, e.getNPC().getId(), e.getClicker(), ClickType.RIGHT); + super.clickEvent(e, e.getNPC().getId(), e.getClicker(), e.getClicker().isSneaking() ? ClickType.SHIFT_RIGHT : ClickType.RIGHT); } @EventHandler (priority = EventPriority.HIGHEST) public void onNPCLeftClick(NPCLeftClickEvent e) { if (e.getNPC().getOwningRegistry() != CitizensAPI.getNPCRegistry()) return; - super.clickEvent(e, e.getNPC().getId(), e.getClicker(), ClickType.LEFT); + super.clickEvent(e, e.getNPC().getId(), e.getClicker(), e.getClicker().isSneaking() ? ClickType.SHIFT_LEFT : ClickType.LEFT); } @EventHandler diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/npcs/BQServerNPCs.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/npcs/BQServerNPCs.java index 26b870e3..7623fb64 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/compatibility/npcs/BQServerNPCs.java +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/npcs/BQServerNPCs.java @@ -62,7 +62,7 @@ protected BQNPC create(Location location, EntityType type, String name) { @EventHandler public void onInteract(NPCInteractEvent e) { - super.clickEvent(null, e.getNpc().getNpcPojo().getId(), e.getPlayer(), e.isLeftClick() ? ClickType.LEFT : ClickType.RIGHT); + super.clickEvent(null, e.getNpc().getNpcPojo().getId(), e.getPlayer(), ClickType.of(e.isLeftClick(), e.getPlayer().isSneaking())); } public static class BQServerNPC extends BQNPC { diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index 8e883dcf..035291bb 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -50,9 +50,10 @@ fireworks: true mobsProgressBar: false # Amount of seconds before the progress bar will disappear (set it to 0 to make it persistent) progressBarTimeoutSeconds: 15 -# Which click the player has to do on the NPC to start a quest, follow a dialog... (can be: RIGHT, LEFT, ANY) -npcClick: RIGHT -# Item which represents a quest in the Choose Quest GUI +# Which clicks are acceptable for a player to do on the NPC in order to start a quest, follow a dialog... +# (can be: RIGHT, LEFT, SHIFT_RIGHT, SHIFT_LEFT) +npcClick: [RIGHT, SHIFT_RIGHT] +# Default item shown for a quest in the menus item: BOOK # Page item material pageItem: ARROW From 8fae7ca75b8bffb92928aed5e70d6d7de3a5f8cd Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 24 Jul 2022 18:43:44 +0200 Subject: [PATCH 042/111] :heavy_plus_sign: Added AdvancedSpawners integration --- core/libs.sh | 1 + core/pom.xml | 6 ++ .../skytasul/quests/api/mobs/MobFactory.java | 1 - .../fr/skytasul/quests/stages/StageMobs.java | 2 +- .../java/fr/skytasul/quests/utils/Lang.java | 3 +- .../compatibility/DependenciesManager.java | 5 +- .../mobs/BQAdvancedSpawners.java | 62 +++++++++++++++++++ core/src/main/resources/locales/en_US.yml | 2 +- core/src/main/resources/plugin.yml | 1 + 9 files changed, 78 insertions(+), 5 deletions(-) create mode 100644 core/src/main/java/fr/skytasul/quests/utils/compatibility/mobs/BQAdvancedSpawners.java diff --git a/core/libs.sh b/core/libs.sh index 896cf1ef..1f470bb9 100644 --- a/core/libs.sh +++ b/core/libs.sh @@ -24,6 +24,7 @@ echo -e "Maven path: $mavenPath\e[39m" "$mavenPath" install:install-file -Dfile=$jarsPath/CMI.jar -DgroupId=com.zrips -DartifactId=cmi -Dversion=9.0.2.1 -Dpackaging=jar "$mavenPath" install:install-file -Dfile=$jarsPath/CMILib.jar -DgroupId=com.zrips -DartifactId=cmilib -Dversion=1.0.4.1 -Dpackaging=jar "$mavenPath" install:install-file -Dfile=$jarsPath/UltimateTimber.jar -DgroupId=com.songoda -DartifactId=ultimatetimber -Dversion=2.2.5 -Dpackaging=jar +"$mavenPath" install:install-file -Dfile=$jarsPath/AdvancedSpawners-API.jar -DgroupId=gcspawners -DartifactId=gcspawners -Dversion=3.3.0 -Dpackaging=jar #"$mavenPath" install:install-file -Dfile=$jarsPath/MythicMobs.jar -DgroupId=io.lumine.xikage -DartifactId=MythicMobs -Dversion=4.12.0 -Dpackaging=jar #"$mavenPath" install:install-file -Dfile=$jarsPath/TokenEnchantAPI.jar -DgroupId=com.vk2gpz.tokenenchant -DartifactId=TokenEnchantAPI -Dversion=18.15.2 -Dpackaging=jar diff --git a/core/pom.xml b/core/pom.xml index 43106f5e..84b09871 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -256,6 +256,12 @@ 2.7.5 provided + + gcspawners + gcspawners + 3.3.0 + provided + diff --git a/core/src/main/java/fr/skytasul/quests/api/mobs/MobFactory.java b/core/src/main/java/fr/skytasul/quests/api/mobs/MobFactory.java index 8f32bf2c..27c122e7 100644 --- a/core/src/main/java/fr/skytasul/quests/api/mobs/MobFactory.java +++ b/core/src/main/java/fr/skytasul/quests/api/mobs/MobFactory.java @@ -93,7 +93,6 @@ public default boolean mobApplies(T first, Object other) { */ public default void callEvent(Event originalEvent, T pluginMob, Entity entity, Player player) { Validate.notNull(pluginMob, "Plugin mob object cannot be null"); - Validate.notNull(entity, "Bukkit entity object cannot be null"); Validate.notNull(player, "Player cannot be null"); if (originalEvent != null) { CompatMobDeathEvent existingCompat = eventsCache.getIfPresent(originalEvent); diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageMobs.java b/core/src/main/java/fr/skytasul/quests/stages/StageMobs.java index 8a588ce6..97a4ac38 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageMobs.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageMobs.java @@ -42,7 +42,7 @@ public void setShoot(boolean shoot) { @EventHandler public void onMobKilled(CompatMobDeathEvent e){ - if (shoot && e.getBukkitEntity().getLastDamageCause().getCause() != DamageCause.PROJECTILE) return; + if (shoot && e.getBukkitEntity() != null && e.getBukkitEntity().getLastDamageCause().getCause() != DamageCause.PROJECTILE) return; Player p = e.getKiller(); if (p == e.getBukkitEntity()) return; // player suicidal PlayerAccount acc = PlayersManager.getPlayerAccount(p); diff --git a/core/src/main/java/fr/skytasul/quests/utils/Lang.java b/core/src/main/java/fr/skytasul/quests/utils/Lang.java index 1ed6b9f0..d927ee85 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Lang.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Lang.java @@ -307,7 +307,7 @@ public enum Lang implements Locale { MYTHICMOB_LIST("msg.editor.mythicmobs.list"), MYTHICMOB_NOT_EXISTS("msg.editor.mythicmobs.isntMythicMob"), MYTHICMOB_DISABLED("msg.editor.mythicmobs.disabled"), - EPICBOSS_NOT_EXISTS("msg.editor.epicBossDoesntExist"), + ADVANCED_SPAWNERS_MOB("msg.editor.advancedSpawnersMob", EditorPrefix), TEXTLIST_SYNTAX("msg.editor.textList.syntax"), TEXTLIST_TEXT_ADDED("msg.editor.textList.added"), @@ -524,6 +524,7 @@ public enum Lang implements Locale { mythicMob("inv.mobSelect.mythicMob"), epicBoss("inv.mobSelect.epicBoss"), boss("inv.mobSelect.boss"), + advancedSpawners("inv.mobSelect.advancedSpawners"), location("inv.stageEnding.locationTeleport"), command("inv.stageEnding.command"), diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/DependenciesManager.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/DependenciesManager.java index b09373ec..524152bf 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/compatibility/DependenciesManager.java +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/DependenciesManager.java @@ -32,6 +32,7 @@ import fr.skytasul.quests.utils.XMaterial; import fr.skytasul.quests.utils.compatibility.maps.BQBlueMap; import fr.skytasul.quests.utils.compatibility.maps.BQDynmap; +import fr.skytasul.quests.utils.compatibility.mobs.BQAdvancedSpawners; import fr.skytasul.quests.utils.compatibility.mobs.BQBoss; import fr.skytasul.quests.utils.compatibility.mobs.CitizensFactory; import fr.skytasul.quests.utils.compatibility.mobs.MythicMobs; @@ -93,6 +94,8 @@ public class DependenciesManager implements Listener { } }); + public static final BQDependency advancedspawners = new BQDependency("AdvancedSpawners", () -> QuestsAPI.registerMobFactory(new BQAdvancedSpawners())); + public static final BQDependency holod2 = new BQDependency("HolographicDisplays", () -> QuestsAPI.setHologramsManager(new BQHolographicDisplays2()), null, plugin -> plugin.getClass().getName().equals("com.gmail.filoghost.holographicdisplays.HolographicDisplays")); public static final BQDependency holod3 = new BQDependency("HolographicDisplays", () -> QuestsAPI.setHologramsManager(new BQHolographicDisplays3()), null, plugin -> { if (!plugin.getClass().getName().equals("me.filoghost.holographicdisplays.plugin.HolographicDisplays")) return false; @@ -129,7 +132,7 @@ public class DependenciesManager implements Listener { private boolean lockDependencies = false; public DependenciesManager() { - dependencies = new ArrayList<>(Arrays.asList(znpcs, citizens, wg, mm, vault, papi, skapi, jobs, fac, acc, dyn, BlueMap, /*par, eboss, */gps, mmo, mclvl, boss, cmi, holod2, holod3, tokenEnchant, ultimateTimber, sentinel)); + dependencies = new ArrayList<>(Arrays.asList(znpcs, citizens, wg, mm, vault, papi, skapi, jobs, fac, acc, dyn, BlueMap, /*par, eboss, */gps, mmo, mclvl, boss, cmi, holod2, holod3, tokenEnchant, ultimateTimber, sentinel, advancedspawners)); } public List getDependencies() { diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/mobs/BQAdvancedSpawners.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/mobs/BQAdvancedSpawners.java new file mode 100644 index 00000000..a35841ab --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/mobs/BQAdvancedSpawners.java @@ -0,0 +1,62 @@ +package fr.skytasul.quests.utils.compatibility.mobs; + +import java.util.function.Consumer; + +import org.bukkit.entity.EntityType; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.inventory.ItemStack; + +import fr.skytasul.quests.api.mobs.MobFactory; +import fr.skytasul.quests.editors.TextEditor; +import fr.skytasul.quests.gui.ItemUtils; +import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.XMaterial; + +import gcspawners.AdvancedEntityDeathEvent; + +public class BQAdvancedSpawners implements MobFactory { + + @Override + public String getID() { + return "advanced-spawners"; + } + + @Override + public ItemStack getFactoryItem() { + return ItemUtils.item(XMaterial.SPAWNER, Lang.advancedSpawners.toString()); + } + + @Override + public void itemClick(Player p, Consumer run) { + Lang.ADVANCED_SPAWNERS_MOB.send(p); + new TextEditor<>(p, () -> run.accept(null), run).enter(); + } + + @Override + public String fromValue(String value) { + return value; + } + + @Override + public String getValue(String data) { + return data; + } + + @Override + public String getName(String data) { + return data; + } + + @Override + public EntityType getEntityType(String data) { + return EntityType.PIG; + } + + @EventHandler + public void onMobDeath(AdvancedEntityDeathEvent event) { + if (!(event.getDamager() instanceof Player)) return; + callEvent(event, event.getEntityType(), null, (Player) event.getDamager()); + } + +} diff --git a/core/src/main/resources/locales/en_US.yml b/core/src/main/resources/locales/en_US.yml index 31816477..1ea30ac4 100644 --- a/core/src/main/resources/locales/en_US.yml +++ b/core/src/main/resources/locales/en_US.yml @@ -297,7 +297,7 @@ msg: list: '§aA list of all Mythic Mobs:' isntMythicMob: §cThis Mythic Mob doesn't exist. disabled: §cMythicMob is disabled. - epicBossDoesntExist: §cThis Epic Boss doesn't exist. + advancedSpawnersMob: 'Write the name of the custom spawner mob to kill:' textList: syntax: '§cCorrect syntax: ' added: §aText "§7{0}§a" added. diff --git a/core/src/main/resources/plugin.yml b/core/src/main/resources/plugin.yml index 40a557e3..c7cdacec 100644 --- a/core/src/main/resources/plugin.yml +++ b/core/src/main/resources/plugin.yml @@ -34,6 +34,7 @@ softdepend: - ProSkillAPI - BlueMap - Sentinel +- AdvancedSpawners commands: beautyquests: From 8e4d2c9edfd08382efaa66620b988fad4c08e0ba Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Mon, 8 Aug 2022 18:44:35 +0200 Subject: [PATCH 043/111] :bug: Fixed craft exploit * closes 236 --- .../main/java/fr/skytasul/quests/stages/StageCraft.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageCraft.java b/core/src/main/java/fr/skytasul/quests/stages/StageCraft.java index f81c8f86..f22e0f06 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageCraft.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageCraft.java @@ -96,7 +96,13 @@ public void onCraft(CraftItemEvent e){ default: cursor = e.getCursor(); - if (cursor != null && cursor.getType() != Material.AIR && !cursor.isSimilar(item)) recipeAmount = 0; + if (cursor != null && cursor.getType() != Material.AIR) { + if (cursor.isSimilar(item)) { + if (cursor.getAmount() + item.getAmount() > cursor.getMaxStackSize()) recipeAmount = 0; + }else { + recipeAmount = 0; + } + } break; } From 2b3e91016a61e5d150cde3317efe462923ba088e Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Wed, 17 Aug 2022 21:28:14 +0200 Subject: [PATCH 044/111] :bug: Fixed quest start dialog not checking requirements --- core/src/main/java/fr/skytasul/quests/structure/Quest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/fr/skytasul/quests/structure/Quest.java b/core/src/main/java/fr/skytasul/quests/structure/Quest.java index a2ad56e3..a5991301 100644 --- a/core/src/main/java/fr/skytasul/quests/structure/Quest.java +++ b/core/src/main/java/fr/skytasul/quests/structure/Quest.java @@ -294,6 +294,7 @@ public double getDescriptionPriority() { } public void attemptStart(Player p, Runnable atStart) { + if (!isLauncheable(p, PlayersManager.getPlayerAccount(p), true)) return; String confirm; if (QuestsConfiguration.questConfirmGUI() && !"none".equals(confirm = getOptionValueOrDef(OptionConfirmMessage.class))) { new ConfirmGUI(() -> { From 44832b52517521e9613bac5cca0d6c0fda657c82 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Wed, 17 Aug 2022 21:28:52 +0200 Subject: [PATCH 045/111] :arrow_up: Upgraded Jobs and CMILib dependencies --- core/libs.sh | 4 ++-- core/pom.xml | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/core/libs.sh b/core/libs.sh index 1f470bb9..91882d9a 100644 --- a/core/libs.sh +++ b/core/libs.sh @@ -16,13 +16,13 @@ echo -e "Maven path: $mavenPath\e[39m" "$mavenPath" install:install-file -Dfile=$jarsPath/Factions.jar -DgroupId=com.massivecraft -DartifactId=factions -Dversion=1.0 -Dpackaging=jar "$mavenPath" install:install-file -Dfile=$jarsPath/MassiveCore.jar -DgroupId=com.massivecraft -DartifactId=massivecore -Dversion=1.0 -Dpackaging=jar "$mavenPath" install:install-file -Dfile=$jarsPath/GPS.jar -DgroupId=com.live.bemmamin -DartifactId=gps -Dversion=1.0 -Dpackaging=jar -"$mavenPath" install:install-file -Dfile=$jarsPath/Jobs.jar -DgroupId=com.gamingmesh -DartifactId=jobs -Dversion=5.0.0.9 -Dpackaging=jar +"$mavenPath" install:install-file -Dfile=$jarsPath/Jobs.jar -DgroupId=com.gamingmesh -DartifactId=jobs -Dversion=5.1.0.1 -Dpackaging=jar "$mavenPath" install:install-file -Dfile=$jarsPath/McCombatLevel.jar -DgroupId=com.gmail.mrphpfan -DartifactId=mccombatlevel -Dversion=1.0 -Dpackaging=jar "$mavenPath" install:install-file -Dfile=$jarsPath/mcMMO.jar -DgroupId=com.gmail.nossr50 -DartifactId=mcmmo -Dversion=1.0 -Dpackaging=jar "$mavenPath" install:install-file -Dfile=$jarsPath/SkillAPI.jar -DgroupId=com.suxy -DartifactId=skillapi -Dversion=1.0 -Dpackaging=jar "$mavenPath" install:install-file -Dfile=$jarsPath/Boss.jar -DgroupId=org.mineacademy -DartifactId=boss -Dversion=4.2.1 -Dpackaging=jar "$mavenPath" install:install-file -Dfile=$jarsPath/CMI.jar -DgroupId=com.zrips -DartifactId=cmi -Dversion=9.0.2.1 -Dpackaging=jar -"$mavenPath" install:install-file -Dfile=$jarsPath/CMILib.jar -DgroupId=com.zrips -DartifactId=cmilib -Dversion=1.0.4.1 -Dpackaging=jar +"$mavenPath" install:install-file -Dfile=$jarsPath/CMILib.jar -DgroupId=com.zrips -DartifactId=cmilib -Dversion=1.2.3.3 -Dpackaging=jar "$mavenPath" install:install-file -Dfile=$jarsPath/UltimateTimber.jar -DgroupId=com.songoda -DartifactId=ultimatetimber -Dversion=2.2.5 -Dpackaging=jar "$mavenPath" install:install-file -Dfile=$jarsPath/AdvancedSpawners-API.jar -DgroupId=gcspawners -DartifactId=gcspawners -Dversion=3.3.0 -Dpackaging=jar #"$mavenPath" install:install-file -Dfile=$jarsPath/MythicMobs.jar -DgroupId=io.lumine.xikage -DartifactId=MythicMobs -Dversion=4.12.0 -Dpackaging=jar diff --git a/core/pom.xml b/core/pom.xml index 84b09871..2d5de926 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -256,12 +256,6 @@ 2.7.5 provided - - gcspawners - gcspawners - 3.3.0 - provided - @@ -303,7 +297,7 @@ com.gamingmesh jobs - 5.0.0.9 + 5.1.0.1 provided @@ -333,7 +327,13 @@ com.zrips cmilib - 1.0.4.1 + 1.2.3.3 + provided + + + gcspawners + gcspawners + 3.3.0 provided From b9cea926768f35a6502c39973c51dc06f7a3726d Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 7 Aug 2022 22:52:16 +0200 Subject: [PATCH 046/111] :recycle: Refactored some player account management code * moved the Join and Leave PlayerAccount events * added the PlayerAccountResetEvent * simplified database update code --- core/pom.xml | 2 +- .../fr/skytasul/quests/QuestsListener.java | 4 +- .../events/accounts/PlayerAccountEvent.java | 30 + .../accounts}/PlayerAccountJoinEvent.java | 6 +- .../accounts}/PlayerAccountLeaveEvent.java | 6 +- .../accounts/PlayerAccountResetEvent.java | 25 + .../quests/api/stages/AbstractStage.java | 4 +- .../fr/skytasul/quests/commands/Commands.java | 5 +- .../quests/players/PlayerAccount.java | 290 ++-- .../quests/players/PlayersManager.java | 4 +- .../quests/players/PlayersManagerDB.java | 1311 +++++++++-------- .../quests/players/PlayersManagerYAML.java | 632 ++++---- .../players/events/PlayerAccountEvent.java | 20 - .../quests/scoreboards/ScoreboardManager.java | 4 +- .../quests/structure/QuestsManager.java | 11 +- .../quests/utils/ThrowingConsumer.java | 8 + v1_19_R1/pom.xml | 10 +- 17 files changed, 1216 insertions(+), 1156 deletions(-) create mode 100644 core/src/main/java/fr/skytasul/quests/api/events/accounts/PlayerAccountEvent.java rename core/src/main/java/fr/skytasul/quests/{players/events => api/events/accounts}/PlayerAccountJoinEvent.java (83%) rename core/src/main/java/fr/skytasul/quests/{players/events => api/events/accounts}/PlayerAccountLeaveEvent.java (80%) create mode 100644 core/src/main/java/fr/skytasul/quests/api/events/accounts/PlayerAccountResetEvent.java delete mode 100644 core/src/main/java/fr/skytasul/quests/players/events/PlayerAccountEvent.java create mode 100644 core/src/main/java/fr/skytasul/quests/utils/ThrowingConsumer.java diff --git a/core/pom.xml b/core/pom.xml index 2d5de926..ac4a7826 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -133,7 +133,7 @@ io.papermc.paper paper-api - 1.19-R0.1-SNAPSHOT + 1.19.2-R0.1-SNAPSHOT provided diff --git a/core/src/main/java/fr/skytasul/quests/QuestsListener.java b/core/src/main/java/fr/skytasul/quests/QuestsListener.java index 75f8d3ee..c2828cf0 100644 --- a/core/src/main/java/fr/skytasul/quests/QuestsListener.java +++ b/core/src/main/java/fr/skytasul/quests/QuestsListener.java @@ -28,6 +28,8 @@ import fr.skytasul.quests.api.QuestsAPI; import fr.skytasul.quests.api.events.BQBlockBreakEvent; import fr.skytasul.quests.api.events.BQNPCClickEvent; +import fr.skytasul.quests.api.events.accounts.PlayerAccountJoinEvent; +import fr.skytasul.quests.api.events.accounts.PlayerAccountLeaveEvent; import fr.skytasul.quests.api.npcs.BQNPC; import fr.skytasul.quests.api.options.QuestOption; import fr.skytasul.quests.gui.Inventories; @@ -37,8 +39,6 @@ import fr.skytasul.quests.options.OptionAutoQuest; import fr.skytasul.quests.players.PlayerAccount; import fr.skytasul.quests.players.PlayersManager; -import fr.skytasul.quests.players.events.PlayerAccountJoinEvent; -import fr.skytasul.quests.players.events.PlayerAccountLeaveEvent; import fr.skytasul.quests.structure.Quest; import fr.skytasul.quests.structure.pools.QuestPool; import fr.skytasul.quests.utils.DebugUtils; diff --git a/core/src/main/java/fr/skytasul/quests/api/events/accounts/PlayerAccountEvent.java b/core/src/main/java/fr/skytasul/quests/api/events/accounts/PlayerAccountEvent.java new file mode 100644 index 00000000..26be1092 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/api/events/accounts/PlayerAccountEvent.java @@ -0,0 +1,30 @@ +package fr.skytasul.quests.api.events.accounts; + +import org.bukkit.entity.Player; +import org.bukkit.event.Event; + +import fr.skytasul.quests.players.PlayerAccount; + +public abstract class PlayerAccountEvent extends Event { + + private Player who; + private PlayerAccount account; + + protected PlayerAccountEvent(Player who, PlayerAccount account) { + this.who = who; + this.account = account; + } + + public boolean isAccountCurrent() { + return who != null; + } + + public Player getPlayer() { + return who; + } + + public PlayerAccount getPlayerAccount() { + return account; + } + +} \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/players/events/PlayerAccountJoinEvent.java b/core/src/main/java/fr/skytasul/quests/api/events/accounts/PlayerAccountJoinEvent.java similarity index 83% rename from core/src/main/java/fr/skytasul/quests/players/events/PlayerAccountJoinEvent.java rename to core/src/main/java/fr/skytasul/quests/api/events/accounts/PlayerAccountJoinEvent.java index bc61d03e..264c4862 100644 --- a/core/src/main/java/fr/skytasul/quests/players/events/PlayerAccountJoinEvent.java +++ b/core/src/main/java/fr/skytasul/quests/api/events/accounts/PlayerAccountJoinEvent.java @@ -1,4 +1,4 @@ -package fr.skytasul.quests.players.events; +package fr.skytasul.quests.api.events.accounts; import org.bukkit.entity.Player; import org.bukkit.event.HandlerList; @@ -10,8 +10,7 @@ public class PlayerAccountJoinEvent extends PlayerAccountEvent { private boolean firstJoin; public PlayerAccountJoinEvent(Player who, PlayerAccount account, boolean firstJoin) { - super(who); - this.account = account; + super(who, account); this.firstJoin = firstJoin; } @@ -19,6 +18,7 @@ public boolean isFirstJoin() { return firstJoin; } + @Override public HandlerList getHandlers() { return handlers; } diff --git a/core/src/main/java/fr/skytasul/quests/players/events/PlayerAccountLeaveEvent.java b/core/src/main/java/fr/skytasul/quests/api/events/accounts/PlayerAccountLeaveEvent.java similarity index 80% rename from core/src/main/java/fr/skytasul/quests/players/events/PlayerAccountLeaveEvent.java rename to core/src/main/java/fr/skytasul/quests/api/events/accounts/PlayerAccountLeaveEvent.java index d516ad29..5c304316 100644 --- a/core/src/main/java/fr/skytasul/quests/players/events/PlayerAccountLeaveEvent.java +++ b/core/src/main/java/fr/skytasul/quests/api/events/accounts/PlayerAccountLeaveEvent.java @@ -1,4 +1,4 @@ -package fr.skytasul.quests.players.events; +package fr.skytasul.quests.api.events.accounts; import org.bukkit.entity.Player; import org.bukkit.event.HandlerList; @@ -8,10 +8,10 @@ public class PlayerAccountLeaveEvent extends PlayerAccountEvent { public PlayerAccountLeaveEvent(Player who, PlayerAccount account) { - super(who); - this.account = account; + super(who, account); } + @Override public HandlerList getHandlers() { return handlers; } diff --git a/core/src/main/java/fr/skytasul/quests/api/events/accounts/PlayerAccountResetEvent.java b/core/src/main/java/fr/skytasul/quests/api/events/accounts/PlayerAccountResetEvent.java new file mode 100644 index 00000000..fa2de5cc --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/api/events/accounts/PlayerAccountResetEvent.java @@ -0,0 +1,25 @@ +package fr.skytasul.quests.api.events.accounts; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; + +import fr.skytasul.quests.players.PlayerAccount; + +public class PlayerAccountResetEvent extends PlayerAccountEvent { + + public PlayerAccountResetEvent(Player who, PlayerAccount account) { + super(who, account); + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + private static final HandlerList handlers = new HandlerList(); + +} diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java index 37eeead0..05c376cd 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java @@ -20,6 +20,8 @@ import fr.skytasul.quests.BeautyQuests; import fr.skytasul.quests.QuestsConfiguration; import fr.skytasul.quests.api.QuestsAPI; +import fr.skytasul.quests.api.events.accounts.PlayerAccountJoinEvent; +import fr.skytasul.quests.api.events.accounts.PlayerAccountLeaveEvent; import fr.skytasul.quests.api.objects.QuestObject; import fr.skytasul.quests.api.requirements.AbstractRequirement; import fr.skytasul.quests.api.rewards.AbstractReward; @@ -28,8 +30,6 @@ import fr.skytasul.quests.api.stages.options.StageOption; import fr.skytasul.quests.players.PlayerAccount; import fr.skytasul.quests.players.PlayersManager; -import fr.skytasul.quests.players.events.PlayerAccountJoinEvent; -import fr.skytasul.quests.players.events.PlayerAccountLeaveEvent; import fr.skytasul.quests.structure.QuestBranch; import fr.skytasul.quests.structure.QuestBranch.Source; import fr.skytasul.quests.utils.Utils; diff --git a/core/src/main/java/fr/skytasul/quests/commands/Commands.java b/core/src/main/java/fr/skytasul/quests/commands/Commands.java index f1bcabda..c4dc0507 100644 --- a/core/src/main/java/fr/skytasul/quests/commands/Commands.java +++ b/core/src/main/java/fr/skytasul/quests/commands/Commands.java @@ -22,6 +22,7 @@ import fr.skytasul.quests.BeautyQuests; import fr.skytasul.quests.api.QuestsAPI; +import fr.skytasul.quests.api.events.accounts.PlayerAccountResetEvent; import fr.skytasul.quests.api.npcs.BQNPC; import fr.skytasul.quests.api.stages.AbstractStage; import fr.skytasul.quests.api.stages.types.Dialogable; @@ -301,7 +302,9 @@ public void resetPlayer(CommandContext cmd){ }else acc.removePoolDatas(poolDatas.getPoolID()); pools++; } - if (acc.isCurrent()) Lang.DATA_REMOVED.send(acc.getPlayer(), quests, cmd.sender.getName(), pools); + Player player = acc.getPlayer(); + Bukkit.getPluginManager().callEvent(new PlayerAccountResetEvent(player, acc)); + if (acc.isCurrent()) Lang.DATA_REMOVED.send(player, quests, cmd.sender.getName(), pools); Lang.DATA_REMOVED_INFO.send(cmd.sender, quests, target.getName(), pools); } diff --git a/core/src/main/java/fr/skytasul/quests/players/PlayerAccount.java b/core/src/main/java/fr/skytasul/quests/players/PlayerAccount.java index 2c346a0d..44ac98c0 100644 --- a/core/src/main/java/fr/skytasul/quests/players/PlayerAccount.java +++ b/core/src/main/java/fr/skytasul/quests/players/PlayerAccount.java @@ -1,145 +1,145 @@ -package fr.skytasul.quests.players; - -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -import org.bukkit.OfflinePlayer; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.entity.Player; - -import fr.skytasul.quests.players.accounts.AbstractAccount; -import fr.skytasul.quests.structure.Quest; -import fr.skytasul.quests.structure.pools.QuestPool; -import fr.skytasul.quests.utils.Utils; - -public class PlayerAccount { - - public final AbstractAccount abstractAcc; - protected final Map questDatas = new HashMap<>(); - protected final Map poolDatas = new HashMap<>(); - protected final int index; - - public PlayerAccount(AbstractAccount account, int index) { - this.abstractAcc = account; - this.index = index; - } - - /** - * @return if this account is currently used by the player (if true, {@link #getPlayer()} cannot return a null player) - */ - public boolean isCurrent() { - return abstractAcc.isCurrent(); - } - - /** - * @return the OfflinePlayer instance attached to this account (no matter if the player is online or not, or if the account is the currently used) - */ - public OfflinePlayer getOfflinePlayer(){ - return abstractAcc.getOfflinePlayer(); - } - - /** - * @return the Player instance who own this account. If the account is not which in use by the player ({@link #isCurrent()}), this will return null. - */ - public Player getPlayer(){ - return abstractAcc.getPlayer(); - } - - public boolean hasQuestDatas(Quest quest) { - return questDatas.containsKey(quest.getID()); - } - - public PlayerQuestDatas getQuestDatasIfPresent(Quest quest) { - return questDatas.get(quest.getID()); - } - - public PlayerQuestDatas getQuestDatas(Quest quest) { - PlayerQuestDatas datas = questDatas.get(quest.getID()); - if (datas == null) { - datas = PlayersManager.manager.createPlayerQuestDatas(this, quest); - questDatas.put(quest.getID(), datas); - } - return datas; - } - - public PlayerQuestDatas removeQuestDatas(Quest quest) { - return removeQuestDatas(quest.getID()); - } - - public PlayerQuestDatas removeQuestDatas(int id) { - PlayerQuestDatas removed = questDatas.remove(id); - if (removed != null) PlayersManager.manager.playerQuestDataRemoved(this, id, removed); - return removed; - } - - protected PlayerQuestDatas removeQuestDatasSilently(int id) { - return questDatas.remove(id); - } - - public Collection getQuestsDatas() { - return questDatas.values(); - } - - public boolean hasPoolDatas(QuestPool pool) { - return poolDatas.containsKey(pool.getID()); - } - - public PlayerPoolDatas getPoolDatas(QuestPool pool) { - PlayerPoolDatas datas = poolDatas.get(pool.getID()); - if (datas == null) { - datas = PlayersManager.manager.createPlayerPoolDatas(this, pool); - poolDatas.put(pool.getID(), datas); - } - return datas; - } - - public PlayerPoolDatas removePoolDatas(QuestPool pool) { - return removePoolDatas(pool.getID()); - } - - public PlayerPoolDatas removePoolDatas(int id) { - PlayerPoolDatas removed = poolDatas.remove(id); - if (removed != null) PlayersManager.manager.playerPoolDataRemoved(this, id, removed); - return removed; - } - - public Collection getPoolDatas() { - return poolDatas.values(); - } - - @Override - public boolean equals(Object arg0) { - if (arg0 == this) return true; - if (arg0.getClass() != this.getClass()) return false; - PlayerAccount otherAccount = (PlayerAccount) arg0; - if (!abstractAcc.equals(otherAccount.abstractAcc)) return false; - return true; - } - - @Override - public int hashCode() { - int hash = 1; - - hash = hash * 31 + index; - hash = hash * 31 + abstractAcc.hashCode(); - - return hash; - } - - public String getName() { - Player p = getPlayer(); - return p == null ? debugName() : p.getName(); - } - - public String debugName() { - return abstractAcc.getIdentifier() + " (#" + index + ")"; - } - - public void serialize(ConfigurationSection config) { - config.set("identifier", abstractAcc.getIdentifier()); - config.set("quests", questDatas.isEmpty() ? null : Utils.serializeList(questDatas.values(), PlayerQuestDatas::serialize)); - config.set("pools", poolDatas.isEmpty() ? null : Utils.serializeList(poolDatas.values(), PlayerPoolDatas::serialize)); - } - -} +package fr.skytasul.quests.players; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.bukkit.OfflinePlayer; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; + +import fr.skytasul.quests.players.accounts.AbstractAccount; +import fr.skytasul.quests.structure.Quest; +import fr.skytasul.quests.structure.pools.QuestPool; +import fr.skytasul.quests.utils.Utils; + +public class PlayerAccount { + + public final AbstractAccount abstractAcc; + protected final Map questDatas = new HashMap<>(); + protected final Map poolDatas = new HashMap<>(); + protected final int index; + + public PlayerAccount(AbstractAccount account, int index) { + this.abstractAcc = account; + this.index = index; + } + + /** + * @return if this account is currently used by the player (if true, {@link #getPlayer()} cannot return a null player) + */ + public boolean isCurrent() { + return abstractAcc.isCurrent(); + } + + /** + * @return the OfflinePlayer instance attached to this account (no matter if the player is online or not, or if the account is the currently used) + */ + public OfflinePlayer getOfflinePlayer(){ + return abstractAcc.getOfflinePlayer(); + } + + /** + * @return the Player instance who own this account. If the account is not which in use by the player ({@link #isCurrent()}), this will return null. + */ + public Player getPlayer(){ + return abstractAcc.getPlayer(); + } + + public boolean hasQuestDatas(Quest quest) { + return questDatas.containsKey(quest.getID()); + } + + public PlayerQuestDatas getQuestDatasIfPresent(Quest quest) { + return questDatas.get(quest.getID()); + } + + public PlayerQuestDatas getQuestDatas(Quest quest) { + PlayerQuestDatas datas = questDatas.get(quest.getID()); + if (datas == null) { + datas = PlayersManager.manager.createPlayerQuestDatas(this, quest); + questDatas.put(quest.getID(), datas); + } + return datas; + } + + public PlayerQuestDatas removeQuestDatas(Quest quest) { + return removeQuestDatas(quest.getID()); + } + + public PlayerQuestDatas removeQuestDatas(int id) { + PlayerQuestDatas removed = questDatas.remove(id); + if (removed != null) PlayersManager.manager.playerQuestDataRemoved(this, id, removed); + return removed; + } + + protected PlayerQuestDatas removeQuestDatasSilently(int id) { + return questDatas.remove(id); + } + + public Collection getQuestsDatas() { + return questDatas.values(); + } + + public boolean hasPoolDatas(QuestPool pool) { + return poolDatas.containsKey(pool.getID()); + } + + public PlayerPoolDatas getPoolDatas(QuestPool pool) { + PlayerPoolDatas datas = poolDatas.get(pool.getID()); + if (datas == null) { + datas = PlayersManager.manager.createPlayerPoolDatas(this, pool); + poolDatas.put(pool.getID(), datas); + } + return datas; + } + + public PlayerPoolDatas removePoolDatas(QuestPool pool) { + return removePoolDatas(pool.getID()); + } + + public PlayerPoolDatas removePoolDatas(int id) { + PlayerPoolDatas removed = poolDatas.remove(id); + if (removed != null) PlayersManager.manager.playerPoolDataRemoved(this, id, removed); + return removed; + } + + public Collection getPoolDatas() { + return poolDatas.values(); + } + + @Override + public boolean equals(Object arg0) { + if (arg0 == this) return true; + if (arg0.getClass() != this.getClass()) return false; + PlayerAccount otherAccount = (PlayerAccount) arg0; + if (!abstractAcc.equals(otherAccount.abstractAcc)) return false; + return true; + } + + @Override + public int hashCode() { + int hash = 1; + + hash = hash * 31 + index; + hash = hash * 31 + abstractAcc.hashCode(); + + return hash; + } + + public String getName() { + Player p = getPlayer(); + return p == null ? debugName() : p.getName(); + } + + public String debugName() { + return abstractAcc.getIdentifier() + " (#" + index + ")"; + } + + public void serialize(ConfigurationSection config) { + config.set("identifier", abstractAcc.getIdentifier()); + config.set("quests", questDatas.isEmpty() ? null : Utils.serializeList(questDatas.values(), PlayerQuestDatas::serialize)); + config.set("pools", poolDatas.isEmpty() ? null : Utils.serializeList(poolDatas.values(), PlayerPoolDatas::serialize)); + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/players/PlayersManager.java b/core/src/main/java/fr/skytasul/quests/players/PlayersManager.java index a98199e2..8e61b332 100644 --- a/core/src/main/java/fr/skytasul/quests/players/PlayersManager.java +++ b/core/src/main/java/fr/skytasul/quests/players/PlayersManager.java @@ -11,10 +11,10 @@ import fr.skytasul.quests.BeautyQuests; import fr.skytasul.quests.QuestsConfiguration; import fr.skytasul.quests.api.QuestsAPI; +import fr.skytasul.quests.api.events.accounts.PlayerAccountJoinEvent; +import fr.skytasul.quests.api.events.accounts.PlayerAccountLeaveEvent; import fr.skytasul.quests.players.accounts.AbstractAccount; import fr.skytasul.quests.players.accounts.UUIDAccount; -import fr.skytasul.quests.players.events.PlayerAccountJoinEvent; -import fr.skytasul.quests.players.events.PlayerAccountLeaveEvent; import fr.skytasul.quests.structure.Quest; import fr.skytasul.quests.structure.pools.QuestPool; import fr.skytasul.quests.utils.DebugUtils; diff --git a/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java b/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java index aa267f10..e4b626fc 100644 --- a/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java +++ b/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java @@ -1,650 +1,661 @@ -package fr.skytasul.quests.players; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; -import java.util.stream.Collectors; - -import org.apache.commons.lang.StringUtils; -import org.bukkit.entity.Player; -import org.bukkit.scheduler.BukkitRunnable; - -import fr.skytasul.quests.BeautyQuests; -import fr.skytasul.quests.api.stages.AbstractStage; -import fr.skytasul.quests.players.accounts.AbstractAccount; -import fr.skytasul.quests.structure.Quest; -import fr.skytasul.quests.structure.pools.QuestPool; -import fr.skytasul.quests.utils.CustomizedObjectTypeAdapter; -import fr.skytasul.quests.utils.Database; -import fr.skytasul.quests.utils.DebugUtils; - -public class PlayersManagerDB extends PlayersManager { - - private final String ACCOUNTS_TABLE; - private final String QUESTS_DATAS_TABLE; - private final String POOLS_DATAS_TABLE; - - private Database db; - - /* Accounts statements */ - private String getAccounts; - private String insertAccount; - private String deleteAccount; - - /* Quest datas statements */ - private String insertQuestData; - private String removeQuestData; - private String getQuestsData; - private String getQuestAccountData; - - private String removeExistingQuestDatas; - - private String updateFinished; - private String updateTimer; - private String updateBranch; - private String updateStage; - private String updateDatas; - private String updateFlow; - - /* Pool datas statements */ - private String insertPoolData; - private String removePoolData; - private String getPoolData; - private String getPoolAccountData; - - private String updatePoolLastGive; - private String updatePoolCompletedQuests; - - public PlayersManagerDB(Database db) { - this.db = db; - ACCOUNTS_TABLE = db.getConfig().getString("tables.playerAccounts"); - QUESTS_DATAS_TABLE = db.getConfig().getString("tables.playerQuests"); - POOLS_DATAS_TABLE = db.getConfig().getString("tables.playerPools"); - } - - private synchronized void retrievePlayerDatas(PlayerAccount acc) { - try (Connection connection = db.getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(getQuestsData)) { - statement.setInt(1, acc.index); - ResultSet result = statement.executeQuery(); - while (result.next()) { - int questID = result.getInt("quest_id"); - acc.questDatas.put(questID, new PlayerQuestDatasDB(acc, questID, result)); - } - result.close(); - } - try (PreparedStatement statement = connection.prepareStatement(getPoolData)) { - statement.setInt(1, acc.index); - ResultSet result = statement.executeQuery(); - while (result.next()) { - int poolID = result.getInt("pool_id"); - String completedQuests = result.getString("completed_quests"); - if (StringUtils.isEmpty(completedQuests)) completedQuests = null; - acc.poolDatas.put(poolID, new PlayerPoolDatasDB(acc, poolID, result.getLong("last_give"), completedQuests == null ? new HashSet<>() : Arrays.stream(completedQuests.split(";")).map(Integer::parseInt).collect(Collectors.toSet()))); - } - result.close(); - } - }catch (SQLException e) { - e.printStackTrace(); - } - } - - @Override - protected synchronized Entry load(Player player, long joinTimestamp) { - try (Connection connection = db.getConnection()) { - String uuid = player.getUniqueId().toString(); - try (PreparedStatement statement = connection.prepareStatement(getAccounts)) { - statement.setString(1, uuid); - ResultSet result = statement.executeQuery(); - while (result.next()) { - AbstractAccount abs = createAccountFromIdentifier(result.getString("identifier")); - if (abs.isCurrent()) { - PlayerAccount account = new PlayerAccount(abs, result.getInt("id")); - result.close(); - try { - // in order to ensure that, if the player was previously connected to another server, - // its datas have been fully pushed to database, we wait for 0,4 seconds - long timeout = 400 - (System.currentTimeMillis() - joinTimestamp); - if (timeout > 0) wait(timeout); - }catch (InterruptedException e) { - e.printStackTrace(); - Thread.currentThread().interrupt(); - } - retrievePlayerDatas(account); - return new AbstractMap.SimpleEntry<>(account, false); - } - } - result.close(); - } - try (PreparedStatement statement = connection.prepareStatement(insertAccount, PreparedStatement.RETURN_GENERATED_KEYS)) { - AbstractAccount absacc = super.createAbstractAccount(player); - statement.setString(1, absacc.getIdentifier()); - statement.setString(2, uuid); - statement.executeUpdate(); - ResultSet result = statement.getGeneratedKeys(); - if (!result.next()) throw new SQLException("The plugin has not been able to create a player account."); - int index = result.getInt(1); // some drivers don't return a ResultSet with correct column names - result.close(); - return new AbstractMap.SimpleEntry<>(new PlayerAccount(absacc, index), true); - } - }catch (SQLException e) { - e.printStackTrace(); - } - return null; - } - - @Override - protected synchronized void removeAccount(PlayerAccount acc) { - try (Connection connection = db.getConnection(); - PreparedStatement statement = connection.prepareStatement(deleteAccount)) { - statement.setInt(1, acc.index); - statement.executeUpdate(); - }catch (SQLException ex) { - ex.printStackTrace(); - } - } - - @Override - public PlayerQuestDatas createPlayerQuestDatas(PlayerAccount acc, Quest quest) { - return new PlayerQuestDatasDB(acc, quest.getID()); - } - - @Override - public synchronized void playerQuestDataRemoved(PlayerAccount acc, int id, PlayerQuestDatas datas) { - try (Connection connection = db.getConnection(); - PreparedStatement statement = connection.prepareStatement(removeQuestData)) { - ((PlayerQuestDatasDB) datas).stop(); - statement.setInt(1, acc.index); - statement.setInt(2, id); - statement.executeUpdate(); - }catch (SQLException e) { - e.printStackTrace(); - } - } - - @Override - public PlayerPoolDatas createPlayerPoolDatas(PlayerAccount acc, QuestPool pool) { - return new PlayerPoolDatasDB(acc, pool.getID()); - } - - @Override - public synchronized void playerPoolDataRemoved(PlayerAccount acc, int id, PlayerPoolDatas datas) { - try (Connection connection = db.getConnection(); - PreparedStatement statement = connection.prepareStatement(removePoolData)) { - statement.setInt(1, acc.index); - statement.setInt(2, id); - statement.executeUpdate(); - }catch (SQLException e) { - e.printStackTrace(); - } - } - - @Override - public synchronized int removeQuestDatas(Quest quest) { - int amount = 0; - try (Connection connection = db.getConnection(); - PreparedStatement statement = connection.prepareStatement(removeExistingQuestDatas)) { - for (PlayerAccount acc : PlayersManager.cachedAccounts.values()) { - PlayerQuestDatasDB datas = (PlayerQuestDatasDB) acc.removeQuestDatasSilently(quest.getID()); - if (datas != null) datas.stop(); - } - statement.setInt(1, quest.getID()); - amount += statement.executeUpdate(); - }catch (SQLException e) { - e.printStackTrace(); - } - DebugUtils.logMessage("Removed " + amount + " quest datas for quest " + quest.getID()); - return amount; - } - - public synchronized boolean hasAccounts(Player p) { - try (Connection connection = db.getConnection(); - PreparedStatement statement = connection.prepareStatement(getAccounts)) { - statement.setString(1, p.getUniqueId().toString()); - ResultSet result = statement.executeQuery(); - boolean has = result.next(); - result.close(); - return has; - }catch (SQLException e) { - e.printStackTrace(); - } - return false; - } - - @Override - public void load() { - try { - createTables(); - - getAccounts = "SELECT * FROM " + ACCOUNTS_TABLE + " WHERE `player_uuid` = ?"; - insertAccount = "INSERT INTO " + ACCOUNTS_TABLE + " (`identifier`, `player_uuid`) VALUES (?, ?)"; - deleteAccount = "DELETE FROM " + ACCOUNTS_TABLE + " WHERE `id` = ?"; - - insertQuestData = "INSERT INTO " + QUESTS_DATAS_TABLE + " (`account_id`, `quest_id`) VALUES (?, ?)"; - removeQuestData = "DELETE FROM " + QUESTS_DATAS_TABLE + " WHERE `account_id` = ? AND `quest_id` = ?"; - getQuestsData = "SELECT * FROM " + QUESTS_DATAS_TABLE + " WHERE `account_id` = ?"; - getQuestAccountData = "SELECT 1 FROM " + QUESTS_DATAS_TABLE + " WHERE `account_id` = ? AND `quest_id` = ?"; - - removeExistingQuestDatas = "DELETE FROM " + QUESTS_DATAS_TABLE + " WHERE `quest_id` = ?"; - - updateFinished = prepareDatasStatement("finished"); - updateTimer = prepareDatasStatement("timer"); - updateBranch = prepareDatasStatement("current_branch"); - updateStage = prepareDatasStatement("current_stage"); - updateDatas = prepareDatasStatement("additional_datas"); - updateFlow = prepareDatasStatement("quest_flow"); - - insertPoolData = "INSERT INTO " + POOLS_DATAS_TABLE + " (`account_id`, `pool_id`) VALUES (?, ?)"; - removePoolData = "DELETE FROM " + POOLS_DATAS_TABLE + " WHERE `account_id` = ? AND `pool_id` = ?"; - getPoolData = "SELECT * FROM " + POOLS_DATAS_TABLE + " WHERE `account_id` = ?"; - getPoolAccountData = "SELECT 1 FROM " + POOLS_DATAS_TABLE + " WHERE `account_id` = ? AND `pool_id` = ?"; - - updatePoolLastGive = "UPDATE " + POOLS_DATAS_TABLE + " SET `last_give` = ? WHERE `account_id` = ? AND `pool_id` = ?"; - updatePoolCompletedQuests = "UPDATE " + POOLS_DATAS_TABLE + " SET `completed_quests` = ? WHERE `account_id` = ? AND `pool_id` = ?"; - }catch (SQLException e) { - e.printStackTrace(); - } - } - - private String prepareDatasStatement(String column) throws SQLException { - return "UPDATE " + QUESTS_DATAS_TABLE + " SET `" + column + "` = ? WHERE `account_id` = ? AND `quest_id` = ?"; - } - - @Override - public void save() { - PlayersManager.cachedAccounts.values().forEach(x -> saveAccount(x, false)); - } - - private void createTables() throws SQLException { - try (Connection connection = db.getConnection(); Statement statement = connection.createStatement()) { - statement.execute("CREATE TABLE IF NOT EXISTS " + ACCOUNTS_TABLE + " (" - + " `id` int NOT NULL AUTO_INCREMENT ," - + " `identifier` text NOT NULL ," - + " `player_uuid` char(36) NOT NULL ," - + " PRIMARY KEY (`id`)" - + " )"); - statement.execute("CREATE TABLE IF NOT EXISTS " + QUESTS_DATAS_TABLE + " (" + - " `id` int NOT NULL AUTO_INCREMENT ," + - " `account_id` int(11) NOT NULL," + - " `quest_id` int(11) NOT NULL," + - " `finished` INT(11) DEFAULT NULL," + - " `timer` bigint(20) DEFAULT NULL," + - " `current_branch` tinyint(4) DEFAULT NULL," + - " `current_stage` tinyint(4) DEFAULT NULL," + - " `additional_datas` longtext DEFAULT NULL," + - " `quest_flow` VARCHAR(8000) DEFAULT NULL," + - " PRIMARY KEY (`id`)" + - ")"); - statement.execute("CREATE TABLE IF NOT EXISTS " + POOLS_DATAS_TABLE + " (" - + "`id` int NOT NULL AUTO_INCREMENT, " - + "`account_id` int(11) NOT NULL, " - + "`pool_id` int(11) NOT NULL, " - + "`last_give` bigint(20) DEFAULT NULL, " - + "`completed_quests` varchar(1000) DEFAULT NULL, " - + "PRIMARY KEY (`id`)" - + ")"); - - List columns = new ArrayList<>(14); - try (ResultSet set = connection.getMetaData().getColumns(db.getDatabase(), null, QUESTS_DATAS_TABLE, null)) { - while (set.next()) { - columns.add(set.getString("COLUMN_NAME").toLowerCase()); - } - } - if (columns.isEmpty()) { - BeautyQuests.logger.severe("Cannot check integrity of SQL table " + QUESTS_DATAS_TABLE); - }else { - if (!columns.contains("quest_flow")) { // 0.19 - statement.execute("ALTER TABLE " + QUESTS_DATAS_TABLE - + " ADD COLUMN quest_flow VARCHAR(8000) DEFAULT NULL"); - BeautyQuests.logger.info("Updated database with quest_flow column."); - } - - if (!columns.contains("additional_datas")) { // 0.20 - statement.execute("ALTER TABLE " + QUESTS_DATAS_TABLE - + " ADD COLUMN `additional_datas` longtext DEFAULT NULL AFTER `current_stage`"); - BeautyQuests.logger.info("Updated database with additional_datas column."); - - BeautyQuests.logger.info("Migrating old datas..."); - PreparedStatement migration = connection.prepareStatement("UPDATE " + QUESTS_DATAS_TABLE + " SET `additional_datas` = ? WHERE `id` = ?"); - ResultSet result = statement.executeQuery("SELECT `id`, `stage_0_datas`, `stage_1_datas`, `stage_2_datas`, `stage_3_datas`, `stage_4_datas` FROM " + QUESTS_DATAS_TABLE); - while (result.next()) { - Map datas = new HashMap<>(); - for (int i = 0; i < 5; i++) { - String stageDatas = result.getString("stage_" + i + "_datas"); - if (stageDatas != null && !"{}".equals(stageDatas)) datas.put("stage" + i, CustomizedObjectTypeAdapter.deserializeNullable(stageDatas, Map.class)); - } - - if (datas.isEmpty()) continue; - migration.setString(1, CustomizedObjectTypeAdapter.serializeNullable(datas)); - migration.setInt(2, result.getInt("id")); - migration.addBatch(); - } - int migrated = migration.executeBatch().length; - BeautyQuests.logger.info("Migrated " + migrated + " quest datas."); - - statement.execute("ALTER TABLE " + QUESTS_DATAS_TABLE - + " DROP COLUMN `stage_0_datas`," - + " DROP COLUMN `stage_1_datas`," - + " DROP COLUMN `stage_2_datas`," - + " DROP COLUMN `stage_3_datas`," - + " DROP COLUMN `stage_4_datas`;"); - BeautyQuests.logger.info("Updated database by deleting old stage_[0::4]_datas columns."); - - int deletedDuplicates = - statement.executeUpdate("DELETE R1 FROM " + QUESTS_DATAS_TABLE + " R1" - + " JOIN " + QUESTS_DATAS_TABLE + " R2" - + " ON R1.account_id = R2.account_id" - + " AND R1.quest_id = R2.quest_id" - + " AND R1.id < R2.id;"); - if (deletedDuplicates > 0) BeautyQuests.logger.info("Deleted " + deletedDuplicates + " duplicated rows in the " + QUESTS_DATAS_TABLE + " table."); - } - } - statement.execute("ALTER TABLE " + QUESTS_DATAS_TABLE + " MODIFY COLUMN finished INT(11) DEFAULT 0"); - } - } - - public static synchronized String migrate(Database db, PlayersManagerYAML yaml) throws SQLException { - try (Connection connection = db.getConnection()) { - ResultSet result = connection.getMetaData().getTables(null, null, "%", null); - while (result.next()) { - String tableName = result.getString(3); - if (tableName.equals("player_accounts") || tableName.equals("player_quests")) { - result.close(); - return "§cTable \"" + tableName + "\" already exists. Please drop it before migration."; - } - } - result.close(); - - PlayersManagerDB manager = new PlayersManagerDB(db); - manager.createTables(); - - PreparedStatement insertAccount = - connection.prepareStatement("INSERT INTO " + manager.ACCOUNTS_TABLE + " (`id`, `identifier`, `player_uuid`) VALUES (?, ?, ?)"); - PreparedStatement insertQuestData = - connection.prepareStatement("INSERT INTO " + manager.QUESTS_DATAS_TABLE - + " (`account_id`, `quest_id`, `finished`, `timer`, `current_branch`, `current_stage`, `stage_0_datas`, `stage_1_datas`, `stage_2_datas`, `stage_3_datas`, `stage_4_datas`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); - PreparedStatement insertPoolData = - connection.prepareStatement("INSERT INTO " + manager.POOLS_DATAS_TABLE + " (`account_id`, `pool_id`, `last_give`, `completed_quests`) VALUES (?, ?, ?, ?)"); - - int amount = 0, failed = 0; - yaml.loadAllAccounts(); - for (PlayerAccount acc : yaml.loadedAccounts.values()) { - try { - insertAccount.setInt(1, acc.index); - insertAccount.setString(2, acc.abstractAcc.getIdentifier()); - insertAccount.setString(3, acc.getOfflinePlayer().getUniqueId().toString()); - insertAccount.executeUpdate(); - - for (Entry entry : acc.questDatas.entrySet()) { - insertQuestData.setInt(1, acc.index); - insertQuestData.setInt(2, entry.getKey()); - insertQuestData.setInt(3, entry.getValue().getTimesFinished()); - insertQuestData.setLong(4, entry.getValue().getTimer()); - insertQuestData.setInt(5, entry.getValue().getBranch()); - insertQuestData.setInt(6, entry.getValue().getStage()); - for (int i = 0; i < 5; i++) { - Map stageDatas = entry.getValue().getStageDatas(i); - insertQuestData.setString(7 + i, stageDatas == null ? null : CustomizedObjectTypeAdapter.GSON.toJson(stageDatas)); - } - insertQuestData.executeUpdate(); - } - - for (Entry entry : acc.poolDatas.entrySet()) { - insertPoolData.setInt(1, acc.index); - insertPoolData.setInt(2, entry.getKey()); - insertPoolData.setLong(3, entry.getValue().getLastGive()); - insertPoolData.setString(4, getCompletedQuestsString(entry.getValue().getCompletedQuests())); - insertPoolData.executeUpdate(); - } - - amount++; - }catch (Exception ex) { - BeautyQuests.logger.severe("Failed to migrate datas for account " + acc.debugName(), ex); - failed++; - } - } - - insertAccount.close(); - insertQuestData.close(); - insertPoolData.close(); - - return "§aMigration succeed! " + amount + " accounts migrated, " + failed + " accounts failed to migrate.\n§oDatabase saving system is §lnot§r§a§o enabled. You need to reboot the server with the line \"database.enabled\" set to true."; - } - } - - @Override - public void unloadAccount(PlayerAccount acc) { - saveAccount(acc, true); - } - - public void saveAccount(PlayerAccount acc, boolean stop) { - acc.getQuestsDatas() - .stream() - .map(PlayerQuestDatasDB.class::cast) - .forEach(x -> x.flushAll(stop)); - } - - protected static String getCompletedQuestsString(Set completedQuests) { - return completedQuests.isEmpty() ? null : completedQuests.stream().map(x -> Integer.toString(x)).collect(Collectors.joining(";")); - } - - public class PlayerQuestDatasDB extends PlayerQuestDatas { - - private static final int DATA_FLUSHING_TIME = 10; - - private Map> cachedDatas = new HashMap<>(5); - private Lock datasLock = new ReentrantLock(); - private Lock dbLock = new ReentrantLock(); - private boolean disabled = false; - - public PlayerQuestDatasDB(PlayerAccount acc, int questID) { - super(acc, questID); - } - - public PlayerQuestDatasDB(PlayerAccount acc, int questID, ResultSet result) throws SQLException { - super( - acc, - questID, - result.getLong("timer"), - result.getInt("finished"), - result.getInt("current_branch"), - result.getInt("current_stage"), - CustomizedObjectTypeAdapter.deserializeNullable(result.getString("additional_datas"), Map.class), - result.getString("quest_flow")); - } - - @Override - public void incrementFinished() { - super.incrementFinished(); - setDataStatement(updateFinished, getTimesFinished(), false); - } - - @Override - public void setTimer(long timer) { - super.setTimer(timer); - setDataStatement(updateTimer, timer, false); - } - - @Override - public void setBranch(int branch) { - super.setBranch(branch); - setDataStatement(updateBranch, branch, false); - } - - @Override - public void setStage(int stage) { - super.setStage(stage); - setDataStatement(updateStage, stage, false); - } - - @Override - public T setAdditionalData(String key, T value) { - T additionalData = super.setAdditionalData(key, value); - setDataStatement(updateDatas, super.additionalDatas.isEmpty() ? null : CustomizedObjectTypeAdapter.serializeNullable(super.additionalDatas), true); - return additionalData; - } - - @Override - public void addQuestFlow(AbstractStage finished) { - super.addQuestFlow(finished); - setDataStatement(updateFlow, getQuestFlow(), true); - } - - @Override - public void resetQuestFlow() { - super.resetQuestFlow(); - setDataStatement(updateFlow, null, true); - } - - private void setDataStatement(String dataStatement, Object data, boolean allowNull) { - if (disabled) return; - try { - datasLock.lock(); - if (disabled) { - // in case disabled while acquiring lock - }else if (cachedDatas.containsKey(dataStatement)) { - cachedDatas.get(dataStatement).setValue(data); - }else { - BukkitRunnable runnable = new BukkitRunnable() { - - @Override - public void run() { - if (disabled) return; - Entry entry = null; - datasLock.lock(); - try { - if (!disabled) { // in case disabled while acquiring lock - entry = cachedDatas.remove(dataStatement); - } - }finally { - datasLock.unlock(); - } - if (entry != null) { - dbLock.lock(); - try (Connection connection = db.getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(getQuestAccountData)) { - statement.setInt(1, acc.index); - statement.setInt(2, questID); - if (!statement.executeQuery().next()) { // if result set empty => need to insert data then update - try (PreparedStatement insertStatement = connection.prepareStatement(insertQuestData)) { - insertStatement.setInt(1, acc.index); - insertStatement.setInt(2, questID); - insertStatement.executeUpdate(); - DebugUtils.logMessage("Inserting DB row of quest " + questID + " for account " + acc.index); - } - } - } - try (PreparedStatement statement = connection.prepareStatement(dataStatement)) { - statement.setObject(1, entry.getValue()); - statement.setInt(2, acc.index); - statement.setInt(3, questID); - statement.executeUpdate(); - if (entry.getValue() == null && !allowNull) { - BeautyQuests.logger.warning("Setting an illegal NULL value in statement \"" + dataStatement + "\" for account " + acc.index + " and quest " + questID); - } - } - }catch (SQLException e) { - e.printStackTrace(); - }finally { - dbLock.unlock(); - } - } - } - }; - runnable.runTaskLaterAsynchronously(BeautyQuests.getInstance(), DATA_FLUSHING_TIME); - cachedDatas.put(dataStatement, new AbstractMap.SimpleEntry<>(runnable, data)); - } - }finally { - datasLock.unlock(); - } - } - - protected void flushAll(boolean stop) { - datasLock.lock(); - cachedDatas.values() - .stream() - .map(Entry::getKey) - .collect(Collectors.toList()) // to prevent ConcurrentModificationException - .forEach(run -> { - run.run(); - run.cancel(); - }); - if (!cachedDatas.isEmpty()) BeautyQuests.logger.warning("Still waiting values in quest data " + questID + " for account " + acc.index + " despite flushing all."); - if (stop) disabled = true; - datasLock.unlock(); - } - - protected void stop() { - disabled = true; - datasLock.lock(); - cachedDatas.values() - .stream() - .map(Entry::getKey) - .forEach(BukkitRunnable::cancel); - cachedDatas.clear(); - datasLock.unlock(); - } - - } - - public class PlayerPoolDatasDB extends PlayerPoolDatas { - - public PlayerPoolDatasDB(PlayerAccount acc, int poolID) { - super(acc, poolID); - } - - public PlayerPoolDatasDB(PlayerAccount acc, int poolID, long lastGive, Set completedQuests) { - super(acc, poolID, lastGive, completedQuests); - } - - @Override - public void setLastGive(long lastGive) { - super.setLastGive(lastGive); - updateData(updatePoolLastGive, lastGive); - } - - @Override - public void updatedCompletedQuests() { - updateData(updatePoolCompletedQuests, getCompletedQuestsString(getCompletedQuests())); - } - - private void updateData(String dataStatement, Object data) { - try (Connection connection = db.getConnection()) { - try (PreparedStatement statement = connection.prepareStatement(getPoolAccountData)) { - statement.setInt(1, acc.index); - statement.setInt(2, poolID); - if (!statement.executeQuery().next()) { // if result set empty => need to insert data then update - try (PreparedStatement insertStatement = connection.prepareStatement(insertPoolData)) { - insertStatement.setInt(1, acc.index); - insertStatement.setInt(2, poolID); - insertStatement.executeUpdate(); - } - } - } - try (PreparedStatement statement = connection.prepareStatement(dataStatement)) { - statement.setObject(1, data); - statement.setInt(2, acc.index); - statement.setInt(3, poolID); - statement.executeUpdate(); - } - }catch (SQLException e) { - e.printStackTrace(); - } - } - - } - -} +package fr.skytasul.quests.players; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; + +import org.apache.commons.lang.StringUtils; +import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitRunnable; + +import fr.skytasul.quests.BeautyQuests; +import fr.skytasul.quests.api.stages.AbstractStage; +import fr.skytasul.quests.players.accounts.AbstractAccount; +import fr.skytasul.quests.structure.Quest; +import fr.skytasul.quests.structure.pools.QuestPool; +import fr.skytasul.quests.utils.CustomizedObjectTypeAdapter; +import fr.skytasul.quests.utils.Database; +import fr.skytasul.quests.utils.DebugUtils; +import fr.skytasul.quests.utils.ThrowingConsumer; + +public class PlayersManagerDB extends PlayersManager { + + public final String ACCOUNTS_TABLE; + public final String QUESTS_DATAS_TABLE; + public final String POOLS_DATAS_TABLE; + + private Database db; + + /* Accounts statements */ + private String getAccounts; + private String insertAccount; + private String deleteAccount; + + /* Quest datas statements */ + private String insertQuestData; + private String removeQuestData; + private String getQuestsData; + private String getQuestAccountData; + + private String removeExistingQuestDatas; + + private String updateFinished; + private String updateTimer; + private String updateBranch; + private String updateStage; + private String updateDatas; + private String updateFlow; + + /* Pool datas statements */ + private String insertPoolData; + private String removePoolData; + private String getPoolData; + private String getPoolAccountData; + + private String updatePoolLastGive; + private String updatePoolCompletedQuests; + + public PlayersManagerDB(Database db) { + this.db = db; + ACCOUNTS_TABLE = db.getConfig().getString("tables.playerAccounts"); + QUESTS_DATAS_TABLE = db.getConfig().getString("tables.playerQuests"); + POOLS_DATAS_TABLE = db.getConfig().getString("tables.playerPools"); + } + + public Database getDatabase() { + return db; + } + + private synchronized void retrievePlayerDatas(PlayerAccount acc) { + try (Connection connection = db.getConnection()) { + try (PreparedStatement statement = connection.prepareStatement(getQuestsData)) { + statement.setInt(1, acc.index); + ResultSet result = statement.executeQuery(); + while (result.next()) { + int questID = result.getInt("quest_id"); + acc.questDatas.put(questID, new PlayerQuestDatasDB(acc, questID, result)); + } + result.close(); + } + try (PreparedStatement statement = connection.prepareStatement(getPoolData)) { + statement.setInt(1, acc.index); + ResultSet result = statement.executeQuery(); + while (result.next()) { + int poolID = result.getInt("pool_id"); + String completedQuests = result.getString("completed_quests"); + if (StringUtils.isEmpty(completedQuests)) completedQuests = null; + acc.poolDatas.put(poolID, new PlayerPoolDatasDB(acc, poolID, result.getLong("last_give"), completedQuests == null ? new HashSet<>() : Arrays.stream(completedQuests.split(";")).map(Integer::parseInt).collect(Collectors.toSet()))); + } + result.close(); + } + }catch (SQLException e) { + e.printStackTrace(); + } + } + + @Override + protected synchronized Entry load(Player player, long joinTimestamp) { + try (Connection connection = db.getConnection()) { + String uuid = player.getUniqueId().toString(); + try (PreparedStatement statement = connection.prepareStatement(getAccounts)) { + statement.setString(1, uuid); + ResultSet result = statement.executeQuery(); + while (result.next()) { + AbstractAccount abs = createAccountFromIdentifier(result.getString("identifier")); + if (abs.isCurrent()) { + PlayerAccount account = new PlayerAccount(abs, result.getInt("id")); + result.close(); + try { + // in order to ensure that, if the player was previously connected to another server, + // its datas have been fully pushed to database, we wait for 0,4 seconds + long timeout = 400 - (System.currentTimeMillis() - joinTimestamp); + if (timeout > 0) wait(timeout); + }catch (InterruptedException e) { + e.printStackTrace(); + Thread.currentThread().interrupt(); + } + retrievePlayerDatas(account); + return new AbstractMap.SimpleEntry<>(account, false); + } + } + result.close(); + } + try (PreparedStatement statement = connection.prepareStatement(insertAccount, PreparedStatement.RETURN_GENERATED_KEYS)) { + AbstractAccount absacc = super.createAbstractAccount(player); + statement.setString(1, absacc.getIdentifier()); + statement.setString(2, uuid); + statement.executeUpdate(); + ResultSet result = statement.getGeneratedKeys(); + if (!result.next()) throw new SQLException("The plugin has not been able to create a player account."); + int index = result.getInt(1); // some drivers don't return a ResultSet with correct column names + result.close(); + return new AbstractMap.SimpleEntry<>(new PlayerAccount(absacc, index), true); + } + }catch (SQLException e) { + e.printStackTrace(); + } + return null; + } + + @Override + protected synchronized void removeAccount(PlayerAccount acc) { + try (Connection connection = db.getConnection(); + PreparedStatement statement = connection.prepareStatement(deleteAccount)) { + statement.setInt(1, acc.index); + statement.executeUpdate(); + }catch (SQLException ex) { + ex.printStackTrace(); + } + } + + @Override + public PlayerQuestDatas createPlayerQuestDatas(PlayerAccount acc, Quest quest) { + return new PlayerQuestDatasDB(acc, quest.getID()); + } + + @Override + public synchronized void playerQuestDataRemoved(PlayerAccount acc, int id, PlayerQuestDatas datas) { + try (Connection connection = db.getConnection(); + PreparedStatement statement = connection.prepareStatement(removeQuestData)) { + ((PlayerQuestDatasDB) datas).stop(); + statement.setInt(1, acc.index); + statement.setInt(2, id); + statement.executeUpdate(); + }catch (SQLException e) { + e.printStackTrace(); + } + } + + @Override + public PlayerPoolDatas createPlayerPoolDatas(PlayerAccount acc, QuestPool pool) { + return new PlayerPoolDatasDB(acc, pool.getID()); + } + + @Override + public synchronized void playerPoolDataRemoved(PlayerAccount acc, int id, PlayerPoolDatas datas) { + try (Connection connection = db.getConnection(); + PreparedStatement statement = connection.prepareStatement(removePoolData)) { + statement.setInt(1, acc.index); + statement.setInt(2, id); + statement.executeUpdate(); + }catch (SQLException e) { + e.printStackTrace(); + } + } + + @Override + public synchronized int removeQuestDatas(Quest quest) { + int amount = 0; + try (Connection connection = db.getConnection(); + PreparedStatement statement = connection.prepareStatement(removeExistingQuestDatas)) { + for (PlayerAccount acc : PlayersManager.cachedAccounts.values()) { + PlayerQuestDatasDB datas = (PlayerQuestDatasDB) acc.removeQuestDatasSilently(quest.getID()); + if (datas != null) datas.stop(); + } + statement.setInt(1, quest.getID()); + amount += statement.executeUpdate(); + }catch (SQLException e) { + e.printStackTrace(); + } + DebugUtils.logMessage("Removed " + amount + " quest datas for quest " + quest.getID()); + return amount; + } + + public synchronized boolean hasAccounts(Player p) { + try (Connection connection = db.getConnection(); + PreparedStatement statement = connection.prepareStatement(getAccounts)) { + statement.setString(1, p.getUniqueId().toString()); + ResultSet result = statement.executeQuery(); + boolean has = result.next(); + result.close(); + return has; + }catch (SQLException e) { + e.printStackTrace(); + } + return false; + } + + @Override + public void load() { + try { + createTables(); + + getAccounts = "SELECT * FROM " + ACCOUNTS_TABLE + " WHERE `player_uuid` = ?"; + insertAccount = "INSERT INTO " + ACCOUNTS_TABLE + " (`identifier`, `player_uuid`) VALUES (?, ?)"; + deleteAccount = "DELETE FROM " + ACCOUNTS_TABLE + " WHERE `id` = ?"; + + insertQuestData = "INSERT INTO " + QUESTS_DATAS_TABLE + " (`account_id`, `quest_id`) VALUES (?, ?)"; + removeQuestData = "DELETE FROM " + QUESTS_DATAS_TABLE + " WHERE `account_id` = ? AND `quest_id` = ?"; + getQuestsData = "SELECT * FROM " + QUESTS_DATAS_TABLE + " WHERE `account_id` = ?"; + getQuestAccountData = "SELECT 1 FROM " + QUESTS_DATAS_TABLE + " WHERE `account_id` = ? AND `quest_id` = ?"; + + removeExistingQuestDatas = "DELETE FROM " + QUESTS_DATAS_TABLE + " WHERE `quest_id` = ?"; + + updateFinished = prepareDatasStatement("finished"); + updateTimer = prepareDatasStatement("timer"); + updateBranch = prepareDatasStatement("current_branch"); + updateStage = prepareDatasStatement("current_stage"); + updateDatas = prepareDatasStatement("additional_datas"); + updateFlow = prepareDatasStatement("quest_flow"); + + insertPoolData = "INSERT INTO " + POOLS_DATAS_TABLE + " (`account_id`, `pool_id`) VALUES (?, ?)"; + removePoolData = "DELETE FROM " + POOLS_DATAS_TABLE + " WHERE `account_id` = ? AND `pool_id` = ?"; + getPoolData = "SELECT * FROM " + POOLS_DATAS_TABLE + " WHERE `account_id` = ?"; + getPoolAccountData = "SELECT 1 FROM " + POOLS_DATAS_TABLE + " WHERE `account_id` = ? AND `pool_id` = ?"; + + updatePoolLastGive = "UPDATE " + POOLS_DATAS_TABLE + " SET `last_give` = ? WHERE `account_id` = ? AND `pool_id` = ?"; + updatePoolCompletedQuests = "UPDATE " + POOLS_DATAS_TABLE + " SET `completed_quests` = ? WHERE `account_id` = ? AND `pool_id` = ?"; + }catch (SQLException e) { + e.printStackTrace(); + } + } + + private String prepareDatasStatement(String column) throws SQLException { + return "UPDATE " + QUESTS_DATAS_TABLE + " SET `" + column + "` = ? WHERE `account_id` = ? AND `quest_id` = ?"; + } + + @Override + public void save() { + PlayersManager.cachedAccounts.values().forEach(x -> saveAccount(x, false)); + } + + private void createTables() throws SQLException { + try (Connection connection = db.getConnection(); Statement statement = connection.createStatement()) { + statement.execute("CREATE TABLE IF NOT EXISTS " + ACCOUNTS_TABLE + " (" + + " `id` int NOT NULL AUTO_INCREMENT ," + + " `identifier` text NOT NULL ," + + " `player_uuid` char(36) NOT NULL ," + + " PRIMARY KEY (`id`)" + + " )"); + statement.execute("CREATE TABLE IF NOT EXISTS " + QUESTS_DATAS_TABLE + " (" + + " `id` int NOT NULL AUTO_INCREMENT ," + + " `account_id` int(11) NOT NULL," + + " `quest_id` int(11) NOT NULL," + + " `finished` INT(11) DEFAULT NULL," + + " `timer` bigint(20) DEFAULT NULL," + + " `current_branch` tinyint(4) DEFAULT NULL," + + " `current_stage` tinyint(4) DEFAULT NULL," + + " `additional_datas` longtext DEFAULT NULL," + + " `quest_flow` VARCHAR(8000) DEFAULT NULL," + + " PRIMARY KEY (`id`)" + + ")"); + statement.execute("CREATE TABLE IF NOT EXISTS " + POOLS_DATAS_TABLE + " (" + + "`id` int NOT NULL AUTO_INCREMENT, " + + "`account_id` int(11) NOT NULL, " + + "`pool_id` int(11) NOT NULL, " + + "`last_give` bigint(20) DEFAULT NULL, " + + "`completed_quests` varchar(1000) DEFAULT NULL, " + + "PRIMARY KEY (`id`)" + + ")"); + + upgradeTable(connection, QUESTS_DATAS_TABLE, columns -> { + if (!columns.contains("quest_flow")) { // 0.19 + statement.execute("ALTER TABLE " + QUESTS_DATAS_TABLE + + " ADD COLUMN quest_flow VARCHAR(8000) DEFAULT NULL"); + BeautyQuests.logger.info("Updated database with quest_flow column."); + } + + if (!columns.contains("additional_datas")) { // 0.20 + statement.execute("ALTER TABLE " + QUESTS_DATAS_TABLE + + " ADD COLUMN `additional_datas` longtext DEFAULT NULL AFTER `current_stage`"); + BeautyQuests.logger.info("Updated table " + QUESTS_DATAS_TABLE + " with additional_datas column."); + + BeautyQuests.logger.info("Migrating old datas..."); + PreparedStatement migration = connection.prepareStatement("UPDATE " + QUESTS_DATAS_TABLE + " SET `additional_datas` = ? WHERE `id` = ?"); + ResultSet result = statement.executeQuery("SELECT `id`, `stage_0_datas`, `stage_1_datas`, `stage_2_datas`, `stage_3_datas`, `stage_4_datas` FROM " + QUESTS_DATAS_TABLE); + while (result.next()) { + Map datas = new HashMap<>(); + for (int i = 0; i < 5; i++) { + String stageDatas = result.getString("stage_" + i + "_datas"); + if (stageDatas != null && !"{}".equals(stageDatas)) datas.put("stage" + i, CustomizedObjectTypeAdapter.deserializeNullable(stageDatas, Map.class)); + } + + if (datas.isEmpty()) continue; + migration.setString(1, CustomizedObjectTypeAdapter.serializeNullable(datas)); + migration.setInt(2, result.getInt("id")); + migration.addBatch(); + } + int migrated = migration.executeBatch().length; + BeautyQuests.logger.info("Migrated " + migrated + " quest datas."); + + statement.execute("ALTER TABLE " + QUESTS_DATAS_TABLE + + " DROP COLUMN `stage_0_datas`," + + " DROP COLUMN `stage_1_datas`," + + " DROP COLUMN `stage_2_datas`," + + " DROP COLUMN `stage_3_datas`," + + " DROP COLUMN `stage_4_datas`;"); + BeautyQuests.logger.info("Updated database by deleting old stage_[0::4]_datas columns."); + + int deletedDuplicates = + statement.executeUpdate("DELETE R1 FROM " + QUESTS_DATAS_TABLE + " R1" + + " JOIN " + QUESTS_DATAS_TABLE + " R2" + + " ON R1.account_id = R2.account_id" + + " AND R1.quest_id = R2.quest_id" + + " AND R1.id < R2.id;"); + if (deletedDuplicates > 0) BeautyQuests.logger.info("Deleted " + deletedDuplicates + " duplicated rows in the " + QUESTS_DATAS_TABLE + " table."); + } + }); + statement.execute("ALTER TABLE " + QUESTS_DATAS_TABLE + " MODIFY COLUMN finished INT(11) DEFAULT 0"); + } + } + + private void upgradeTable(Connection connection, String tableName, ThrowingConsumer, SQLException> columnsConsumer) throws SQLException { + List columns = new ArrayList<>(14); + try (ResultSet set = connection.getMetaData().getColumns(db.getDatabase(), null, tableName, null)) { + while (set.next()) { + columns.add(set.getString("COLUMN_NAME").toLowerCase()); + } + } + if (columns.isEmpty()) { + BeautyQuests.logger.severe("Cannot check integrity of SQL table " + tableName); + }else { + columnsConsumer.accept(columns); + } + } + + public static synchronized String migrate(Database db, PlayersManagerYAML yaml) throws SQLException { + try (Connection connection = db.getConnection()) { + ResultSet result = connection.getMetaData().getTables(null, null, "%", null); + while (result.next()) { + String tableName = result.getString(3); + if (tableName.equals("player_accounts") || tableName.equals("player_quests")) { + result.close(); + return "§cTable \"" + tableName + "\" already exists. Please drop it before migration."; + } + } + result.close(); + + PlayersManagerDB manager = new PlayersManagerDB(db); + manager.createTables(); + + PreparedStatement insertAccount = + connection.prepareStatement("INSERT INTO " + manager.ACCOUNTS_TABLE + " (`id`, `identifier`, `player_uuid`) VALUES (?, ?, ?)"); + PreparedStatement insertQuestData = + connection.prepareStatement("INSERT INTO " + manager.QUESTS_DATAS_TABLE + + " (`account_id`, `quest_id`, `finished`, `timer`, `current_branch`, `current_stage`, `stage_0_datas`, `stage_1_datas`, `stage_2_datas`, `stage_3_datas`, `stage_4_datas`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"); + PreparedStatement insertPoolData = + connection.prepareStatement("INSERT INTO " + manager.POOLS_DATAS_TABLE + " (`account_id`, `pool_id`, `last_give`, `completed_quests`) VALUES (?, ?, ?, ?)"); + + int amount = 0, failed = 0; + yaml.loadAllAccounts(); + for (PlayerAccount acc : yaml.loadedAccounts.values()) { + try { + insertAccount.setInt(1, acc.index); + insertAccount.setString(2, acc.abstractAcc.getIdentifier()); + insertAccount.setString(3, acc.getOfflinePlayer().getUniqueId().toString()); + insertAccount.executeUpdate(); + + for (Entry entry : acc.questDatas.entrySet()) { + insertQuestData.setInt(1, acc.index); + insertQuestData.setInt(2, entry.getKey()); + insertQuestData.setInt(3, entry.getValue().getTimesFinished()); + insertQuestData.setLong(4, entry.getValue().getTimer()); + insertQuestData.setInt(5, entry.getValue().getBranch()); + insertQuestData.setInt(6, entry.getValue().getStage()); + for (int i = 0; i < 5; i++) { + Map stageDatas = entry.getValue().getStageDatas(i); + insertQuestData.setString(7 + i, stageDatas == null ? null : CustomizedObjectTypeAdapter.GSON.toJson(stageDatas)); + } + insertQuestData.executeUpdate(); + } + + for (Entry entry : acc.poolDatas.entrySet()) { + insertPoolData.setInt(1, acc.index); + insertPoolData.setInt(2, entry.getKey()); + insertPoolData.setLong(3, entry.getValue().getLastGive()); + insertPoolData.setString(4, getCompletedQuestsString(entry.getValue().getCompletedQuests())); + insertPoolData.executeUpdate(); + } + + amount++; + }catch (Exception ex) { + BeautyQuests.logger.severe("Failed to migrate datas for account " + acc.debugName(), ex); + failed++; + } + } + + insertAccount.close(); + insertQuestData.close(); + insertPoolData.close(); + + return "§aMigration succeed! " + amount + " accounts migrated, " + failed + " accounts failed to migrate.\n§oDatabase saving system is §lnot§r§a§o enabled. You need to reboot the server with the line \"database.enabled\" set to true."; + } + } + + @Override + public void unloadAccount(PlayerAccount acc) { + saveAccount(acc, true); + } + + public void saveAccount(PlayerAccount acc, boolean stop) { + acc.getQuestsDatas() + .stream() + .map(PlayerQuestDatasDB.class::cast) + .forEach(x -> x.flushAll(stop)); + } + + protected static String getCompletedQuestsString(Set completedQuests) { + return completedQuests.isEmpty() ? null : completedQuests.stream().map(x -> Integer.toString(x)).collect(Collectors.joining(";")); + } + + public class PlayerQuestDatasDB extends PlayerQuestDatas { + + private static final int DATA_FLUSHING_TIME = 10; + + private Map> cachedDatas = new HashMap<>(5); + private Lock datasLock = new ReentrantLock(); + private Lock dbLock = new ReentrantLock(); + private boolean disabled = false; + + public PlayerQuestDatasDB(PlayerAccount acc, int questID) { + super(acc, questID); + } + + public PlayerQuestDatasDB(PlayerAccount acc, int questID, ResultSet result) throws SQLException { + super( + acc, + questID, + result.getLong("timer"), + result.getInt("finished"), + result.getInt("current_branch"), + result.getInt("current_stage"), + CustomizedObjectTypeAdapter.deserializeNullable(result.getString("additional_datas"), Map.class), + result.getString("quest_flow")); + } + + @Override + public void incrementFinished() { + super.incrementFinished(); + setDataStatement(updateFinished, getTimesFinished(), false); + } + + @Override + public void setTimer(long timer) { + super.setTimer(timer); + setDataStatement(updateTimer, timer, false); + } + + @Override + public void setBranch(int branch) { + super.setBranch(branch); + setDataStatement(updateBranch, branch, false); + } + + @Override + public void setStage(int stage) { + super.setStage(stage); + setDataStatement(updateStage, stage, false); + } + + @Override + public T setAdditionalData(String key, T value) { + T additionalData = super.setAdditionalData(key, value); + setDataStatement(updateDatas, super.additionalDatas.isEmpty() ? null : CustomizedObjectTypeAdapter.serializeNullable(super.additionalDatas), true); + return additionalData; + } + + @Override + public void addQuestFlow(AbstractStage finished) { + super.addQuestFlow(finished); + setDataStatement(updateFlow, getQuestFlow(), true); + } + + @Override + public void resetQuestFlow() { + super.resetQuestFlow(); + setDataStatement(updateFlow, null, true); + } + + private void setDataStatement(String dataStatement, Object data, boolean allowNull) { + if (disabled) return; + try { + datasLock.lock(); + if (disabled) { + // in case disabled while acquiring lock + }else if (cachedDatas.containsKey(dataStatement)) { + cachedDatas.get(dataStatement).setValue(data); + }else { + BukkitRunnable runnable = new BukkitRunnable() { + + @Override + public void run() { + if (disabled) return; + Entry entry = null; + datasLock.lock(); + try { + if (!disabled) { // in case disabled while acquiring lock + entry = cachedDatas.remove(dataStatement); + } + }finally { + datasLock.unlock(); + } + if (entry != null) { + dbLock.lock(); + try (Connection connection = db.getConnection()) { + try (PreparedStatement statement = connection.prepareStatement(getQuestAccountData)) { + statement.setInt(1, acc.index); + statement.setInt(2, questID); + if (!statement.executeQuery().next()) { // if result set empty => need to insert data then update + try (PreparedStatement insertStatement = connection.prepareStatement(insertQuestData)) { + insertStatement.setInt(1, acc.index); + insertStatement.setInt(2, questID); + insertStatement.executeUpdate(); + DebugUtils.logMessage("Inserting DB row of quest " + questID + " for account " + acc.index); + } + } + } + try (PreparedStatement statement = connection.prepareStatement(dataStatement)) { + statement.setObject(1, entry.getValue()); + statement.setInt(2, acc.index); + statement.setInt(3, questID); + statement.executeUpdate(); + if (entry.getValue() == null && !allowNull) { + BeautyQuests.logger.warning("Setting an illegal NULL value in statement \"" + dataStatement + "\" for account " + acc.index + " and quest " + questID); + } + } + }catch (SQLException e) { + e.printStackTrace(); + }finally { + dbLock.unlock(); + } + } + } + }; + runnable.runTaskLaterAsynchronously(BeautyQuests.getInstance(), DATA_FLUSHING_TIME); + cachedDatas.put(dataStatement, new AbstractMap.SimpleEntry<>(runnable, data)); + } + }finally { + datasLock.unlock(); + } + } + + protected void flushAll(boolean stop) { + datasLock.lock(); + cachedDatas.values() + .stream() + .map(Entry::getKey) + .collect(Collectors.toList()) // to prevent ConcurrentModificationException + .forEach(run -> { + run.run(); + run.cancel(); + }); + if (!cachedDatas.isEmpty()) BeautyQuests.logger.warning("Still waiting values in quest data " + questID + " for account " + acc.index + " despite flushing all."); + if (stop) disabled = true; + datasLock.unlock(); + } + + protected void stop() { + disabled = true; + datasLock.lock(); + cachedDatas.values() + .stream() + .map(Entry::getKey) + .forEach(BukkitRunnable::cancel); + cachedDatas.clear(); + datasLock.unlock(); + } + + } + + public class PlayerPoolDatasDB extends PlayerPoolDatas { + + public PlayerPoolDatasDB(PlayerAccount acc, int poolID) { + super(acc, poolID); + } + + public PlayerPoolDatasDB(PlayerAccount acc, int poolID, long lastGive, Set completedQuests) { + super(acc, poolID, lastGive, completedQuests); + } + + @Override + public void setLastGive(long lastGive) { + super.setLastGive(lastGive); + updateData(updatePoolLastGive, lastGive); + } + + @Override + public void updatedCompletedQuests() { + updateData(updatePoolCompletedQuests, getCompletedQuestsString(getCompletedQuests())); + } + + private void updateData(String dataStatement, Object data) { + try (Connection connection = db.getConnection()) { + try (PreparedStatement statement = connection.prepareStatement(getPoolAccountData)) { + statement.setInt(1, acc.index); + statement.setInt(2, poolID); + if (!statement.executeQuery().next()) { // if result set empty => need to insert data then update + try (PreparedStatement insertStatement = connection.prepareStatement(insertPoolData)) { + insertStatement.setInt(1, acc.index); + insertStatement.setInt(2, poolID); + insertStatement.executeUpdate(); + } + } + } + try (PreparedStatement statement = connection.prepareStatement(dataStatement)) { + statement.setObject(1, data); + statement.setInt(2, acc.index); + statement.setInt(3, poolID); + statement.executeUpdate(); + } + }catch (SQLException e) { + e.printStackTrace(); + } + } + + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/players/PlayersManagerYAML.java b/core/src/main/java/fr/skytasul/quests/players/PlayersManagerYAML.java index 37a2205b..286a54e7 100644 --- a/core/src/main/java/fr/skytasul/quests/players/PlayersManagerYAML.java +++ b/core/src/main/java/fr/skytasul/quests/players/PlayersManagerYAML.java @@ -1,316 +1,316 @@ -package fr.skytasul.quests.players; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.concurrent.TimeUnit; - -import org.apache.commons.lang.Validate; -import org.bukkit.Bukkit; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; - -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; - -import fr.skytasul.quests.BeautyQuests; -import fr.skytasul.quests.players.accounts.AbstractAccount; -import fr.skytasul.quests.players.accounts.GhostAccount; -import fr.skytasul.quests.structure.Quest; -import fr.skytasul.quests.structure.pools.QuestPool; -import fr.skytasul.quests.utils.DebugUtils; -import fr.skytasul.quests.utils.Utils; - -public class PlayersManagerYAML extends PlayersManager { - - private static final int ACCOUNTS_THRESHOLD = 1000; - - Map loadedAccounts = new HashMap<>(); - private Map identifiersIndex = Collections.synchronizedMap(new HashMap<>()); - private int lastAccountID = 0; - - private Cache unloadedAccounts = CacheBuilder.newBuilder().expireAfterWrite(2, TimeUnit.MINUTES).build(); - - private File directory = new File(BeautyQuests.getInstance().getDataFolder(), "players"); - - public File getDirectory() { - return directory; - } - - @Override - protected Entry load(Player player, long joinTimestamp) { - String identifier = super.getIdentifier(player); - if (identifiersIndex.containsValue(identifier)) { - int id = Utils.getKeyByValue(identifiersIndex, identifier); - return new AbstractMap.SimpleEntry<>(getByIndex(id), false); - } - - AbstractAccount absacc = super.createAbstractAccount(player); - PlayerAccount acc = new PlayerAccount(absacc, lastAccountID + 1); - addAccount(acc); - - return new AbstractMap.SimpleEntry<>(acc, true); - } - - @Override - protected void removeAccount(PlayerAccount acc) { - loadedAccounts.remove(acc.index); - identifiersIndex.remove(acc.index); - removePlayerFile(acc.index); - } - - @Override - public PlayerQuestDatas createPlayerQuestDatas(PlayerAccount acc, Quest quest) { - return new PlayerQuestDatas(acc, quest.getID()); - } - - @Override - public PlayerPoolDatas createPlayerPoolDatas(PlayerAccount acc, QuestPool pool) { - return new PlayerPoolDatas(acc, pool.getID()); - } - - @Override - public int removeQuestDatas(Quest quest) { - loadAllAccounts(); - int amount = 0; - - for (PlayerAccount account : loadedAccounts.values()) { - if (account.removeQuestDatas(quest) != null) amount++; - } - - return amount; - } - - public boolean hasAccounts(Player p) { - return identifiersIndex.containsValue(getIdentifier(p)); - } - - private synchronized PlayerAccount createPlayerAccount(String identifier, int index) { - Validate.notNull(identifier, "Identifier cannot be null (index: " + index + ")"); - AbstractAccount abs = super.createAccountFromIdentifier(identifier); - if (abs == null) { - BeautyQuests.logger.info("Player account with identifier " + identifier + " is not enabled, but will be kept in the data file."); - return new PlayerAccount(new GhostAccount(identifier), index); - } - return new PlayerAccount(abs, index); - } - - void loadAllAccounts() { - BeautyQuests.getInstance().getLogger().warning("CAUTION - BeautyQuests will now load every single player data into the server's memory. We HIGHLY recommend the server to be restarted at the end of the operation. Be prepared to experience some lags."); - for (Entry entry : identifiersIndex.entrySet()) { - if (loadedAccounts.containsKey(entry.getKey())) continue; - try { - PlayerAccount acc = loadFromFile(entry.getKey(), false); - if (acc == null) { - acc = createPlayerAccount(entry.getValue(), entry.getKey()); - addAccount(acc); - } - }catch (Exception ex) { - BeautyQuests.logger.severe("An error occured when loading player account " + entry.getKey(), ex); - } - } - BeautyQuests.getInstance().getLogger().info("Total loaded accounts: " + loadedAccounts.size()); - } - - public void debugDuplicate() { - for (Player p : Bukkit.getOnlinePlayers()) { - p.kickPlayer("§cCleanup operation."); - } - PlayersManager.cachedAccounts.clear(); - - loadAllAccounts(); - int amount = 0; - - Map> playerAccounts = new HashMap<>(); - for (PlayerAccount acc : loadedAccounts.values()) { - List list = playerAccounts.get(acc.abstractAcc.getIdentifier()); - if (list == null) { - list = new ArrayList<>(); - playerAccounts.put(acc.abstractAcc.getIdentifier(), list); - } - list.add(acc); - } - BeautyQuests.getInstance().getLogger().info(playerAccounts.size() + " unique identifiers."); - - List removed = new ArrayList<>(); - for (Entry> en : playerAccounts.entrySet()) { - if (removed.contains(en.getKey())) System.out.println("CRITICAL - Already removed " + en.getKey()); - - List list = en.getValue(); - - int maxID = 0; - int maxSize = 0; - for (int i = 0; i < list.size(); i++) { - PlayerAccount acc = list.get(i); - if (acc.questDatas.size() > maxSize) { - maxID = i; - maxSize = acc.questDatas.size(); - } - } - for (int i = 0; i < list.size(); i++) { - if (i != maxID) { - PlayerAccount acc = list.get(i); - int index = Utils.getKeyByValue(loadedAccounts, acc); - loadedAccounts.remove(index); - identifiersIndex.remove(index); - removePlayerFile(index); - amount++; - } - } - removed.add(en.getKey()); - } - - BeautyQuests.getInstance().getLogger().info(amount + " duplicated accounts removeds. Total loaded accounts/identifiers: " + loadedAccounts.size() + "/" + identifiersIndex.size()); - BeautyQuests.getInstance().getLogger().info("Now scanning for remaining duplicated accounts..."); - boolean dup = false; - for (String id : identifiersIndex.values()) { - int size = Utils.getKeysByValue(identifiersIndex, id).size(); - if (size != 1) { - dup = true; - System.out.println(size + " accounts with identifier " + id); - } - } - if (dup) BeautyQuests.getInstance().getLogger().warning("There is still duplicated accounts."); - BeautyQuests.getInstance().getLogger().info("Operation complete."); - } - - public PlayerAccount getByIndex(Object index) { // TODO remove on 0.19 - int id = index instanceof Integer ? (int) index : Utils.parseInt(index); - PlayerAccount acc = loadedAccounts.get(id); - if (acc != null) return acc; - acc = unloadedAccounts.asMap().remove(id); - if (acc != null) { - loadedAccounts.put(id, acc); - return acc; - } - acc = loadFromFile(id, true); - if (acc != null) return acc; - acc = createPlayerAccount(identifiersIndex.get(id), id); - addAccount(acc); - return acc; - } - - private synchronized void addAccount(PlayerAccount acc) { - loadedAccounts.put(acc.index, acc); - identifiersIndex.put(acc.index, acc.abstractAcc.getIdentifier()); - if (acc.index >= lastAccountID) lastAccountID = acc.index; - } - - public PlayerAccount loadFromFile(int index, boolean msg) { - File file = new File(directory, index + ".yml"); - if (!file.exists()) return null; - DebugUtils.logMessage("Loading account #" + index + ". Last file edition: " + new Date(file.lastModified()).toString()); - YamlConfiguration playerConfig = YamlConfiguration.loadConfiguration(file); - return loadFromConfig(index, playerConfig); - } - - private PlayerAccount loadFromConfig(int index, ConfigurationSection datas) { - String identifier = datas.getString("identifier"); - if (identifier == null) { - BeautyQuests.logger.warning("No identifier found in file for index " + index + "."); - identifier = identifiersIndex.get(index); - } - PlayerAccount acc = createPlayerAccount(identifier, index); - for (Map questConfig : datas.getMapList("quests")) { - PlayerQuestDatas questDatas = PlayerQuestDatas.deserialize(acc, (Map) questConfig); - acc.questDatas.put(questDatas.questID, questDatas); - } - for (Map poolConfig : datas.getMapList("pools")) { - PlayerPoolDatas questDatas = PlayerPoolDatas.deserialize(acc, (Map) poolConfig); - acc.poolDatas.put(questDatas.getPoolID(), questDatas); - } - addAccount(acc); - return acc; - } - - public void savePlayerFile(PlayerAccount acc) throws IOException { - File file = new File(directory, acc.index + ".yml"); - file.createNewFile(); - YamlConfiguration playerConfig = YamlConfiguration.loadConfiguration(file); - acc.serialize(playerConfig); - playerConfig.save(file); - } - - public void removePlayerFile(int index) { - File file = new File(directory, index + ".yml"); - if (file.exists()) { - try { - Files.delete(file.toPath()); - DebugUtils.logMessage("Removed " + file.getName()); - }catch (IOException e) { - e.printStackTrace(); - } - }else DebugUtils.logMessage("Can't remove " + file.getName() + ": file does not exist"); - } - - @Override - public void load() { - if (!directory.exists()) directory.mkdirs(); - - FileConfiguration config = BeautyQuests.getInstance().getDataFile(); - if (config.isConfigurationSection("players")) { - for (String key : config.getConfigurationSection("players").getKeys(false)) { - try { - String path = "players." + key; - int index = Integer.parseInt(key); - identifiersIndex.put(index, config.getString(path)); - if (index >= lastAccountID) lastAccountID = index; - }catch (Exception ex) { - BeautyQuests.logger.severe("An error occured while loading player account. Data: " + config.get(key), ex); - } - } - } - DebugUtils.logMessage(loadedAccounts.size() + " accounts loaded and " + identifiersIndex.size() + " identifiers."); - - if (identifiersIndex.size() >= ACCOUNTS_THRESHOLD) { - BeautyQuests.logger.warning( - "⚠ WARNING - " + identifiersIndex.size() + " players are registered on this server." - + " It is recommended to switch to a SQL database setup in order to keep proper performances and scalability." - + " In order to do that, setup your database credentials in config.yml (without enabling it) and run the command" - + " /quests migrateDatas. Then follow steps on screen."); - } - } - - @Override - public synchronized void save() { - DebugUtils.logMessage("Saving " + loadedAccounts.size() + " loaded accounts and " + identifiersIndex.size() + " identifiers."); - - BeautyQuests.getInstance().getDataFile().set("players", identifiersIndex); - - // as the save can take a few seconds and MAY be done asynchronously, - // it is possible that the "loadedAccounts" map is being edited concurrently. - // therefore, we create a new list to avoid this issue. - ArrayList accountToSave = new ArrayList<>(loadedAccounts.values()); - for (PlayerAccount acc : accountToSave) { - try { - savePlayerFile(acc); - }catch (Exception e) { - BeautyQuests.logger.severe("An error ocurred while trying to save " + acc.debugName() + " account file", e); - } - } - } - - @Override - public void unloadAccount(PlayerAccount acc) { - loadedAccounts.remove(acc.index); - unloadedAccounts.put(acc.index, acc); - Utils.runAsync(() -> { - try { - savePlayerFile(acc); - }catch (IOException e) { - BeautyQuests.logger.warning("An error ocurred while saving player file " + acc.debugName(), e); - } - }); - } - -} +package fr.skytasul.quests.players; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.lang.Validate; +import org.bukkit.Bukkit; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + +import fr.skytasul.quests.BeautyQuests; +import fr.skytasul.quests.players.accounts.AbstractAccount; +import fr.skytasul.quests.players.accounts.GhostAccount; +import fr.skytasul.quests.structure.Quest; +import fr.skytasul.quests.structure.pools.QuestPool; +import fr.skytasul.quests.utils.DebugUtils; +import fr.skytasul.quests.utils.Utils; + +public class PlayersManagerYAML extends PlayersManager { + + private static final int ACCOUNTS_THRESHOLD = 1000; + + Map loadedAccounts = new HashMap<>(); + private Map identifiersIndex = Collections.synchronizedMap(new HashMap<>()); + private int lastAccountID = 0; + + private Cache unloadedAccounts = CacheBuilder.newBuilder().expireAfterWrite(2, TimeUnit.MINUTES).build(); + + private File directory = new File(BeautyQuests.getInstance().getDataFolder(), "players"); + + public File getDirectory() { + return directory; + } + + @Override + protected Entry load(Player player, long joinTimestamp) { + String identifier = super.getIdentifier(player); + if (identifiersIndex.containsValue(identifier)) { + int id = Utils.getKeyByValue(identifiersIndex, identifier); + return new AbstractMap.SimpleEntry<>(getByIndex(id), false); + } + + AbstractAccount absacc = super.createAbstractAccount(player); + PlayerAccount acc = new PlayerAccount(absacc, lastAccountID + 1); + addAccount(acc); + + return new AbstractMap.SimpleEntry<>(acc, true); + } + + @Override + protected void removeAccount(PlayerAccount acc) { + loadedAccounts.remove(acc.index); + identifiersIndex.remove(acc.index); + removePlayerFile(acc.index); + } + + @Override + public PlayerQuestDatas createPlayerQuestDatas(PlayerAccount acc, Quest quest) { + return new PlayerQuestDatas(acc, quest.getID()); + } + + @Override + public PlayerPoolDatas createPlayerPoolDatas(PlayerAccount acc, QuestPool pool) { + return new PlayerPoolDatas(acc, pool.getID()); + } + + @Override + public int removeQuestDatas(Quest quest) { + loadAllAccounts(); + int amount = 0; + + for (PlayerAccount account : loadedAccounts.values()) { + if (account.removeQuestDatas(quest) != null) amount++; + } + + return amount; + } + + public boolean hasAccounts(Player p) { + return identifiersIndex.containsValue(getIdentifier(p)); + } + + private synchronized PlayerAccount createPlayerAccount(String identifier, int index) { + Validate.notNull(identifier, "Identifier cannot be null (index: " + index + ")"); + AbstractAccount abs = super.createAccountFromIdentifier(identifier); + if (abs == null) { + BeautyQuests.logger.info("Player account with identifier " + identifier + " is not enabled, but will be kept in the data file."); + return new PlayerAccount(new GhostAccount(identifier), index); + } + return new PlayerAccount(abs, index); + } + + void loadAllAccounts() { + BeautyQuests.getInstance().getLogger().warning("CAUTION - BeautyQuests will now load every single player data into the server's memory. We HIGHLY recommend the server to be restarted at the end of the operation. Be prepared to experience some lags."); + for (Entry entry : identifiersIndex.entrySet()) { + if (loadedAccounts.containsKey(entry.getKey())) continue; + try { + PlayerAccount acc = loadFromFile(entry.getKey(), false); + if (acc == null) { + acc = createPlayerAccount(entry.getValue(), entry.getKey()); + addAccount(acc); + } + }catch (Exception ex) { + BeautyQuests.logger.severe("An error occured when loading player account " + entry.getKey(), ex); + } + } + BeautyQuests.getInstance().getLogger().info("Total loaded accounts: " + loadedAccounts.size()); + } + + public void debugDuplicate() { + for (Player p : Bukkit.getOnlinePlayers()) { + p.kickPlayer("§cCleanup operation."); + } + PlayersManager.cachedAccounts.clear(); + + loadAllAccounts(); + int amount = 0; + + Map> playerAccounts = new HashMap<>(); + for (PlayerAccount acc : loadedAccounts.values()) { + List list = playerAccounts.get(acc.abstractAcc.getIdentifier()); + if (list == null) { + list = new ArrayList<>(); + playerAccounts.put(acc.abstractAcc.getIdentifier(), list); + } + list.add(acc); + } + BeautyQuests.getInstance().getLogger().info(playerAccounts.size() + " unique identifiers."); + + List removed = new ArrayList<>(); + for (Entry> en : playerAccounts.entrySet()) { + if (removed.contains(en.getKey())) System.out.println("CRITICAL - Already removed " + en.getKey()); + + List list = en.getValue(); + + int maxID = 0; + int maxSize = 0; + for (int i = 0; i < list.size(); i++) { + PlayerAccount acc = list.get(i); + if (acc.questDatas.size() > maxSize) { + maxID = i; + maxSize = acc.questDatas.size(); + } + } + for (int i = 0; i < list.size(); i++) { + if (i != maxID) { + PlayerAccount acc = list.get(i); + int index = Utils.getKeyByValue(loadedAccounts, acc); + loadedAccounts.remove(index); + identifiersIndex.remove(index); + removePlayerFile(index); + amount++; + } + } + removed.add(en.getKey()); + } + + BeautyQuests.getInstance().getLogger().info(amount + " duplicated accounts removeds. Total loaded accounts/identifiers: " + loadedAccounts.size() + "/" + identifiersIndex.size()); + BeautyQuests.getInstance().getLogger().info("Now scanning for remaining duplicated accounts..."); + boolean dup = false; + for (String id : identifiersIndex.values()) { + int size = Utils.getKeysByValue(identifiersIndex, id).size(); + if (size != 1) { + dup = true; + System.out.println(size + " accounts with identifier " + id); + } + } + if (dup) BeautyQuests.getInstance().getLogger().warning("There is still duplicated accounts."); + BeautyQuests.getInstance().getLogger().info("Operation complete."); + } + + public PlayerAccount getByIndex(Object index) { // TODO remove on 0.19 + int id = index instanceof Integer ? (int) index : Utils.parseInt(index); + PlayerAccount acc = loadedAccounts.get(id); + if (acc != null) return acc; + acc = unloadedAccounts.asMap().remove(id); + if (acc != null) { + loadedAccounts.put(id, acc); + return acc; + } + acc = loadFromFile(id, true); + if (acc != null) return acc; + acc = createPlayerAccount(identifiersIndex.get(id), id); + addAccount(acc); + return acc; + } + + private synchronized void addAccount(PlayerAccount acc) { + loadedAccounts.put(acc.index, acc); + identifiersIndex.put(acc.index, acc.abstractAcc.getIdentifier()); + if (acc.index >= lastAccountID) lastAccountID = acc.index; + } + + public PlayerAccount loadFromFile(int index, boolean msg) { + File file = new File(directory, index + ".yml"); + if (!file.exists()) return null; + DebugUtils.logMessage("Loading account #" + index + ". Last file edition: " + new Date(file.lastModified()).toString()); + YamlConfiguration playerConfig = YamlConfiguration.loadConfiguration(file); + return loadFromConfig(index, playerConfig); + } + + private PlayerAccount loadFromConfig(int index, ConfigurationSection datas) { + String identifier = datas.getString("identifier"); + if (identifier == null) { + BeautyQuests.logger.warning("No identifier found in file for index " + index + "."); + identifier = identifiersIndex.get(index); + } + PlayerAccount acc = createPlayerAccount(identifier, index); + for (Map questConfig : datas.getMapList("quests")) { + PlayerQuestDatas questDatas = PlayerQuestDatas.deserialize(acc, (Map) questConfig); + acc.questDatas.put(questDatas.questID, questDatas); + } + for (Map poolConfig : datas.getMapList("pools")) { + PlayerPoolDatas questDatas = PlayerPoolDatas.deserialize(acc, (Map) poolConfig); + acc.poolDatas.put(questDatas.getPoolID(), questDatas); + } + addAccount(acc); + return acc; + } + + public void savePlayerFile(PlayerAccount acc) throws IOException { + File file = new File(directory, acc.index + ".yml"); + file.createNewFile(); + YamlConfiguration playerConfig = YamlConfiguration.loadConfiguration(file); + acc.serialize(playerConfig); + playerConfig.save(file); + } + + public void removePlayerFile(int index) { + File file = new File(directory, index + ".yml"); + if (file.exists()) { + try { + Files.delete(file.toPath()); + DebugUtils.logMessage("Removed " + file.getName()); + }catch (IOException e) { + e.printStackTrace(); + } + }else DebugUtils.logMessage("Can't remove " + file.getName() + ": file does not exist"); + } + + @Override + public void load() { + if (!directory.exists()) directory.mkdirs(); + + FileConfiguration config = BeautyQuests.getInstance().getDataFile(); + if (config.isConfigurationSection("players")) { + for (String key : config.getConfigurationSection("players").getKeys(false)) { + try { + String path = "players." + key; + int index = Integer.parseInt(key); + identifiersIndex.put(index, config.getString(path)); + if (index >= lastAccountID) lastAccountID = index; + }catch (Exception ex) { + BeautyQuests.logger.severe("An error occured while loading player account. Data: " + config.get(key), ex); + } + } + } + DebugUtils.logMessage(loadedAccounts.size() + " accounts loaded and " + identifiersIndex.size() + " identifiers."); + + if (identifiersIndex.size() >= ACCOUNTS_THRESHOLD) { + BeautyQuests.logger.warning( + "⚠ WARNING - " + identifiersIndex.size() + " players are registered on this server." + + " It is recommended to switch to a SQL database setup in order to keep proper performances and scalability." + + " In order to do that, setup your database credentials in config.yml (without enabling it) and run the command" + + " /quests migrateDatas. Then follow steps on screen."); + } + } + + @Override + public synchronized void save() { + DebugUtils.logMessage("Saving " + loadedAccounts.size() + " loaded accounts and " + identifiersIndex.size() + " identifiers."); + + BeautyQuests.getInstance().getDataFile().set("players", identifiersIndex); + + // as the save can take a few seconds and MAY be done asynchronously, + // it is possible that the "loadedAccounts" map is being edited concurrently. + // therefore, we create a new list to avoid this issue. + ArrayList accountToSave = new ArrayList<>(loadedAccounts.values()); + for (PlayerAccount acc : accountToSave) { + try { + savePlayerFile(acc); + }catch (Exception e) { + BeautyQuests.logger.severe("An error ocurred while trying to save " + acc.debugName() + " account file", e); + } + } + } + + @Override + public void unloadAccount(PlayerAccount acc) { + loadedAccounts.remove(acc.index); + unloadedAccounts.put(acc.index, acc); + Utils.runAsync(() -> { + try { + savePlayerFile(acc); + }catch (IOException e) { + BeautyQuests.logger.warning("An error ocurred while saving player file " + acc.debugName(), e); + } + }); + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/players/events/PlayerAccountEvent.java b/core/src/main/java/fr/skytasul/quests/players/events/PlayerAccountEvent.java deleted file mode 100644 index ed0961ce..00000000 --- a/core/src/main/java/fr/skytasul/quests/players/events/PlayerAccountEvent.java +++ /dev/null @@ -1,20 +0,0 @@ -package fr.skytasul.quests.players.events; - -import org.bukkit.entity.Player; -import org.bukkit.event.player.PlayerEvent; - -import fr.skytasul.quests.players.PlayerAccount; - -public abstract class PlayerAccountEvent extends PlayerEvent { - - protected PlayerAccount account; - - public PlayerAccountEvent(Player who) { - super(who); - } - - public PlayerAccount getPlayerAccount() { - return account; - } - -} \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/scoreboards/ScoreboardManager.java b/core/src/main/java/fr/skytasul/quests/scoreboards/ScoreboardManager.java index a899cfb6..656095f4 100644 --- a/core/src/main/java/fr/skytasul/quests/scoreboards/ScoreboardManager.java +++ b/core/src/main/java/fr/skytasul/quests/scoreboards/ScoreboardManager.java @@ -20,9 +20,9 @@ import fr.skytasul.quests.BeautyQuests; import fr.skytasul.quests.QuestsConfiguration; import fr.skytasul.quests.api.QuestsHandler; +import fr.skytasul.quests.api.events.accounts.PlayerAccountJoinEvent; +import fr.skytasul.quests.api.events.accounts.PlayerAccountLeaveEvent; import fr.skytasul.quests.players.PlayerAccount; -import fr.skytasul.quests.players.events.PlayerAccountJoinEvent; -import fr.skytasul.quests.players.events.PlayerAccountLeaveEvent; import fr.skytasul.quests.structure.Quest; import fr.skytasul.quests.utils.DebugUtils; diff --git a/core/src/main/java/fr/skytasul/quests/structure/QuestsManager.java b/core/src/main/java/fr/skytasul/quests/structure/QuestsManager.java index 240a4645..687ed3a9 100644 --- a/core/src/main/java/fr/skytasul/quests/structure/QuestsManager.java +++ b/core/src/main/java/fr/skytasul/quests/structure/QuestsManager.java @@ -131,18 +131,21 @@ public List getQuestsStarted(PlayerAccount acc, boolean hide, boolean wit .filter(PlayerQuestDatas::hasStarted) .map(PlayerQuestDatas::getQuest) .filter(Objects::nonNull) + .filter(quest -> !quest.isRemoved()) .filter(quest -> !hide || !quest.isHidden()) .filter(quest -> !withoutScoreboard || quest.isScoreboardEnabled()) .collect(Collectors.toList()); } public void updateQuestsStarted(PlayerAccount acc, boolean withoutScoreboard, List list) { + for (Iterator iterator = list.iterator(); iterator.hasNext();) { + Quest existing = iterator.next(); + if (!existing.hasStarted(acc) || (withoutScoreboard && !existing.isScoreboardEnabled())) iterator.remove(); + } + for (Quest qu : quests) { if (withoutScoreboard && !qu.isScoreboardEnabled()) continue; - boolean contains = list.contains(qu); - if (qu.hasStarted(acc)) { - if (!list.contains(qu)) list.add(qu); - }else if (contains) list.remove(qu); + if (!list.contains(qu) && qu.hasStarted(acc)) list.add(qu); } } diff --git a/core/src/main/java/fr/skytasul/quests/utils/ThrowingConsumer.java b/core/src/main/java/fr/skytasul/quests/utils/ThrowingConsumer.java new file mode 100644 index 00000000..4e52d93d --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/utils/ThrowingConsumer.java @@ -0,0 +1,8 @@ +package fr.skytasul.quests.utils; + +@FunctionalInterface +public interface ThrowingConsumer { + + public void accept(T object) throws E; + +} diff --git a/v1_19_R1/pom.xml b/v1_19_R1/pom.xml index 5be7e271..0b4ba97c 100644 --- a/v1_19_R1/pom.xml +++ b/v1_19_R1/pom.xml @@ -26,7 +26,7 @@ org.spigotmc spigot - 1.19-R0.1-SNAPSHOT + 1.19.2-R0.1-SNAPSHOT remapped-mojang provided @@ -46,9 +46,9 @@ remap-obf - org.spigotmc:minecraft-server:1.19-R0.1-SNAPSHOT:txt:maps-mojang + org.spigotmc:minecraft-server:1.19.2-R0.1-SNAPSHOT:txt:maps-mojang true - org.spigotmc:spigot:1.19-R0.1-SNAPSHOT:jar:remapped-mojang + org.spigotmc:spigot:1.19.2-R0.1-SNAPSHOT:jar:remapped-mojang true remapped-obf @@ -61,8 +61,8 @@ remap-spigot ${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar - org.spigotmc:minecraft-server:1.19-R0.1-SNAPSHOT:csrg:maps-spigot - org.spigotmc:spigot:1.19-R0.1-SNAPSHOT:jar:remapped-obf + org.spigotmc:minecraft-server:1.19.2-R0.1-SNAPSHOT:csrg:maps-spigot + org.spigotmc:spigot:1.19.2-R0.1-SNAPSHOT:jar:remapped-obf From 75ad45f064531e57e991657f600947014cb8bca5 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Fri, 19 Aug 2022 13:40:24 +0200 Subject: [PATCH 047/111] :sparkles: Added external account datas saving --- .github/FUNDING.yml | 13 ++ .../java/fr/skytasul/quests/BeautyQuests.java | 5 +- .../quests/api/data/SQLDataSaver.java | 154 ++++++++++++++++++ .../skytasul/quests/api/data/SavableData.java | 75 +++++++++ .../api/objects/QuestObjectClickEvent.java | 9 +- .../quests/gui/creation/QuestObjectGUI.java | 4 +- .../quests/gui/quests/PlayerListGUI.java | 67 ++++---- .../quests/players/PlayerAccount.java | 23 ++- .../quests/players/PlayersManager.java | 36 +++- .../quests/players/PlayersManagerDB.java | 77 ++++++++- .../quests/players/PlayersManagerYAML.java | 20 ++- .../quests/utils/ComparisonMethod.java | 15 +- .../java/fr/skytasul/quests/utils/Lang.java | 1 + core/src/main/resources/locales/en_US.yml | 1 + 14 files changed, 442 insertions(+), 58 deletions(-) create mode 100644 .github/FUNDING.yml create mode 100644 core/src/main/java/fr/skytasul/quests/api/data/SQLDataSaver.java create mode 100644 core/src/main/java/fr/skytasul/quests/api/data/SavableData.java diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..a3961dfa --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: skytasul +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java index 21de2465..daad7055 100644 --- a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java +++ b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java @@ -368,6 +368,8 @@ private void loadConfigParameters(boolean init) throws LoadingException { } } + PlayersManager.manager = db == null ? new PlayersManagerYAML() : new PlayersManagerDB(db); + /* static initialization */ if (init) { StagesGUI.initialize(); // initializing default stage types @@ -431,8 +433,6 @@ private void loadAllDatas() throws Throwable { } try{ - PlayersManager.manager = db == null ? new PlayersManagerYAML() : new PlayersManagerDB(db); - if (db == null && backupDir != null) createPlayerDatasBackup(backupDir, (PlayersManagerYAML) PlayersManager.manager); PlayersManager.manager.load(); @@ -516,6 +516,7 @@ private void resetDatas(){ quests = null; pools = null; if (db != null) db.closeConnection(); + PlayersManager.manager = null; //HandlerList.unregisterAll(this); loaded = false; } diff --git a/core/src/main/java/fr/skytasul/quests/api/data/SQLDataSaver.java b/core/src/main/java/fr/skytasul/quests/api/data/SQLDataSaver.java new file mode 100644 index 00000000..dade3e87 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/api/data/SQLDataSaver.java @@ -0,0 +1,154 @@ +package fr.skytasul.quests.api.data; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.sql.Types; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import com.google.common.collect.ImmutableMap; + +import fr.skytasul.quests.utils.CustomizedObjectTypeAdapter; + +public class SQLDataSaver { + + private static final SQLType TYPE_DATE = new SQLType(Types.TIMESTAMP, "TIMESTAMP", (resultSet, column) -> new Date(resultSet.getTimestamp(column).getTime())) { + @Override + public Object convert(Date obj) { + return new Timestamp(obj.getTime()); + } + }; + private static final SQLType TYPE_CHAR = new SQLType(Types.CHAR, "CHAR(1)", (resultSet, column) -> resultSet.getString(column).charAt(0)) { + @Override + public Object convert(Character obj) { + return obj.toString(); + } + }.omitLength(); + private static final SQLType TYPE_STRING = new SQLType(Types.VARCHAR, "VARCHAR", ResultSet::getString).requiresLength(); + private static final SQLType TYPE_BOOLEAN = new SQLType<>(Types.BOOLEAN, "BOOLEAN", ResultSet::getBoolean); + private static final SQLType TYPE_FLOAT = new SQLType<>(Types.FLOAT, "FLOAT", ResultSet::getFloat); + private static final SQLType TYPE_DOUBLE = new SQLType<>(Types.DOUBLE, "DOUBLE", ResultSet::getDouble); + private static final SQLType TYPE_BIGINT = new SQLType<>(Types.BIGINT, "BIGINT", ResultSet::getLong); + private static final SQLType TYPE_INT = new SQLType<>(Types.INTEGER, "INTEGER", ResultSet::getInt); + private static final SQLType TYPE_SMALLINT = new SQLType<>(Types.SMALLINT, "SMALLINT", ResultSet::getShort); + private static final SQLType TYPE_TINYINT = new SQLType<>(Types.TINYINT, "TINYINT", ResultSet::getByte); + + private static final Map, SQLType> SQL_TYPES = new HashMap<>(ImmutableMap., SQLType>builder() + .put(byte.class, TYPE_TINYINT) + .put(Byte.class, TYPE_TINYINT) + .put(short.class, TYPE_SMALLINT) + .put(Short.class, TYPE_SMALLINT) + .put(int.class, TYPE_INT) + .put(Integer.class, TYPE_INT) + .put(long.class, TYPE_BIGINT) + .put(Long.class, TYPE_BIGINT) + .put(double.class, TYPE_DOUBLE) + .put(Double.class, TYPE_DOUBLE) + .put(float.class, TYPE_FLOAT) + .put(Float.class, TYPE_FLOAT) + .put(boolean.class, TYPE_BOOLEAN) + .put(Boolean.class, TYPE_BOOLEAN) + .put(char.class, TYPE_CHAR) + .put(Character.class, TYPE_CHAR) + .put(String.class, TYPE_STRING) + .put(Date.class, TYPE_DATE) + .build()); + + private final SavableData wrappedData; + private final SQLType sqlType; + private final String updateStatement; + private final String columnDefinition; + + public SQLDataSaver(SavableData wrappedData, String updateStatement) { + this.wrappedData = wrappedData; + this.updateStatement = updateStatement; + + sqlType = (SQLType) SQL_TYPES.computeIfAbsent(wrappedData.getDataType(), JsonSQLType::new); + + String length = ""; + if (!sqlType.omitLength) { + if (wrappedData.getMaxLength().isPresent()) { + length = "(" + wrappedData.getMaxLength().getAsInt() + ")"; + }else { + if (sqlType.requiresLength) + throw new IllegalArgumentException("Column " + wrappedData.getColumnName() + " requires a max length."); + } + } + + columnDefinition = String.format("`%s` %s%s DEFAULT %s", wrappedData.getColumnName(), sqlType.sqlTypeName, length, Objects.toString(wrappedData.getDefaultValue())); + } + + public SavableData getWrappedData() { + return wrappedData; + } + + public String getUpdateStatement() { + return updateStatement; + } + + public String getColumnDefinition() { + return columnDefinition; + } + + public void setInStatement(PreparedStatement statement, int index, T value) throws SQLException { + statement.setObject(index, sqlType.convert(value), sqlType.jdbcTypeCode); + } + + public T getFromResultSet(ResultSet resultSet) throws SQLException { + return sqlType.getter.get(resultSet, wrappedData.getColumnName()); + } + + private static class SQLType { + private final int jdbcTypeCode; + private final String sqlTypeName; + private final ResultSetProcessor getter; + + private boolean requiresLength = false; + private boolean omitLength = false; + + private SQLType(int jdbcTypeCode, String sqlTypeName, ResultSetProcessor getter) { + this.jdbcTypeCode = jdbcTypeCode; + this.sqlTypeName = sqlTypeName; + this.getter = getter; + } + + public SQLType requiresLength() { + requiresLength = true; + return this; + } + + public SQLType omitLength() { + omitLength = true; + return this; + } + + public Object convert(T obj) { + return obj; + } + + } + + private static class JsonSQLType extends SQLType { + private JsonSQLType(Class type) { + super(Types.VARCHAR, "JSON", (resultSet, column) -> { + String json = resultSet.getString(column); + return CustomizedObjectTypeAdapter.GSON.fromJson(json, type); + }); + } + + @Override + public Object convert(T obj) { + return CustomizedObjectTypeAdapter.GSON.toJson(obj); + } + } + + @FunctionalInterface + public static interface ResultSetProcessor { + T get(ResultSet resultSet, String column) throws SQLException; + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/api/data/SavableData.java b/core/src/main/java/fr/skytasul/quests/api/data/SavableData.java new file mode 100644 index 00000000..3fca300f --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/api/data/SavableData.java @@ -0,0 +1,75 @@ +package fr.skytasul.quests.api.data; + +import java.util.Objects; +import java.util.OptionalInt; + +public class SavableData { + + private final String id; + private final Class dataType; + private final T defaultValue; + private final OptionalInt maxLength; + + private String columnName; + + public SavableData(String id, Class dataType, T defaultValue) { + this(id, dataType, defaultValue, OptionalInt.empty()); + } + + public SavableData(String id, Class dataType, T defaultValue, OptionalInt maxLength) { + if (id == null || id.isEmpty()) throw new IllegalArgumentException("Data id cannot be null or empty"); + if (dataType == null) throw new IllegalArgumentException("Data type cannot be null"); + if (maxLength == null) throw new IllegalArgumentException("Data max length cannot be a null optional"); + this.id = id; + this.dataType = dataType; + this.defaultValue = defaultValue; + this.maxLength = maxLength; + } + + public String getId() { + return id; + } + + public Class getDataType() { + return dataType; + } + + public T getDefaultValue() { + return defaultValue; + } + + public OptionalInt getMaxLength() { + return maxLength; + } + + public String getColumnName() { + return columnName == null ? id : columnName; + } + + public void setColumnName(String columnName) { + this.columnName = columnName; + } + + @Override + public int hashCode() { + int hash = 7; + + hash = hash * 23 + id.hashCode(); + hash = hash * 23 + dataType.hashCode(); + hash = hash * 23 + maxLength.hashCode(); + hash = hash * 23 + Objects.hashCode(defaultValue); + + return hash; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof SavableData)) return false; + SavableData oth = (SavableData) obj; + return oth.id.equals(id) + && oth.dataType.equals(dataType) + && Objects.equals(oth.defaultValue, defaultValue) + && oth.maxLength.equals(maxLength); + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectClickEvent.java b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectClickEvent.java index d572bb10..1166a91b 100644 --- a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectClickEvent.java +++ b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectClickEvent.java @@ -16,13 +16,15 @@ public class QuestObjectClickEvent { private final ItemStack item; private final ClickType click; private final boolean creation; + private final Object clickedObject; - public QuestObjectClickEvent(Player player, QuestObjectGUI gui, ItemStack item, ClickType click, boolean creation) { + public QuestObjectClickEvent(Player player, QuestObjectGUI gui, ItemStack item, ClickType click, boolean creation, Object clickedObject) { this.player = player; this.gui = gui; this.item = item; this.click = click; this.creation = creation; + this.clickedObject = clickedObject; } public Player getPlayer() { @@ -49,6 +51,11 @@ public void reopenGUI() { gui.reopen(); } + public void cancel() { + if (creation) gui.remove(clickedObject); + gui.reopen(); + } + public void updateItemLore(String... lore) { ItemUtils.lore(item, lore); } diff --git a/core/src/main/java/fr/skytasul/quests/gui/creation/QuestObjectGUI.java b/core/src/main/java/fr/skytasul/quests/gui/creation/QuestObjectGUI.java index 8aa391fd..4d475c7b 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/creation/QuestObjectGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/creation/QuestObjectGUI.java @@ -72,7 +72,7 @@ public ItemStack getItemStack(QuestObjectCreator object) { public void click(QuestObjectCreator existing, ItemStack item, ClickType clickType) { T object = existing.newObject(); if (!existing.canBeMultiple()) creators.remove(existing); - object.itemClick(new QuestObjectClickEvent(p, QuestObjectGUI.this, callback.apply(object), clickType, true)); + object.itemClick(new QuestObjectClickEvent(p, QuestObjectGUI.this, callback.apply(object), clickType, true, object)); } @Override @@ -86,7 +86,7 @@ public CloseBehavior onClose(Player p, Inventory inv) { @Override public void clickObject(QuestObject existing, ItemStack item, ClickType clickType) { - existing.itemClick(new QuestObjectClickEvent(p, this, item, clickType, false)); + existing.itemClick(new QuestObjectClickEvent(p, this, item, clickType, false, existing)); } @Override diff --git a/core/src/main/java/fr/skytasul/quests/gui/quests/PlayerListGUI.java b/core/src/main/java/fr/skytasul/quests/gui/quests/PlayerListGUI.java index 60204ee1..c94ea753 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/quests/PlayerListGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/quests/PlayerListGUI.java @@ -1,6 +1,7 @@ package fr.skytasul.quests.gui.quests; import java.util.List; +import java.util.function.Function; import java.util.stream.Collectors; import org.bukkit.Bukkit; @@ -95,51 +96,35 @@ private void setItems(){ switch (cat){ case FINISHED: - setQuests(QuestsAPI.getQuests().getQuestsFinished(acc, hide)); - for (int i = page * 35; i < quests.size(); i++){ - if (i == (page + 1) * 35) break; - Quest qu = quests.get(i); + displayQuests(QuestsAPI.getQuests().getQuestsFinished(acc, hide), qu -> { List lore = new QuestDescriptionContext(QuestsConfiguration.getQuestDescription(), qu, acc, cat).formatDescription(); if (QuestsConfiguration.getDialogsConfig().isHistoryEnabled() && acc.getQuestDatas(qu).hasFlowDialogs()) { if (!lore.isEmpty()) lore.add(null); lore.add("§8" + Lang.ClickRight + " §8> " + Lang.dialogsHistoryLore); } - setMainItem(i - page * 35, createQuestItem(qu, lore)); - } + return createQuestItem(qu, lore); + }); break; case IN_PROGRESS: - setQuests(QuestsAPI.getQuests().getQuestsStarted(acc, true, false)); - for (int i = page * 35; i < quests.size(); i++){ - if (i == (page + 1) * 35) break; - Quest qu = quests.get(i); - ItemStack item; - try { - List lore = new QuestDescriptionContext(QuestsConfiguration.getQuestDescription(), qu, acc, cat).formatDescription(); - - boolean hasDialogs = QuestsConfiguration.getDialogsConfig().isHistoryEnabled() && acc.getQuestDatas(qu).hasFlowDialogs(); - boolean cancellable = QuestsConfiguration.getMenuConfig().allowPlayerCancelQuest() && qu.isCancellable(); - if (cancellable || hasDialogs) { - if (!lore.isEmpty()) lore.add(null); - if (cancellable) lore.add("§8" + Lang.ClickLeft + " §8> " + Lang.cancelLore); - if (hasDialogs) lore.add("§8" + Lang.ClickRight + " §8> " + Lang.dialogsHistoryLore); - } - item = createQuestItem(qu, lore); - }catch (Exception ex) { - item = ItemUtils.item(XMaterial.BARRIER, "§cError - Quest #" + qu.getID()); - BeautyQuests.logger.severe("An error ocurred when creating item of quest " + qu.getID() + " for account " + acc.abstractAcc.getIdentifier(), ex); + displayQuests(QuestsAPI.getQuests().getQuestsStarted(acc, true, false), qu -> { + List lore = new QuestDescriptionContext(QuestsConfiguration.getQuestDescription(), qu, acc, cat).formatDescription(); + + boolean hasDialogs = QuestsConfiguration.getDialogsConfig().isHistoryEnabled() && acc.getQuestDatas(qu).hasFlowDialogs(); + boolean cancellable = QuestsConfiguration.getMenuConfig().allowPlayerCancelQuest() && qu.isCancellable(); + if (cancellable || hasDialogs) { + if (!lore.isEmpty()) lore.add(null); + if (cancellable) lore.add("§8" + Lang.ClickLeft + " §8> " + Lang.cancelLore); + if (hasDialogs) lore.add("§8" + Lang.ClickRight + " §8> " + Lang.dialogsHistoryLore); } - setMainItem(i - page * 35, item); - } + return createQuestItem(qu, lore); + }); break; case NOT_STARTED: - setQuests(QuestsAPI.getQuests().getQuestsNotStarted(acc, hide, true).stream().filter(quest -> !quest.isHiddenWhenRequirementsNotMet() || quest.isLauncheable(acc.getPlayer(), acc, false)).collect(Collectors.toList())); - for (int i = page * 35; i < quests.size(); i++){ - if (i == (page + 1) * 35) break; - Quest qu = quests.get(i); - setMainItem(i - page * 35, createQuestItem(qu, new QuestDescriptionContext(QuestsConfiguration.getQuestDescription(), qu, acc, cat).formatDescription())); - } + displayQuests(QuestsAPI.getQuests().getQuestsNotStarted(acc, hide, true).stream().filter(quest -> !quest.isHiddenWhenRequirementsNotMet() || quest.isLauncheable(acc.getPlayer(), acc, false)).collect(Collectors.toList()), qu -> { + return createQuestItem(qu, new QuestDescriptionContext(QuestsConfiguration.getQuestDescription(), qu, acc, cat).formatDescription()); + }); break; default: @@ -147,6 +132,22 @@ private void setItems(){ } } + private void displayQuests(List quests, Function itemProvider) { + setQuests(quests); + for (int i = page * 35; i < quests.size(); i++) { + if (i == (page + 1) * 35) break; + Quest qu = quests.get(i); + ItemStack item; + try { + item = itemProvider.apply(qu); + }catch (Exception ex) { + item = ItemUtils.item(XMaterial.BARRIER, "§cError - Quest #" + qu.getID()); + BeautyQuests.logger.severe("An error ocurred when creating item of quest " + qu.getID() + " for account " + acc.abstractAcc.getIdentifier(), ex); + } + setMainItem(i - page * 35, item); + } + } + private int setMainItem(int mainSlot, ItemStack is){ int line = (int) Math.floor(mainSlot * 1.0 / 7.0); int slot = mainSlot + (2 * line); diff --git a/core/src/main/java/fr/skytasul/quests/players/PlayerAccount.java b/core/src/main/java/fr/skytasul/quests/players/PlayerAccount.java index 44ac98c0..f516bb9f 100644 --- a/core/src/main/java/fr/skytasul/quests/players/PlayerAccount.java +++ b/core/src/main/java/fr/skytasul/quests/players/PlayerAccount.java @@ -1,13 +1,16 @@ package fr.skytasul.quests.players; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.bukkit.OfflinePlayer; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; +import fr.skytasul.quests.api.data.SavableData; import fr.skytasul.quests.players.accounts.AbstractAccount; import fr.skytasul.quests.structure.Quest; import fr.skytasul.quests.structure.pools.QuestPool; @@ -15,12 +18,15 @@ public class PlayerAccount { + public static final List FORBIDDEN_DATA_ID = Arrays.asList("identifier", "quests", "pools"); + public final AbstractAccount abstractAcc; protected final Map questDatas = new HashMap<>(); protected final Map poolDatas = new HashMap<>(); + protected final Map, Object> additionalDatas = new HashMap<>(); protected final int index; - public PlayerAccount(AbstractAccount account, int index) { + protected PlayerAccount(AbstractAccount account, int index) { this.abstractAcc = account; this.index = index; } @@ -107,7 +113,19 @@ public PlayerPoolDatas removePoolDatas(int id) { public Collection getPoolDatas() { return poolDatas.values(); } + + public T getData(SavableData data) { + if (!PlayersManager.manager.getAccountDatas().contains(data)) + throw new IllegalArgumentException("The " + data.getId() + " account data has not been registered."); + return (T) additionalDatas.getOrDefault(data, data.getDefaultValue()); + } + public void setData(SavableData data, T value) { + if (!PlayersManager.manager.getAccountDatas().contains(data)) + throw new IllegalArgumentException("The " + data.getId() + " account data has not been registered."); + additionalDatas.put(data, value); + } + @Override public boolean equals(Object arg0) { if (arg0 == this) return true; @@ -140,6 +158,9 @@ public void serialize(ConfigurationSection config) { config.set("identifier", abstractAcc.getIdentifier()); config.set("quests", questDatas.isEmpty() ? null : Utils.serializeList(questDatas.values(), PlayerQuestDatas::serialize)); config.set("pools", poolDatas.isEmpty() ? null : Utils.serializeList(poolDatas.values(), PlayerPoolDatas::serialize)); + additionalDatas.entrySet().forEach(entry -> { + config.set(entry.getKey().getId(), entry.getValue()); + }); } } diff --git a/core/src/main/java/fr/skytasul/quests/players/PlayersManager.java b/core/src/main/java/fr/skytasul/quests/players/PlayersManager.java index 8e61b332..d4c7abed 100644 --- a/core/src/main/java/fr/skytasul/quests/players/PlayersManager.java +++ b/core/src/main/java/fr/skytasul/quests/players/PlayersManager.java @@ -1,8 +1,11 @@ package fr.skytasul.quests.players; +import java.util.Collection; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; +import java.util.Set; import java.util.UUID; import org.bukkit.Bukkit; @@ -11,6 +14,7 @@ import fr.skytasul.quests.BeautyQuests; import fr.skytasul.quests.QuestsConfiguration; import fr.skytasul.quests.api.QuestsAPI; +import fr.skytasul.quests.api.data.SavableData; import fr.skytasul.quests.api.events.accounts.PlayerAccountJoinEvent; import fr.skytasul.quests.api.events.accounts.PlayerAccountLeaveEvent; import fr.skytasul.quests.players.accounts.AbstractAccount; @@ -22,8 +26,9 @@ import fr.skytasul.quests.utils.compatibility.MissingDependencyException; public abstract class PlayersManager { - - public static PlayersManager manager; + + protected final Set> accountDatas = new HashSet<>(); + private boolean loaded = false; protected abstract Entry load(Player player, long joinTimestamp); @@ -41,9 +46,33 @@ public void playerPoolDataRemoved(PlayerAccount acc, int id, PlayerPoolDatas dat public abstract void unloadAccount(PlayerAccount acc); - public abstract void load(); + public void load() { + if (loaded) throw new IllegalStateException("Already loaded"); + loaded = true; + } + + public boolean isLoaded() { + return loaded; + } public abstract void save(); + + public void addAccountData(SavableData data) { + if (loaded) + throw new IllegalStateException("Cannot add account data after players manager has been loaded"); + if (PlayerAccount.FORBIDDEN_DATA_ID.contains(data.getId())) + throw new IllegalArgumentException("Forbidden account data id " + data.getId()); + if (accountDatas.stream().anyMatch(x -> x.getId().equals(data.getId()))) + throw new IllegalArgumentException("Another account data already exists with the id " + data.getId()); + if (data.getDataType().isPrimitive()) + throw new IllegalArgumentException("Primitive account data types are not supported"); + accountDatas.add(data); + DebugUtils.logMessage("Registered account data " + data.getId()); + } + + public Collection> getAccountDatas() { + return accountDatas; + } public AbstractAccount createAbstractAccount(Player p) { return QuestsConfiguration.hookAccounts() ? Accounts.getPlayerAccount(p) : new UUIDAccount(p.getUniqueId()); @@ -80,6 +109,7 @@ protected AbstractAccount createAccountFromIdentifier(String identifier) { } protected static Map cachedAccounts = new HashMap<>(); + public static PlayersManager manager; public static synchronized void loadPlayer(Player p) { long time = System.currentTimeMillis(); diff --git a/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java b/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java index e4b626fc..1d54a8ab 100644 --- a/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java +++ b/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java @@ -23,6 +23,8 @@ import org.bukkit.scheduler.BukkitRunnable; import fr.skytasul.quests.BeautyQuests; +import fr.skytasul.quests.api.data.SQLDataSaver; +import fr.skytasul.quests.api.data.SavableData; import fr.skytasul.quests.api.stages.AbstractStage; import fr.skytasul.quests.players.accounts.AbstractAccount; import fr.skytasul.quests.structure.Quest; @@ -38,10 +40,13 @@ public class PlayersManagerDB extends PlayersManager { public final String QUESTS_DATAS_TABLE; public final String POOLS_DATAS_TABLE; - private Database db; + private final Database db; + + private final Map, SQLDataSaver> accountDatas = new HashMap<>(); + private String getAccountDatas; /* Accounts statements */ - private String getAccounts; + private String getAccountsIDs; private String insertAccount; private String deleteAccount; @@ -80,6 +85,16 @@ public Database getDatabase() { return db; } + @Override + public void addAccountData(SavableData data) { + super.addAccountData(data); + accountDatas.put(data, new SQLDataSaver<>(data, "UPDATE " + ACCOUNTS_TABLE + " SET `" + data.getColumnName() + "` = ? WHERE `id` = ?")); + getAccountDatas = accountDatas.keySet() + .stream() + .map(x -> "`" + x.getColumnName() + "`") + .collect(Collectors.joining(", ", "SELECT ", " FROM " + ACCOUNTS_TABLE + " WHERE `id` = ?")); + } + private synchronized void retrievePlayerDatas(PlayerAccount acc) { try (Connection connection = db.getConnection()) { try (PreparedStatement statement = connection.prepareStatement(getQuestsData)) { @@ -102,6 +117,17 @@ private synchronized void retrievePlayerDatas(PlayerAccount acc) { } result.close(); } + if (getAccountDatas != null) { + try (PreparedStatement statement = connection.prepareStatement(getAccountDatas)) { + statement.setInt(1, acc.index); + ResultSet result = statement.executeQuery(); + result.next(); + for (SQLDataSaver data : accountDatas.values()) { + acc.additionalDatas.put(data.getWrappedData(), data.getFromResultSet(result)); + } + result.close(); + } + } }catch (SQLException e) { e.printStackTrace(); } @@ -111,13 +137,13 @@ private synchronized void retrievePlayerDatas(PlayerAccount acc) { protected synchronized Entry load(Player player, long joinTimestamp) { try (Connection connection = db.getConnection()) { String uuid = player.getUniqueId().toString(); - try (PreparedStatement statement = connection.prepareStatement(getAccounts)) { + try (PreparedStatement statement = connection.prepareStatement(getAccountsIDs)) { statement.setString(1, uuid); ResultSet result = statement.executeQuery(); while (result.next()) { AbstractAccount abs = createAccountFromIdentifier(result.getString("identifier")); if (abs.isCurrent()) { - PlayerAccount account = new PlayerAccount(abs, result.getInt("id")); + PlayerAccount account = new PlayerAccountDB(abs, result.getInt("id")); result.close(); try { // in order to ensure that, if the player was previously connected to another server, @@ -143,7 +169,7 @@ protected synchronized Entry load(Player player, long jo if (!result.next()) throw new SQLException("The plugin has not been able to create a player account."); int index = result.getInt(1); // some drivers don't return a ResultSet with correct column names result.close(); - return new AbstractMap.SimpleEntry<>(new PlayerAccount(absacc, index), true); + return new AbstractMap.SimpleEntry<>(new PlayerAccountDB(absacc, index), true); } }catch (SQLException e) { e.printStackTrace(); @@ -217,7 +243,7 @@ public synchronized int removeQuestDatas(Quest quest) { public synchronized boolean hasAccounts(Player p) { try (Connection connection = db.getConnection(); - PreparedStatement statement = connection.prepareStatement(getAccounts)) { + PreparedStatement statement = connection.prepareStatement(getAccountsIDs)) { statement.setString(1, p.getUniqueId().toString()); ResultSet result = statement.executeQuery(); boolean has = result.next(); @@ -231,10 +257,11 @@ public synchronized boolean hasAccounts(Player p) { @Override public void load() { + super.load(); try { createTables(); - getAccounts = "SELECT * FROM " + ACCOUNTS_TABLE + " WHERE `player_uuid` = ?"; + getAccountsIDs = "SELECT `id`, `identifier` FROM " + ACCOUNTS_TABLE + " WHERE `player_uuid` = ?"; insertAccount = "INSERT INTO " + ACCOUNTS_TABLE + " (`identifier`, `player_uuid`) VALUES (?, ?)"; deleteAccount = "DELETE FROM " + ACCOUNTS_TABLE + " WHERE `id` = ?"; @@ -279,6 +306,7 @@ private void createTables() throws SQLException { + " `id` int NOT NULL AUTO_INCREMENT ," + " `identifier` text NOT NULL ," + " `player_uuid` char(36) NOT NULL ," + + accountDatas.values().stream().map(data -> " " + data.getColumnDefinition() + " ,").collect(Collectors.joining()) + " PRIMARY KEY (`id`)" + " )"); statement.execute("CREATE TABLE IF NOT EXISTS " + QUESTS_DATAS_TABLE + " (" + @@ -301,6 +329,7 @@ private void createTables() throws SQLException { + "`completed_quests` varchar(1000) DEFAULT NULL, " + "PRIMARY KEY (`id`)" + ")"); + statement.execute("ALTER TABLE " + QUESTS_DATAS_TABLE + " MODIFY COLUMN finished INT(11) DEFAULT 0"); upgradeTable(connection, QUESTS_DATAS_TABLE, columns -> { if (!columns.contains("quest_flow")) { // 0.19 @@ -349,7 +378,16 @@ private void createTables() throws SQLException { if (deletedDuplicates > 0) BeautyQuests.logger.info("Deleted " + deletedDuplicates + " duplicated rows in the " + QUESTS_DATAS_TABLE + " table."); } }); - statement.execute("ALTER TABLE " + QUESTS_DATAS_TABLE + " MODIFY COLUMN finished INT(11) DEFAULT 0"); + + upgradeTable(connection, ACCOUNTS_TABLE, columns -> { + for (SQLDataSaver data : accountDatas.values()) { + if (!columns.contains(data.getWrappedData().getColumnName().toLowerCase())) { + statement.execute("ALTER TABLE " + ACCOUNTS_TABLE + + " ADD COLUMN " + data.getColumnDefinition()); + BeautyQuests.logger.info("Updated database by adding the missing " + data.getWrappedData().getColumnName() + " column in the player accounts table."); + } + } + }); } } @@ -657,5 +695,28 @@ private void updateData(String dataStatement, Object data) { } } + + public class PlayerAccountDB extends PlayerAccount { + + public PlayerAccountDB(AbstractAccount account, int index) { + super(account, index); + } + + @Override + public void setData(SavableData data, T value) { + super.setData(data, value); + + SQLDataSaver dataSaver = (SQLDataSaver) accountDatas.get(data); + try (Connection connection = db.getConnection(); + PreparedStatement statement = connection.prepareStatement(dataSaver.getUpdateStatement())) { + dataSaver.setInStatement(statement, 1, value); + statement.setInt(2, index); + statement.executeUpdate(); + }catch (SQLException ex) { + BeautyQuests.logger.severe("An error occurred while saving account data " + data.getId() + " to database", ex); + } + } + + } } diff --git a/core/src/main/java/fr/skytasul/quests/players/PlayersManagerYAML.java b/core/src/main/java/fr/skytasul/quests/players/PlayersManagerYAML.java index 286a54e7..739f9b9e 100644 --- a/core/src/main/java/fr/skytasul/quests/players/PlayersManagerYAML.java +++ b/core/src/main/java/fr/skytasul/quests/players/PlayersManagerYAML.java @@ -24,6 +24,7 @@ import com.google.common.cache.CacheBuilder; import fr.skytasul.quests.BeautyQuests; +import fr.skytasul.quests.api.data.SavableData; import fr.skytasul.quests.players.accounts.AbstractAccount; import fr.skytasul.quests.players.accounts.GhostAccount; import fr.skytasul.quests.structure.Quest; @@ -35,13 +36,14 @@ public class PlayersManagerYAML extends PlayersManager { private static final int ACCOUNTS_THRESHOLD = 1000; - Map loadedAccounts = new HashMap<>(); - private Map identifiersIndex = Collections.synchronizedMap(new HashMap<>()); - private int lastAccountID = 0; - - private Cache unloadedAccounts = CacheBuilder.newBuilder().expireAfterWrite(2, TimeUnit.MINUTES).build(); + private final Cache unloadedAccounts = CacheBuilder.newBuilder().expireAfterWrite(2, TimeUnit.MINUTES).build(); - private File directory = new File(BeautyQuests.getInstance().getDataFolder(), "players"); + protected final Map loadedAccounts = new HashMap<>(); + private final Map identifiersIndex = Collections.synchronizedMap(new HashMap<>()); + + private final File directory = new File(BeautyQuests.getInstance().getDataFolder(), "players"); + + private int lastAccountID = 0; public File getDirectory() { return directory; @@ -229,6 +231,11 @@ private PlayerAccount loadFromConfig(int index, ConfigurationSection datas) { PlayerPoolDatas questDatas = PlayerPoolDatas.deserialize(acc, (Map) poolConfig); acc.poolDatas.put(questDatas.getPoolID(), questDatas); } + for (SavableData data : accountDatas) { + if (datas.contains(data.getId())) { + acc.additionalDatas.put(data, datas.getObject(data.getId(), data.getDataType())); + } + } addAccount(acc); return acc; } @@ -255,6 +262,7 @@ public void removePlayerFile(int index) { @Override public void load() { + super.load(); if (!directory.exists()) directory.mkdirs(); FileConfiguration config = BeautyQuests.getInstance().getDataFile(); diff --git a/core/src/main/java/fr/skytasul/quests/utils/ComparisonMethod.java b/core/src/main/java/fr/skytasul/quests/utils/ComparisonMethod.java index ea5bcb32..7239a586 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/ComparisonMethod.java +++ b/core/src/main/java/fr/skytasul/quests/utils/ComparisonMethod.java @@ -3,15 +3,26 @@ import fr.skytasul.quests.editors.checkers.EnumParser; public enum ComparisonMethod { - EQUALS(Lang.ComparisonEquals), DIFFERENT(Lang.ComparisonDifferent), LESS(Lang.ComparisonLess), LESS_OR_EQUAL(Lang.ComparisonLessOrEquals), GREATER(Lang.ComparisonGreater), GREATER_OR_EQUAL(Lang.ComparisonGreaterOrEquals); + EQUALS('=', Lang.ComparisonEquals), + DIFFERENT('≠', Lang.ComparisonDifferent), + LESS('<', Lang.ComparisonLess), + LESS_OR_EQUAL('≤', Lang.ComparisonLessOrEquals), + GREATER('>', Lang.ComparisonGreater), + GREATER_OR_EQUAL('≥', Lang.ComparisonGreaterOrEquals); + private char symbol; private Lang title; private static final EnumParser COMPARISON_PARSER = new EnumParser<>(ComparisonMethod.class); - private ComparisonMethod(Lang title) { + private ComparisonMethod(char symbol, Lang title) { + this.symbol = symbol; this.title = title; } + + public char getSymbol() { + return symbol; + } public Lang getTitle() { return title; diff --git a/core/src/main/java/fr/skytasul/quests/utils/Lang.java b/core/src/main/java/fr/skytasul/quests/utils/Lang.java index d927ee85..b1ba4a26 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Lang.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Lang.java @@ -15,6 +15,7 @@ public enum Lang implements Locale { EditorPrefix("misc.format.editorPrefix"), ErrorPrefix("misc.format.errorPrefix"), SuccessPrefix("misc.format.successPrefix"), + RequirementNotMetPrefix("misc.format.requirementNotMetPrefix"), /* Messages */ FINISHED_BASE("msg.quest.finished.base"), diff --git a/core/src/main/resources/locales/en_US.yml b/core/src/main/resources/locales/en_US.yml index 1ea30ac4..7bff8922 100644 --- a/core/src/main/resources/locales/en_US.yml +++ b/core/src/main/resources/locales/en_US.yml @@ -764,6 +764,7 @@ misc: editorPrefix: §a errorPrefix: §4✖ §c successPrefix: §2✔ §a + requirementNotMetPrefix: §c time: weeks: '{0} weeks' days: '{0} days' From 9d1786560f45c76f33a0ec77dcca26c50a641bbe Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Fri, 19 Aug 2022 14:41:15 +0200 Subject: [PATCH 048/111] :technologist: Made locales resources detected automatically --- .../java/fr/skytasul/quests/BeautyQuests.java | 2 +- .../java/fr/skytasul/quests/api/Locale.java | 17 +++++++---- .../java/fr/skytasul/quests/utils/Utils.java | 28 +++++++++++++++++++ 3 files changed, 41 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java index daad7055..2f9e1397 100644 --- a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java +++ b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java @@ -389,7 +389,7 @@ private void loadConfigParameters(boolean init) throws LoadingException { private YamlConfiguration loadLang() throws LoadingException { try { loadedLanguage = config.getConfig().getString("lang", "en_US"); - return Locale.loadLang(this, Lang.values(), loadedLanguage, "en_US", "fr_FR", "zh_CN", "zh_HK", "de_DE", "pt_PT", "it_IT", "es_ES", "sv_SE", "hu_HU", "ru_RU", "pl_PL", "th_TH", "lt_LT", "vi_VN"); + return Locale.loadLang(this, Lang.values(), loadedLanguage); }catch (Exception ex) { throw new LoadingException("Couldn't load language file.", ex); } diff --git a/core/src/main/java/fr/skytasul/quests/api/Locale.java b/core/src/main/java/fr/skytasul/quests/api/Locale.java index 77bbc064..21ca0eb6 100644 --- a/core/src/main/java/fr/skytasul/quests/api/Locale.java +++ b/core/src/main/java/fr/skytasul/quests/api/Locale.java @@ -4,7 +4,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.util.function.Supplier; import org.bukkit.ChatColor; @@ -49,12 +51,17 @@ public static void loadStrings(Locale[] locales, YamlConfiguration defaultConfig } } - public static YamlConfiguration loadLang(Plugin plugin, Locale[] locales, String loadedLanguage, String... languages) throws IOException { + public static YamlConfiguration loadLang(Plugin plugin, Locale[] locales, String loadedLanguage) throws IOException, URISyntaxException { long lastMillis = System.currentTimeMillis(); - for (String language : languages) { - File file = new File(plugin.getDataFolder(), "locales/" + language + ".yml"); - if (!file.exists()) plugin.saveResource("locales/" + language + ".yml", false); - } + + Utils.walkResources(plugin.getClass(), "/locales", 1, path -> { + String localeFileName = path.getFileName().toString(); + if (!localeFileName.toLowerCase().endsWith(".yml")) return; + + if (!Files.exists(plugin.getDataFolder().toPath().resolve("locales").resolve(localeFileName))) { + plugin.saveResource("locales/" + localeFileName, false); + } + }); String language = "locales/" + loadedLanguage + ".yml"; File file = new File(plugin.getDataFolder(), language); diff --git a/core/src/main/java/fr/skytasul/quests/utils/Utils.java b/core/src/main/java/fr/skytasul/quests/utils/Utils.java index 57230c7d..c6b57fd4 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Utils.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Utils.java @@ -1,5 +1,13 @@ package fr.skytasul.quests.utils; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -16,6 +24,7 @@ import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.stream.Stream; import org.apache.commons.lang.StringUtils; import org.bukkit.Bukkit; @@ -333,6 +342,25 @@ public static double parseDouble(Object obj) { return 0; } + public static void walkResources(Class clazz, String path, int depth, Consumer consumer) throws URISyntaxException, IOException { + URI uri = clazz.getResource(path).toURI(); + FileSystem fileSystem = null; + Path myPath; + try { + if (uri.getScheme().equals("jar")) { + fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap()); + myPath = fileSystem.getPath(path); + }else { + myPath = Paths.get(uri); + } + + try (Stream walker = Files.walk(myPath, depth)) { + walker.forEach(consumer); + } + }finally { + if (fileSystem != null) fileSystem.close(); + } + } public static void runOrSync(Runnable run) { From 2afc94d4b961eb26b12d2614077560dab13dbcfb Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Fri, 19 Aug 2022 14:52:28 +0200 Subject: [PATCH 049/111] :globe_with_meridians: Updated ES, IT, PL and VI locales --- core/src/main/resources/locales/es_ES.yml | 95 ++++++- core/src/main/resources/locales/it_IT.yml | 25 +- core/src/main/resources/locales/pl_PL.yml | 29 ++ core/src/main/resources/locales/vi_VN.yml | 327 +++++++++++++++++++++- 4 files changed, 435 insertions(+), 41 deletions(-) diff --git a/core/src/main/resources/locales/es_ES.yml b/core/src/main/resources/locales/es_ES.yml index 6e5962c4..cffdde5d 100644 --- a/core/src/main/resources/locales/es_ES.yml +++ b/core/src/main/resources/locales/es_ES.yml @@ -2,12 +2,12 @@ msg: quest: finished: - base: '§aFelicitaciones! Has finalizado la misión§e{0}§a!' - obtain: '§aObtuviste {0}!' - started: '§aHas comenzado la misión §r§e{0}§o§6!' - created: '§aFelicitaciones! Tu has creado la misión §e{0}§a el cual incluye {1} rama(s)!' - edited: '§aFelicitaciones! Has editado la misión §e{0}§a el cual incluye ahora {1} rama(s)!' - createCancelled: '§cLa creación o edición a sido cancelada por otro plugin!' + base: '§a¡Felicitaciones! ¡Has finalizado la misión §e{0}§a!' + obtain: '§a¡Obtuviste {0}!' + started: '§a¡Has comenzado la misión §r§e{0}§o§6!' + created: '§a¡Felicitaciones! ¡Has creado la misión §e{0}§a el cual incluye {1} rama(s)!' + edited: '§a¡Felicitaciones! ¡Has editado la misión §e{0}§a el cual incluye ahora {1} rama(s)!' + createCancelled: '§c¡La creación o edición a sido cancelada por otro plugin!' cancelling: '§cProceso de creación de la misión cancelada.' editCancelling: '§cProceso de edición de misión cancelada.' invalidID: '§cLa misión con la id {0} no existe.' @@ -27,6 +27,7 @@ msg: questItem: drop: '§c¡No puedes soltar un objeto de misión!' craft: '§c¡No puedes usar un objeto de misión para fabricar!' + eat: '§c¡No puedes comer un objeto de misión!' stageMobs: noMobs: '§cEsta etapa no necesita ningún mob para matar.' listMobs: '§aDebes matar {0}.' @@ -39,6 +40,7 @@ msg: writeMessage: '§aEscribe el mensaje que se enviará al jugador' writeStartMessage: '§aEscribe el mensaje que será enviado al iniciar la misión, "null" si quieres el predeterminado o "none" si no quieres ninguno' writeEndMsg: '§aEscribe el mensaje que se enviará al final de la misión, "null" si quieres el predeterminado o "none" si no quieres ninguno. Puedes usar "{0}" que será reemplazado por las recompensas obtenidas.' + writeEndSound: '§aEscribe el nombre del sonido que se reproducirá al jugador al final de la misión, "null" si quieres el predeterminado o "none" si no quieres ninguno:' writeDescriptionText: '§aEscribe el texto que describe el objetivo de la etapa:' writeStageText: '§aEscribe el texto que será enviado al jugador al comienzo del paso:' moveToTeleportPoint: '§aIr a la ubicación de teletransporte deseada.' @@ -72,6 +74,7 @@ msg: negative: '§cDebes introducir un número positivo!' zero: '§cDebes introducir un número distinto de 0!' invalid: '§c{0} no es un número válido.' + notInBounds: '§cTu número debe estar entre {0} y {1}.' errorOccurred: '§cHa ocurrido un error, ¡contacta con un administrador! §4§lError: {0}' commandsDisabled: '§cActualmente no tienes permisos para ejecutar comandos!' indexOutOfBounds: '§cEl número {0} está fuera de límites! Debe estar entre {1} y {2}.' @@ -83,6 +86,10 @@ msg: playerNotOnline: '§cEl jugador {0} está desconectado.' playerDataNotFound: '§cDatas del jugador {0} no encontrados.' versionRequired: 'Versión requerida: §l{0}' + restartServer: '§7Reinicia tu servidor para ver las modificaciones.' + dialogs: + skipped: '§8§o Diálogo omitido.' + tooFar: '§7§oEstás demasiado lejos de {0}...' command: downloadTranslations: syntax: '§cDebes especificar un idioma para descargar. Ejemplo: "/quests downloadTranslations en_US".' @@ -98,6 +105,11 @@ msg: next: '§aLa etapa ha sido omitida.' nextUnavailable: '§cLa opción "skip" no está disponible cuando el jugador está al final de una rama.' set: '§aFase {0} lanzada.' + startDialog: + impossible: '§cNo es posible iniciar el diálogo ahora.' + noDialog: '§cEl jugador no tiene un diálogo pendiente.' + alreadyIn: '§cEl jugador ya está reproduciendo un diálogo.' + success: '§aEmpezó el diálogo para el jugador {0} en la misión {1}!' playerNeeded: '§cDebes ser un jugador para ejecutar este comando!' incorrectSyntax: '§cSintaxis incorrecta.' noPermission: '§cNo tienes suficientes permisos para ejecutar este comando! (Requiere: {0})' @@ -146,12 +158,14 @@ msg: remove: '§6/{0} remove : §eElimina una misión con un id especificado o haz clic en el NPC cuando no se haya definido.' finishAll: '§6/{0} finishAll : §eFinish all quests of a player.' setStage: '§6/{0} setStage [new branch] [new stage]: §eSkip the current stage/start the branch/set a stage for a branch.' + startDialog: '§6/{0} startDialog : §eComienza el diálogo pendiente para una etapa del NPC o el diálogo de inicio para una misión.' resetPlayer: '§6/{0} restablecido jugador : §eElimina toda la información sobre un jugador.' resetPlayerQuest: '§6/{0} restablecida la Misión de Jugador [id]: §eElimina información de una misión para un jugador.' seePlayer: '§6/{0} seePlayer : §eVer la informacion sobre un jugador' reload: '§6/{0} reload: §eGuarda y recarga todas las configuraciones y archivos. (§cdeprecado§e)' start: '§6/{0} start [id]: §eForzar el inicio de una mision' setItem: '§6/{0} setItem : §eGuarda el elemento del holograma.' + setFirework: '§6/{0} setFirework: §eEdita el fuego artificial por defecto al finalizar.' adminMode: '§6/{0} adminMode: §eActiva el Modo de administración. (Útil para mostrar mensajes de poco registro.)' version: '§6/{0} version: §eVer la versión actual del plugin.' downloadTranslations: '§6/{0} descargaTraducciones : §eDescargas un archivo de traducción de vaina' @@ -168,6 +182,9 @@ msg: typeLocationRadius: '§aEscribe la distancia requerida de la ubicación:' typeGameTicks: '§aEscribe los ticks requeridos:' already: '§cYa estás en el editor.' + stage: + location: + typeWorldPattern: '§aEscribe una expresión para nombres de mundo:' enter: title: '§6~ Modo editor ~' subtitle: '§2♣§d§kii§a§lSkyblock§d§kii§2♣ §aEscribe "/quests exitEditor" para forzar la salida del editor.' @@ -235,11 +252,15 @@ msg: close: '&6close: &eGuardas y validas los mensajes.' setTime: '§6setTime + + io.github.Revxrsal + bukkit + 3.0.8 + + + io.github.Revxrsal + common + 3.0.8 + diff --git a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java index 2f9e1397..3ae650bf 100644 --- a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java +++ b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java @@ -20,7 +20,6 @@ import org.bstats.charts.SingleLineChart; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; -import org.bukkit.command.PluginCommand; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.configuration.file.YamlConfiguration; @@ -35,7 +34,6 @@ import fr.skytasul.quests.api.Locale; import fr.skytasul.quests.api.QuestsAPI; import fr.skytasul.quests.api.bossbar.BQBossBarImplementation; -import fr.skytasul.quests.commands.Commands; import fr.skytasul.quests.commands.CommandsManager; import fr.skytasul.quests.editors.Editor; import fr.skytasul.quests.gui.Inventories; @@ -43,9 +41,7 @@ import fr.skytasul.quests.gui.creation.QuestObjectGUI; import fr.skytasul.quests.gui.creation.stages.StagesGUI; import fr.skytasul.quests.gui.misc.ItemComparisonGUI; -import fr.skytasul.quests.gui.quests.PlayerListGUI; import fr.skytasul.quests.options.OptionAutoQuest; -import fr.skytasul.quests.players.PlayerAccount; import fr.skytasul.quests.players.PlayersManager; import fr.skytasul.quests.players.PlayersManagerDB; import fr.skytasul.quests.players.PlayersManagerYAML; @@ -130,13 +126,14 @@ public void onEnable(){ saveDefaultConfig(); NMS.getMCVersion(); - registerCommands(); saveFolder = new File(getDataFolder(), "quests"); if (!saveFolder.exists()) saveFolder.mkdirs(); loadDataFile(); loadConfigParameters(true); + registerCommands(); + try { dependencies.initializeCompatibilities(); }catch (Exception ex) { @@ -191,12 +188,23 @@ public void run() { logger.severe("This is a fatal error. Now disabling."); disable = true; setEnabled(false); + }catch (Exception ex) { + logger.severe("An unexpected exception occurred while loading plugin.", ex); + logger.severe("This is a fatal error. Now disabling."); + disable = true; + setEnabled(false); } } @Override public void onDisable(){ try { + try { + if (command != null) command.unload(); + }catch (Throwable ex) { + logger.severe("An error occurred while disabling command manager.", ex); + } + try { Editor.leaveAll(); Inventories.closeAll(); @@ -236,24 +244,8 @@ private void checkPaper() { } private void registerCommands(){ - command = new CommandsManager((sender) -> { - if (!(sender instanceof Player)) return; - Player p = (Player) sender; - if (!p.hasPermission("beautyquests.command.listPlayer")){ - Lang.INCORRECT_SYNTAX.send(p); - }else { - PlayerAccount acc = PlayersManager.getPlayerAccount(p); - if (acc == null) { - Lang.ERROR_OCCURED.send(p, "no account data"); - logger.severe("Player " + p.getName() + " has got no account. This is a CRITICAL issue."); - }else Inventories.create(p, new PlayerListGUI(acc)); - } - }); - PluginCommand cmd = getCommand("beautyquests"); - cmd.setPermission("beautyquests.command"); - cmd.setExecutor(command); - cmd.setTabCompleter(command); - command.registerCommandsClass(new Commands()); + command = new CommandsManager(); + command.initializeCommands(); } private void launchSaveCycle(){ @@ -355,15 +347,12 @@ private void loadConfigParameters(boolean init) throws LoadingException { ConfigurationSection dbConfig = config.getConfig().getConfigurationSection("database"); if (dbConfig.getBoolean("enabled")) { - try { - db = new Database(dbConfig); - db.testConnection(); + db = null; + try (Database newDB = new Database(dbConfig)) { + newDB.testConnection(); logger.info("Connection to database etablished."); + db = newDB; }catch (Exception ex) { - if (db != null) { - db.closeConnection(); - db = null; - } throw new LoadingException("Connection to database has failed.", ex); } } @@ -424,6 +413,7 @@ private void loadDataFile() throws LoadingException { private void loadAllDatas() throws Throwable { if (disable) return; dependencies.lockDependencies(); + command.lockCommands(); if (scoreboards == null && QuestsConfiguration.showScoreboards()) { File scFile = new File(getDataFolder(), "scoreboard.yml"); @@ -515,7 +505,11 @@ public void saveAllConfig(boolean unload) throws Exception { private void resetDatas(){ quests = null; pools = null; - if (db != null) db.closeConnection(); + try { + if (db != null) db.close(); + }catch (Exception ex) { + logger.severe("An error occurred while closing database connection.", ex); + } PlayersManager.manager = null; //HandlerList.unregisterAll(this); loaded = false; diff --git a/core/src/main/java/fr/skytasul/quests/commands/Cmd.java b/core/src/main/java/fr/skytasul/quests/commands/Cmd.java deleted file mode 100644 index 20e3d267..00000000 --- a/core/src/main/java/fr/skytasul/quests/commands/Cmd.java +++ /dev/null @@ -1,60 +0,0 @@ -package fr.skytasul.quests.commands; - -import static java.lang.annotation.ElementType.METHOD; -import static java.lang.annotation.RetentionPolicy.RUNTIME; - -import java.lang.annotation.Retention; -import java.lang.annotation.Target; - -import org.bukkit.entity.Player; - -import fr.skytasul.quests.api.npcs.BQNPC; -import fr.skytasul.quests.structure.Quest; - -@Retention(RUNTIME) -@Target(METHOD) -public @interface Cmd { - - /** - * If true, the command will not be executed if the executor is not a Player - * @return true if the command need to be executed by a player - */ - public boolean player() default false; - - /** - * Need {@link #player()} to be true - * @return if the player must not be in an inventory/editor system to execute the command - */ - public boolean noEditorInventory() default false; - - /** - * If arguments amount is lower than this value, the command will not be executed - * @return minimal amount of arguments for the command to be executed - */ - public int min() default 0; - - /** - * Available : - *
    - *
  • PLAYERS : list of players online - *
  • QUESTSID : list of all quests IDs - *
  • NPCSID : list of all NPCs IDs - *
  • xxx|yyy|zzz : available values, separated by a pipe (|) - *
- * In the case of PLAYERS QUESTSID and NPCSID, they will be directly replaced by - * an instance of {@link Player} / {@link Quest} / {@link BQNPC} when command executing - * (no need for String parsing) - * @return String array of possibles arguments - */ - public String[] args() default {}; - - /** - * Needed permission to execute this command (if empty, no permission will be required)
- * Final permission will be : beautyquests.command.XXXX - * @return name of the permission - */ - public String permission() default ""; - - public boolean hide() default false; - -} diff --git a/core/src/main/java/fr/skytasul/quests/commands/CommandContext.java b/core/src/main/java/fr/skytasul/quests/commands/CommandContext.java deleted file mode 100644 index 164d8739..00000000 --- a/core/src/main/java/fr/skytasul/quests/commands/CommandContext.java +++ /dev/null @@ -1,37 +0,0 @@ -package fr.skytasul.quests.commands; - -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -public class CommandContext { - - public final CommandsManager manager; - public final Object[] args; - public final String label; - public final CommandSender sender; - public final Player player; - - public CommandContext(CommandsManager manager, CommandSender sender, Object[] args, String label){ - this.manager = manager; - this.args = args; - this.label = label; - this.sender = sender; - if (sender instanceof Player){ - this.player = (Player) sender; - }else this.player = null; - } - - public boolean isPlayer(){ - return player != null; - } - - public T get(int arg) { - return get(arg, null); - } - - public T get(int arg, T def) { - if (args.length <= arg) return def; - return (T) args[arg]; - } - -} \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/commands/Commands.java b/core/src/main/java/fr/skytasul/quests/commands/Commands.java deleted file mode 100644 index c4dc0507..00000000 --- a/core/src/main/java/fr/skytasul/quests/commands/Commands.java +++ /dev/null @@ -1,711 +0,0 @@ -package fr.skytasul.quests.commands; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.net.URL; -import java.nio.channels.Channels; -import java.nio.channels.ReadableByteChannel; -import java.nio.file.Path; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.Optional; - -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.FireworkMeta; -import org.bukkit.inventory.meta.ItemMeta; - -import fr.skytasul.quests.BeautyQuests; -import fr.skytasul.quests.api.QuestsAPI; -import fr.skytasul.quests.api.events.accounts.PlayerAccountResetEvent; -import fr.skytasul.quests.api.npcs.BQNPC; -import fr.skytasul.quests.api.stages.AbstractStage; -import fr.skytasul.quests.api.stages.types.Dialogable; -import fr.skytasul.quests.editors.Editor; -import fr.skytasul.quests.editors.SelectNPC; -import fr.skytasul.quests.gui.Inventories; -import fr.skytasul.quests.gui.creation.QuestCreationSession; -import fr.skytasul.quests.gui.misc.ConfirmGUI; -import fr.skytasul.quests.gui.misc.ListBook; -import fr.skytasul.quests.gui.pools.PoolsManageGUI; -import fr.skytasul.quests.gui.quests.ChooseQuestGUI; -import fr.skytasul.quests.gui.quests.PlayerListGUI; -import fr.skytasul.quests.gui.quests.QuestsListGUI; -import fr.skytasul.quests.options.OptionStartDialog; -import fr.skytasul.quests.players.AdminMode; -import fr.skytasul.quests.players.PlayerAccount; -import fr.skytasul.quests.players.PlayerPoolDatas; -import fr.skytasul.quests.players.PlayerQuestDatas; -import fr.skytasul.quests.players.PlayersManager; -import fr.skytasul.quests.players.PlayersManagerDB; -import fr.skytasul.quests.players.PlayersManagerYAML; -import fr.skytasul.quests.rewards.CheckpointReward; -import fr.skytasul.quests.scoreboards.Scoreboard; -import fr.skytasul.quests.structure.BranchesManager; -import fr.skytasul.quests.structure.Quest; -import fr.skytasul.quests.structure.QuestBranch; -import fr.skytasul.quests.structure.pools.QuestPool; -import fr.skytasul.quests.utils.Database; -import fr.skytasul.quests.utils.Lang; -import fr.skytasul.quests.utils.MinecraftNames; -import fr.skytasul.quests.utils.Utils; -import fr.skytasul.quests.utils.nms.NMS; -import fr.skytasul.quests.utils.types.DialogRunner; - -public class Commands { - - @Cmd (permission = "create", args = "NPCSID", min = 1, hide = true) - public void testNPC(CommandContext cmd) { - BQNPC npc = cmd.get(0); - Utils.sendMessage(cmd.sender, npc.toString()); - npc.toggleDebug(); - } - - @Cmd (permission = "create", player = true, noEditorInventory = true) - public void create(CommandContext cmd){ - QuestCreationSession session = new QuestCreationSession(); - if (cmd.args.length == 1) { - Integer id = Utils.parseInt(cmd.sender, cmd.get(0)); - if (id == null) return; - if (QuestsAPI.getQuests().getQuest(id) != null) { - Utils.sendMessage(cmd.sender, "Invalid quest ID: another quest exists with ID {0}", id); - return; - } - session.setCustomID(id); - } - session.openMainGUI(cmd.player); - } - - @Cmd (permission = "edit", args = "QUESTSID", player = true, noEditorInventory = true) - public void edit(CommandContext cmd){ - if (cmd.args.length >= 1) { - new QuestCreationSession(cmd.get(0)).openMainGUI(cmd.player); - return; - } - Lang.CHOOSE_NPC_STARTER.send(cmd.player); - new SelectNPC(cmd.player, () -> {}, npc -> { - if (npc == null) return; - if (!npc.getQuests().isEmpty()) { - Inventories.create(cmd.player, new ChooseQuestGUI(npc.getQuests(), quest -> { - if (quest == null) return; - new QuestCreationSession(quest).openMainGUI(cmd.player); - })); - }else { - Lang.NPC_NOT_QUEST.send(cmd.player); - } - }).enter(); - } - - @Cmd(permission = "remove", args = "QUESTSID") - public void remove(CommandContext cmd){ - if (cmd.args.length >= 1){ - try{ - remove(cmd.sender, (Quest) cmd.args[0]); - }catch (NumberFormatException ex){ - Lang.NUMBER_INVALID.send(cmd.sender, cmd.args[1]); - } - return; - }else if (!cmd.isPlayer()){ - Lang.INCORRECT_SYNTAX.send(cmd.sender); - return; - } - Lang.CHOOSE_NPC_STARTER.send(cmd.sender); - new SelectNPC(cmd.player, () -> {}, npc -> { - if (npc == null) return; - if (!npc.getQuests().isEmpty()) { - Inventories.create(cmd.player, new ChooseQuestGUI(npc.getQuests(), quest -> { - if (quest == null) return; - remove(cmd.sender, quest); - })); - }else { - Lang.NPC_NOT_QUEST.send(cmd.sender); - } - }).enter(); - } - - @Cmd (permission = "manage") - public void reload(CommandContext cmd){ - BeautyQuests.getInstance().performReload(cmd.sender); - } - - @Cmd(permission = "version") - public void version(CommandContext cmd){ - cmd.sender.sendMessage("§eBeautyQuests version : §6§l" + BeautyQuests.getInstance().getDescription().getVersion()); - } - - @Cmd (permission = "manage") - public void save(CommandContext cmd){ - try { - BeautyQuests.getInstance().saveAllConfig(false); - cmd.sender.sendMessage("§aDatas saved!"); - BeautyQuests.logger.info("Datas saved ~ manual save from " + cmd.sender.getName()); - } catch (Throwable e) { - e.printStackTrace(); - cmd.sender.sendMessage("Error while saving the data file."); - } - } - - @Cmd(permission = "finish", min = 1, args = "PLAYERS") - public void finishAll(CommandContext cmd){ - Player target = (Player) cmd.args[0]; - PlayerAccount acc = PlayersManager.getPlayerAccount(target); - int success = 0; - int errors = 0; - for (Quest q : QuestsAPI.getQuests().getQuestsStarted(acc)) { - try{ - q.finish(target); - success++; - }catch (Throwable ex){ - ex.printStackTrace(); - errors++; - continue; - } - } - Lang.LEAVE_ALL_RESULT.send(cmd.sender, success, errors); - } - - @Cmd(permission = "finish", min = 2, args = {"PLAYERS", "QUESTSID"}) - public void finish(CommandContext cmd){ - Player target = (Player) cmd.args[0]; - try{ - ((Quest) cmd.args[1]).finish(target); - Lang.LEAVE_ALL_RESULT.send(cmd.sender, 1, 0); - }catch (Throwable ex){ - ex.printStackTrace(); - Lang.LEAVE_ALL_RESULT.send(cmd.sender, 1, 1); - } - } - - @Cmd(permission = "setStage", min = 2, args = {"PLAYERS", "QUESTSID", "0|1|2|3|4|5|6|7|8|9|10|11|12|13|14", "0|1|2|3|4|5|6|7|8|9|10|11|12|13|14"}) - public void setStage(CommandContext cmd){ - Player target = (Player) cmd.args[0]; - Quest qu = (Quest) cmd.args[1]; - PlayerAccount acc = PlayersManager.getPlayerAccount(target); - BranchesManager manager = qu.getBranchesManager(); // syntax: no arg: next or start | 1 arg: start branch | 2 args: set branch stage - - PlayerQuestDatas datas = acc.getQuestDatasIfPresent(qu); - if (cmd.args.length < 3 && (datas == null || !datas.hasStarted())) { // start quest - qu.start(target); - Lang.START_QUEST.send(cmd.sender, qu.getName(), acc.debugName()); - return; - } - if (datas == null) datas = acc.getQuestDatas(qu); // creates quest datas - - QuestBranch currentBranch = manager.getBranch(datas.getBranch()); - - if (cmd.args.length < 3) { // next - if (!datas.isInEndingStages()) { - currentBranch.finishStage(target, currentBranch.getRegularStage(datas.getStage())); - Lang.COMMAND_SETSTAGE_NEXT.send(cmd.sender); - }else Lang.COMMAND_SETSTAGE_NEXT_UNAVAILABLE.send(cmd.sender); - }else { - Integer branchID = Utils.parseInt(cmd.sender, (String) cmd.args[2]); - if (branchID == null) return; - QuestBranch branch = manager.getBranch(branchID); - if (branch == null){ - Lang.COMMAND_SETSTAGE_BRANCH_DOESNTEXIST.send(cmd.sender, branchID); - return; - } - - Integer stageID = -1; - if (cmd.args.length > 3){ - stageID = Utils.parseInt(cmd.sender, (String) cmd.args[3]); - if (stageID == null) return; - if (stageID < 0) { - Lang.NUMBER_NEGATIVE.send(cmd.sender); - return; - } - if (currentBranch == null) { - Lang.ERROR_OCCURED.send(cmd.sender, "player " + acc.debugName() + " has not started quest"); - return; - } - if (currentBranch.getRegularStages().size() <= stageID) { - Lang.COMMAND_SETSTAGE_STAGE_DOESNTEXIST.send(cmd.sender, stageID); - return; - } - } - Lang.COMMAND_SETSTAGE_SET.send(cmd.sender, stageID); - if (currentBranch != null) { - if (datas.isInEndingStages()) { - for (AbstractStage stage : currentBranch.getEndingStages().keySet()) stage.end(acc); - }else { - currentBranch.getRegularStage(datas.getStage()).end(acc); - } - } - if (cmd.args.length == 3){ // start branch - branch.start(acc); - }else { // set stage in branch - datas.setBranch(branchID); - branch.setStage(acc, stageID); - } - QuestsAPI.propagateQuestsHandlers(handler -> handler.questUpdated(acc, target, qu)); - } - } - - @Cmd (permission = "setStage", min = 2, args = { "PLAYERS", "QUESTSID" }) - public void startDialog(CommandContext cmd) { - Player target = (Player) cmd.args[0]; - Quest qu = (Quest) cmd.args[1]; - PlayerAccount acc = PlayersManager.getPlayerAccount(target); - PlayerQuestDatas datas = acc.getQuestDatasIfPresent(qu); - - DialogRunner runner = null; - if (datas == null || !qu.hasStarted(acc)) { - if (qu.hasOption(OptionStartDialog.class)) { - runner = qu.getOption(OptionStartDialog.class).getDialogRunner(); - } - }else { - if (datas.isInEndingStages() || datas.isInQuestEnd()) { - Lang.COMMAND_STARTDIALOG_IMPOSSIBLE.send(cmd.sender); - return; - }else { - AbstractStage stage = qu.getBranchesManager().getBranch(datas.getBranch()).getRegularStage(datas.getStage()); - if (stage instanceof Dialogable) { - runner = ((Dialogable) stage).getDialogRunner(); - } - } - } - - if (runner == null) { - Lang.COMMAND_STARTDIALOG_NO.send(cmd.sender); - }else { - if (runner.isPlayerInDialog(target)) { - Lang.COMMAND_STARTDIALOG_ALREADY.send(cmd.sender); - }else { - runner.handleNext(target); - Lang.COMMAND_STARTDIALOG_SUCCESS.send(cmd.sender, target.getName(), qu.getID()); - } - } - } - - @Cmd(permission = "resetPlayer", min = 1, args = "PLAYERS") - public void resetPlayer(CommandContext cmd){ - Player target = (Player) cmd.args[0]; - PlayerAccount acc = PlayersManager.getPlayerAccount(target); - int quests = 0, pools = 0; - for (PlayerQuestDatas questDatas : new ArrayList<>(acc.getQuestsDatas())) { - Quest quest = questDatas.getQuest(); - if (quest != null) { - quest.resetPlayer(acc); - }else acc.removeQuestDatas(questDatas.getQuestID()); - quests++; - } - for (PlayerPoolDatas poolDatas : new ArrayList<>(acc.getPoolDatas())) { - QuestPool pool = poolDatas.getPool(); - if (pool != null) { - pool.resetPlayer(acc); - }else acc.removePoolDatas(poolDatas.getPoolID()); - pools++; - } - Player player = acc.getPlayer(); - Bukkit.getPluginManager().callEvent(new PlayerAccountResetEvent(player, acc)); - if (acc.isCurrent()) Lang.DATA_REMOVED.send(player, quests, cmd.sender.getName(), pools); - Lang.DATA_REMOVED_INFO.send(cmd.sender, quests, target.getName(), pools); - } - - @Cmd(permission = "resetPlayer", min = 1, args = {"PLAYERS", "QUESTSID"}) - public void resetPlayerQuest(CommandContext cmd){ - Player target = (Player) cmd.args[0]; - PlayerAccount acc = PlayersManager.getPlayerAccount(target); - if (cmd.args.length > 1){ - Quest qu = (Quest) cmd.args[1]; - reset(cmd.sender, target, acc, qu); - }else if (cmd.isPlayer()){ - QuestsListGUI gui = new QuestsListGUI((obj) -> { - reset(cmd.sender, target, acc, obj); - }, acc, true, false, true); - Inventories.create(cmd.player, gui); - }else Lang.INCORRECT_SYNTAX.sendWP(cmd.sender); - } - - @Cmd (permission = "resetPlayer", min = 2, args = { "PLAYERS", "POOLSID", "BOOLEAN" }) - public void resetPlayerPool(CommandContext cmd) { - Player target = (Player) cmd.args[0]; - PlayerAccount acc = PlayersManager.getPlayerAccount(target); - QuestPool pool = cmd.get(1); - if (Boolean.parseBoolean(cmd.get(2, "false"))) { // only timer - pool.resetPlayerTimer(acc); - Lang.POOL_RESET_TIMER.send(cmd.sender, pool.getID(), target.getName()); - }else { - pool.resetPlayer(acc); - Lang.POOL_RESET_FULL.send(cmd.sender, pool.getID(), target.getName()); - } - } - - @Cmd(permission = "seePlayer", player = true, min = 1, args = "PLAYERS") - public void seePlayer(CommandContext cmd){ - Player target = (Player) cmd.args[0]; - new PlayerListGUI(PlayersManager.getPlayerAccount(target), false).create(cmd.player); - } - - @Cmd(permission = "resetQuest", min = 1, args = {"QUESTSID"}) - public void resetQuest(CommandContext cmd) { - Quest qu = (Quest) cmd.args[0]; - int amount = 0; - for (Player p : Bukkit.getOnlinePlayers()) { - if (qu.resetPlayer(PlayersManager.getPlayerAccount(p))) amount++; - } - amount += PlayersManager.manager.removeQuestDatas(qu); - Lang.QUEST_PLAYERS_REMOVED.send(cmd.sender, amount); - } - - @Cmd (permission = "start", min = 1, args = { "PLAYERS", "QUESTSID", "BOOLEAN" }) - public void start(CommandContext cmd){ - Player target = (Player) cmd.args[0]; - boolean testRequirements = !(CommandsManager.hasPermission(cmd.sender, "start.other", false) && (cmd.args.length > 2 ? Boolean.parseBoolean(cmd.get(2)) : false)); - if (cmd.isPlayer()){ - if (target == cmd.player){ - if (!CommandsManager.hasPermission(cmd.player, "start", true)) return; - }else if (!CommandsManager.hasPermission(cmd.player, "start.other", true)) return; - } - PlayerAccount acc = PlayersManager.getPlayerAccount(target); - if (cmd.args.length < 2 && cmd.isPlayer()){ - QuestsListGUI gui = new QuestsListGUI((obj) -> { - Quest qu = obj; - if (testRequirements && !qu.isLauncheable(target, acc, true)) { - Lang.START_QUEST_NO_REQUIREMENT.send(cmd.sender, qu.getName()); - return; - } - qu.start(target); - Lang.START_QUEST.send(cmd.sender, qu.getName(), acc.abstractAcc.getIdentifier()); - }, acc, false, true, false); - Inventories.create(cmd.player, gui); - }else if (cmd.args.length >= 2){ - Quest qu = (Quest) cmd.args[1]; - if (testRequirements && !qu.isLauncheable(target, acc, true)) { - Lang.START_QUEST_NO_REQUIREMENT.send(cmd.sender, qu.getName()); - return; - } - qu.start(target); - Lang.START_QUEST.send(cmd.sender, qu.getName(), acc.abstractAcc.getIdentifier()); - }else { - Lang.INCORRECT_SYNTAX.send(cmd.sender); - } - } - - @Cmd (permission = "cancel", min = 1, args = { "PLAYERS", "QUESTSID" }) - public void cancel(CommandContext cmd){ - Player target = (Player) cmd.args[0]; - if (cmd.isPlayer()){ - if (target != cmd.player && !CommandsManager.hasPermission(cmd.player, "cancel.other", true)) return; - } - PlayerAccount acc = PlayersManager.getPlayerAccount(target); - if (acc == null) { - Lang.PLAYER_DATA_NOT_FOUND.send(cmd.sender, target.getName()); - return; - } - - if (cmd.args.length < 2 && cmd.isPlayer()){ - QuestsListGUI gui = new QuestsListGUI((obj) -> { - cancelQuest(cmd.sender, acc, obj); - }, acc, true, false, false); - Inventories.create(cmd.player, gui); - }else if (cmd.args.length >= 2){ - cancelQuest(cmd.sender, acc, (Quest) cmd.args[1]); - }else { - Lang.INCORRECT_SYNTAX.send(cmd.sender); - } - } - - @Cmd(permission = "setItem", player = true, min = 1, args = "talk|launch|nolaunch") - public void setItem(CommandContext cmd){ - String name = (String) cmd.args[0]; - if (!"talk".equalsIgnoreCase(name) && !"launch".equalsIgnoreCase(name) && !"nolaunch".equalsIgnoreCase(name)){ - Lang.INCORRECT_SYNTAX.send(cmd.sender); - return; - } - ItemStack item = cmd.player.getInventory().getItemInMainHand(); - if (item.getType() == Material.AIR) { - BeautyQuests.getInstance().getDataFile().set(name.toLowerCase() + "Item", null); - Lang.ITEM_REMOVED.send(cmd.sender); - return; - } - BeautyQuests.getInstance().getDataFile().set(name.toLowerCase() + "Item", item.serialize()); - Lang.ITEM_CHANGED.send(cmd.sender); - } - - @Cmd (permission = "setItem", player = true) - public void setFirework(CommandContext cmd) { - if ("none".equals(cmd.get(0, null))) { - BeautyQuests.getInstance().getDataFile().set("firework", "none"); - Lang.FIREWORK_REMOVED.send(cmd.sender); - Lang.RESTART_SERVER.send(cmd.sender); - }else { - ItemMeta meta = cmd.player.getInventory().getItemInMainHand().getItemMeta(); - if (meta instanceof FireworkMeta) { - BeautyQuests.getInstance().getDataFile().set("firework", meta); - Lang.FIREWORK_EDITED.send(cmd.sender); - Lang.RESTART_SERVER.send(cmd.sender); - }else { - Lang.FIREWORK_INVALID_HAND.send(cmd.sender); - } - } - } - - @Cmd (permission = "manage", min = 1, args = "save|force") - public void backup(CommandContext cmd){ - if (cmd.args[0].equals("save")){ - save(cmd); - }else if (!cmd.args[0].equals("force")){ - Lang.INCORRECT_SYNTAX.send(cmd.sender); - return; - } - - boolean success = true; - BeautyQuests.logger.info("Creating backup due to " + cmd.sender.getName() + "'s manual command."); - Path backup = BeautyQuests.getInstance().backupDir(); - if (!BeautyQuests.getInstance().createFolderBackup(backup)) { - Lang.BACKUP_QUESTS_FAILED.send(cmd.sender); - success = false; - } - if (!BeautyQuests.getInstance().createDataBackup(backup)) { - Lang.BACKUP_PLAYERS_FAILED.send(cmd.sender); - success = false; - } - if (success) Lang.BACKUP_CREATED.send(cmd.sender); - } - - @Cmd(permission = "adminMode") - public void adminMode(CommandContext cmd){ - AdminMode.toggle(cmd.sender); - } - - @Cmd (player = true, hide = true) - public void exitEditor(CommandContext cmd){ - Editor.leave(cmd.player); - Inventories.closeAndExit(cmd.player); - } - - @Cmd (player = true, hide = true) - public void reopenInventory(CommandContext cmd){ - if (Inventories.isInSystem(cmd.player)){ - Inventories.openInventory(cmd.player); - } - } - - @Cmd(permission = "list", player = true) - public void list(CommandContext cmd){ - if (NMS.isValid()){ - ListBook.openQuestBook(cmd.player); - }else Utils.sendMessage(cmd.sender, "Version not supported"); - } - - @Cmd (args = { "PLAYERS", "setline|removeline|resetline|resetall|hide|show" }) - public void scoreboard(CommandContext cmd){ - if (cmd.args.length == 0) { - if (!cmd.isPlayer()) { - Lang.MUST_PLAYER.sendWP(cmd.sender); - return; - } - - if (!CommandsManager.hasPermission(cmd.sender, "scoreboard.toggle", true)) return; - Scoreboard board = BeautyQuests.getInstance().getScoreboardManager().getPlayerScoreboard(cmd.player); - if (board.isForceHidden()) { - board.show(true); - Lang.COMMAND_SCOREBOARD_OWN_SHOWN.send(cmd.player); - }else { - board.hide(true); - Lang.COMMAND_SCOREBOARD_OWN_HIDDEN.send(cmd.player); - } - }else { - if (!CommandsManager.hasPermission(cmd.sender, "scoreboard", true)) return; - if (cmd.args.length < 2) { - Lang.INCORRECT_SYNTAX.sendWP(cmd.sender); - return; - } - Player p = (Player) cmd.args[0]; - Scoreboard board = BeautyQuests.getInstance().getScoreboardManager().getPlayerScoreboard(p); - - switch (((String) cmd.args[1]).toLowerCase()) { - case "setline": - if (cmd.args.length < 4) { - Lang.INCORRECT_SYNTAX.send(cmd.sender); - break; - } - Integer id = Utils.parseInt(cmd.sender, (String) cmd.args[2]); - if (id == null) return; - board.setCustomLine(id, Utils.buildFromArray(cmd.args, 3, " ")); - Lang.COMMAND_SCOREBOARD_LINESET.send(cmd.sender, id); - break; - case "removeline": - if (cmd.args.length < 3) { - Lang.INCORRECT_SYNTAX.send(cmd.sender); - break; - } - id = Utils.parseInt(cmd.sender, (String) cmd.args[2]); - if (id == null) return; - if (board.removeLine(id)) { - Lang.COMMAND_SCOREBOARD_LINEREMOVE.send(cmd.sender, id); - }else Lang.COMMAND_SCOREBOARD_LINENOEXIST.send(cmd.sender, id); - break; - case "resetline": - if (cmd.args.length < 3) { - Lang.INCORRECT_SYNTAX.send(cmd.sender); - break; - } - id = Utils.parseInt(cmd.sender, (String) cmd.args[2]); - if (id == null) return; - if (board.resetLine(id)) { - Lang.COMMAND_SCOREBOARD_LINERESET.send(cmd.sender, id); - }else Lang.COMMAND_SCOREBOARD_LINENOEXIST.send(cmd.sender, id); - break; - case "resetall": - BeautyQuests.getInstance().getScoreboardManager().removePlayerScoreboard(p); - BeautyQuests.getInstance().getScoreboardManager().create(p); - Lang.COMMAND_SCOREBOARD_RESETALL.send(cmd.sender, p.getName()); - break; - case "hide": - board.hide(true); - Lang.COMMAND_SCOREBOARD_HIDDEN.send(cmd.sender, p.getName()); - break; - case "show": - board.show(true); - Lang.COMMAND_SCOREBOARD_SHOWN.send(cmd.sender, p.getName()); - break; - default: - Lang.INCORRECT_SYNTAX.send(cmd.sender); - break; - } - } - } - - @Cmd (player = true, permission = "pools") - public void pools(CommandContext cmd) { - PoolsManageGUI.get().create(cmd.player); - } - - @Cmd (player = true, args = "QUESTSID", min = 1) - public void checkpoint(CommandContext cmd) { - Quest quest = cmd.get(0); - PlayerAccount account = PlayersManager.getPlayerAccount(cmd.player); - if (account.hasQuestDatas(quest)) { - PlayerQuestDatas datas = account.getQuestDatas(quest); - QuestBranch branch = quest.getBranchesManager().getBranch(datas.getBranch()); - int max = datas.isInEndingStages() ? branch.getStageSize() : datas.getStage(); - for (int id = max - 1; id >= 0; id--) { - AbstractStage stage = branch.getRegularStage(id); - Optional optionalCheckpoint = stage.getRewards().stream().filter(CheckpointReward.class::isInstance).findAny().map(CheckpointReward.class::cast); - if (optionalCheckpoint.isPresent()) { - optionalCheckpoint.get().applies(cmd.player); - return; - } - } - Lang.COMMAND_CHECKPOINT_NO.send(cmd.sender, quest.getName()); - }else Lang.COMMAND_CHECKPOINT_NOT_STARTED.send(cmd.sender); - } - - @Cmd (permission = "manage") - public void downloadTranslations(CommandContext cmd) { - if (NMS.getMCVersion() < 13) { - Utils.sendMessage(cmd.sender, "§c" + Lang.VERSION_REQUIRED.toString(), "≥ 1.13"); - return; - } - if (cmd.args.length == 0) { - Lang.COMMAND_TRANSLATION_SYNTAX.send(cmd.sender); - return; - } - String lang = cmd.get(0).toLowerCase(); - String version = NMS.getVersionString(); - String url = MinecraftNames.LANG_DOWNLOAD_URL.replace("%version%", version).replace("%language%", lang); - - try { - File destination = new File(BeautyQuests.getInstance().getDataFolder(), lang + ".json"); - if (destination.isDirectory()) { - Lang.ERROR_OCCURED.send(cmd.sender, lang + ".json is a directory"); - return; - } - if (!(cmd.args.length > 1 && Boolean.parseBoolean(cmd.get(1))) && destination.exists()) { - Lang.COMMAND_TRANSLATION_EXISTS.send(cmd.sender, lang + ".json"); - return; - } - try (ReadableByteChannel channel = Channels.newChannel(new URL(url).openStream())) { - destination.createNewFile(); - try (FileOutputStream output = new FileOutputStream(destination)) { - output.getChannel().transferFrom(channel, 0, Long.MAX_VALUE); - Lang.COMMAND_TRANSLATION_DOWNLOADED.send(cmd.sender, lang); - } - }catch (FileNotFoundException ex) { - Lang.COMMAND_TRANSLATION_NOT_FOUND.send(cmd.sender, lang, version); - } - }catch (IOException e) { - Lang.ERROR_OCCURED.send(cmd.sender, "IO Exception when downloading translation."); - BeautyQuests.logger.severe("An error occurred while downloading translation.", e); - } - } - - @Cmd (permission = "manage") - public void migrateDatas(CommandContext cmd) { - if (!(PlayersManager.manager instanceof PlayersManagerYAML)) { - cmd.sender.sendMessage("§cYou can't migrate YAML datas to a DB system if you are already using the DB system."); - return; - } - Utils.runAsync(() -> { - cmd.sender.sendMessage("§aConnecting to the database."); - Database db = null; - try { - db = new Database(BeautyQuests.getInstance().getConfig().getConfigurationSection("database")); - db.testConnection(); - cmd.sender.sendMessage("§aConnection to database etablished."); - final Database fdb = db; - Utils.runSync(() -> { - cmd.sender.sendMessage("§aStarting migration..."); - try { - cmd.sender.sendMessage(PlayersManagerDB.migrate(fdb, (PlayersManagerYAML) PlayersManager.manager)); - }catch (Exception ex) { - cmd.sender.sendMessage("§cAn exception occured during migration. Process aborted. " + ex.getMessage()); - ex.printStackTrace(); - } - }); - }catch (SQLException ex) { - cmd.sender.sendMessage("§cConnection to database has failed. Aborting. " + ex.getMessage()); - BeautyQuests.logger.severe("An error occurred while connecting to the database for datas migration.", ex); - if (db != null) db.closeConnection(); - } - }); - } - - @Cmd(permission = "help") - public void help(CommandContext cmd){ - for (Lang l : Lang.values()){ - if (l.getPath().startsWith("msg.command.help.")){ - String command = l.getPath().substring(17); - if (command.equals("header")){ - l.sendWP(cmd.sender); - }else if (CommandsManager.hasPermission(cmd.sender, cmd.manager.commands.get(command.toLowerCase()).cmd.permission(), false)) l.sendWP(cmd.sender, cmd.label); - } - } - } - - private static void reset(CommandSender sender, Player target, PlayerAccount acc, Quest qu){ - qu.resetPlayer(acc); - if (acc.isCurrent()) Lang.DATA_QUEST_REMOVED.send(target, qu.getName(), sender.getName()); - Lang.DATA_QUEST_REMOVED_INFO.send(sender, target.getName(), qu.getName()); - } - - private static void remove(CommandSender sender, Quest quest){ - if (sender instanceof Player){ - Inventories.create((Player) sender, new ConfirmGUI(() -> { - quest.remove(true, true); - Lang.SUCCESFULLY_REMOVED.send(sender, quest.getName()); - }, ((Player) sender)::closeInventory, Lang.INDICATION_REMOVE.format(quest.getName()))); - }else { - quest.remove(true, true); - Lang.SUCCESFULLY_REMOVED.send(sender, quest.getName()); - } - } - - private static void cancelQuest(CommandSender sender, PlayerAccount acc, Quest qu){ - if (!sender.hasPermission("beautyquests.command.cancel.other") && !qu.isCancellable()){ - Lang.CANCEL_QUEST_UNAVAILABLE.send(sender, qu.getName()); - return; - } - qu.cancelPlayer(acc); - Lang.CANCEL_QUEST.send(sender, qu.getName()); - } - -} diff --git a/core/src/main/java/fr/skytasul/quests/commands/CommandsAdmin.java b/core/src/main/java/fr/skytasul/quests/commands/CommandsAdmin.java new file mode 100644 index 00000000..79aa0efb --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/commands/CommandsAdmin.java @@ -0,0 +1,302 @@ +package fr.skytasul.quests.commands; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.nio.file.Path; +import java.sql.SQLException; + +import org.bukkit.Material; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.FireworkMeta; +import org.bukkit.inventory.meta.ItemMeta; + +import fr.skytasul.quests.BeautyQuests; +import fr.skytasul.quests.api.QuestsAPI; +import fr.skytasul.quests.api.npcs.BQNPC; +import fr.skytasul.quests.editors.Editor; +import fr.skytasul.quests.editors.SelectNPC; +import fr.skytasul.quests.gui.Inventories; +import fr.skytasul.quests.gui.creation.QuestCreationSession; +import fr.skytasul.quests.gui.misc.ConfirmGUI; +import fr.skytasul.quests.gui.misc.ListBook; +import fr.skytasul.quests.gui.pools.PoolsManageGUI; +import fr.skytasul.quests.gui.quests.ChooseQuestGUI; +import fr.skytasul.quests.players.AdminMode; +import fr.skytasul.quests.players.PlayersManager; +import fr.skytasul.quests.players.PlayersManagerDB; +import fr.skytasul.quests.players.PlayersManagerYAML; +import fr.skytasul.quests.structure.Quest; +import fr.skytasul.quests.utils.Database; +import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.MinecraftNames; +import fr.skytasul.quests.utils.Utils; +import fr.skytasul.quests.utils.nms.NMS; + +import revxrsal.commands.annotation.Optional; +import revxrsal.commands.annotation.SecretCommand; +import revxrsal.commands.annotation.Subcommand; +import revxrsal.commands.annotation.Switch; +import revxrsal.commands.bukkit.BukkitCommandActor; +import revxrsal.commands.bukkit.annotation.CommandPermission; +import revxrsal.commands.exception.CommandErrorException; +import revxrsal.commands.orphan.OrphanCommand; + +public class CommandsAdmin implements OrphanCommand { + + @Subcommand ("create") + @CommandPermission (value = "beautyquests.command.create") + @OutsideEditor + public void create(Player player, @Optional Integer id) { + QuestCreationSession session = new QuestCreationSession(); + if (id != null) { + if (QuestsAPI.getQuests().getQuest(id) != null) + throw new CommandErrorException("Invalid quest ID: another quest exists with ID {0}", id); + + session.setCustomID(id); + } + session.openMainGUI(player); + } + + @Subcommand ("edit") + @CommandPermission (value = "beautyquests.command.edit") + @OutsideEditor + public void edit(Player player, @Optional Quest quest) { + if (quest != null) { + new QuestCreationSession(quest).openMainGUI(player); + }else { + Lang.CHOOSE_NPC_STARTER.send(player); + new SelectNPC(player, () -> {}, npc -> { + if (npc == null) return; + if (!npc.getQuests().isEmpty()) { + new ChooseQuestGUI(npc.getQuests(), questClicked -> { + if (questClicked == null) return; + new QuestCreationSession(questClicked).openMainGUI(player); + }).create(player); + }else { + Lang.NPC_NOT_QUEST.send(player); + } + }).enter(); + } + } + + @Subcommand ("remove") + @CommandPermission (value = "beautyquests.command.remove") + @OutsideEditor + public void remove(BukkitCommandActor actor, @Optional Quest quest) { + if (quest != null) { + remove(actor.getSender(), quest); + }else { + Lang.CHOOSE_NPC_STARTER.send(actor.requirePlayer()); + new SelectNPC(actor.getAsPlayer(), () -> {}, npc -> { + if (npc == null) return; + if (!npc.getQuests().isEmpty()) { + new ChooseQuestGUI(npc.getQuests(), questClicked -> { + if (questClicked == null) return; + remove(actor.getSender(), questClicked); + }).create(actor.getAsPlayer()); + }else { + Lang.NPC_NOT_QUEST.send(actor.getAsPlayer()); + } + }).enter(); + } + } + + private void remove(CommandSender sender, Quest quest) { + if (sender instanceof Player) { + Inventories.create((Player) sender, new ConfirmGUI(() -> { + quest.remove(true, true); + Lang.SUCCESFULLY_REMOVED.send(sender, quest.getName()); + }, ((Player) sender)::closeInventory, Lang.INDICATION_REMOVE.format(quest.getName()))); + }else { + quest.remove(true, true); + Lang.SUCCESFULLY_REMOVED.send(sender, quest.getName()); + } + } + + @Subcommand ("pools") + @CommandPermission ("beautyquests.command.pools") + public void pools(Player player) { + PoolsManageGUI.get().create(player); + } + + @Subcommand ("reload") + @CommandPermission ("beautyquests.command.manage") + public void reload(BukkitCommandActor actor) { + BeautyQuests.getInstance().performReload(actor.getSender()); + } + + @Subcommand ("save") + @CommandPermission ("beautyquests.command.manage") + public void save(BukkitCommandActor actor) { + try { + BeautyQuests.getInstance().saveAllConfig(false); + actor.reply("§aDatas saved!"); + BeautyQuests.logger.info("Datas saved ~ manual save from " + actor.getName()); + }catch (Throwable e) { + e.printStackTrace(); + actor.error("Error while saving the data file."); + } + } + + @Subcommand ("backup") + @CommandPermission ("beautyquests.command.manage") + public void backup(BukkitCommandActor actor, @Switch boolean force) { + if (!force) save(actor); + + boolean success = true; + BeautyQuests.logger.info("Creating backup due to " + actor.getName() + "'s manual command."); + Path backup = BeautyQuests.getInstance().backupDir(); + if (!BeautyQuests.getInstance().createFolderBackup(backup)) { + Lang.BACKUP_QUESTS_FAILED.send(actor.getSender()); + success = false; + } + if (!BeautyQuests.getInstance().createDataBackup(backup)) { + Lang.BACKUP_PLAYERS_FAILED.send(actor.getSender()); + success = false; + } + if (success) Lang.BACKUP_CREATED.send(actor.getSender()); + } + + @Subcommand ("adminMode") + @CommandPermission ("beautyquests.command.adminMode") + public void adminMode(BukkitCommandActor actor) { + AdminMode.toggle(actor.getSender()); + } + + @Subcommand ("exitEditor") + @SecretCommand + public void exitEditor(Player player) { + Editor.leave(player); + Inventories.closeAndExit(player); + } + + @Subcommand ("reopenInventory") + @SecretCommand + public void reopenInventory(Player player) { + if (Inventories.isInSystem(player)) { + Inventories.openInventory(player); + } + } + + @Subcommand ("list") + @CommandPermission ("beautyquests.command.list") + public void list(Player player) { + if (NMS.isValid()) { + ListBook.openQuestBook(player); + }else Utils.sendMessage(player, "Version not supported"); + } + + @Subcommand ("downloadTranslations") + @CommandPermission ("beautyquests.command.manage") + public void downloadTranslations(BukkitCommandActor actor, @Optional String lang, @Switch boolean overwrite) { + if (NMS.getMCVersion() < 13) + throw new CommandErrorException(Lang.VERSION_REQUIRED.format("≥ 1.13")); + + if (lang == null) + throw new CommandErrorException(Lang.COMMAND_TRANSLATION_SYNTAX.toString()); + + String version = NMS.getVersionString(); + String url = MinecraftNames.LANG_DOWNLOAD_URL.replace("%version%", version).replace("%language%", lang); + + try { + File destination = new File(BeautyQuests.getInstance().getDataFolder(), lang + ".json"); + if (destination.isDirectory()) + throw new CommandErrorException(Lang.ERROR_OCCURED.format(lang + ".json is a directory")); + if (!overwrite && destination.exists()) + throw new CommandErrorException(Lang.COMMAND_TRANSLATION_EXISTS.format(lang + ".json")); + + try (ReadableByteChannel channel = Channels.newChannel(new URL(url).openStream())) { + destination.createNewFile(); + try (FileOutputStream output = new FileOutputStream(destination)) { + output.getChannel().transferFrom(channel, 0, Long.MAX_VALUE); + Lang.COMMAND_TRANSLATION_DOWNLOADED.send(actor.getSender(), lang); + } + }catch (FileNotFoundException ex) { + throw new CommandErrorException(Lang.COMMAND_TRANSLATION_NOT_FOUND.format(lang, version)); + } + }catch (IOException e) { + BeautyQuests.logger.severe("An error occurred while downloading translation.", e); + throw new CommandErrorException(Lang.ERROR_OCCURED.format("IO Exception when downloading translation.")); + } + } + + @Subcommand ("migrateDatas") + @CommandPermission ("beautyquests.command.manage") + public void migrateDatas(BukkitCommandActor actor) { + if (!(PlayersManager.manager instanceof PlayersManagerYAML)) + throw new CommandErrorException("§cYou can't migrate YAML datas to a DB system if you are already using the DB system."); + + Utils.runAsync(() -> { + actor.reply("§aConnecting to the database."); + try (Database db = new Database(BeautyQuests.getInstance().getConfig().getConfigurationSection("database"))) { + db.testConnection(); + actor.reply("§aConnection to database etablished."); + final Database fdb = db; + Utils.runSync(() -> { + actor.reply("§aStarting migration..."); + try { + actor.reply(PlayersManagerDB.migrate(fdb, (PlayersManagerYAML) PlayersManager.manager)); + }catch (Exception ex) { + actor.error("An exception occured during migration. Process aborted. " + ex.getMessage()); + BeautyQuests.logger.severe("Error during data migration", ex); + } + }); + }catch (SQLException ex) { + actor.error("§cConnection to database has failed. Aborting. " + ex.getMessage()); + BeautyQuests.logger.severe("An error occurred while connecting to the database for datas migration.", ex); + } + }); + } + + @Subcommand ("setItem") + @CommandPermission ("beautyquests.command.setItem") + public void setItem(Player player, ItemHologram position) { + ItemStack item = player.getInventory().getItemInMainHand(); + if (item.getType() == Material.AIR) { + BeautyQuests.getInstance().getDataFile().set(position.name().toLowerCase() + "Item", null); + Lang.ITEM_REMOVED.send(player); + return; + } + BeautyQuests.getInstance().getDataFile().set(position.name().toLowerCase() + "Item", item.serialize()); + Lang.ITEM_CHANGED.send(player); + } + + @Subcommand ("setFirework") + @CommandPermission ("beautyquests.command.setItem") + public void setFirework(Player player, @Switch boolean remove) { + if (remove) { + BeautyQuests.getInstance().getDataFile().set("firework", "none"); + Lang.FIREWORK_REMOVED.send(player); + Lang.RESTART_SERVER.send(player); + }else { + ItemMeta meta = player.getInventory().getItemInMainHand().getItemMeta(); + if (meta instanceof FireworkMeta) { + BeautyQuests.getInstance().getDataFile().set("firework", meta); + Lang.FIREWORK_EDITED.send(player); + Lang.RESTART_SERVER.send(player); + }else { + Lang.FIREWORK_INVALID_HAND.send(player); + } + } + } + + @Subcommand ("testNPC") + @CommandPermission (value = "beautyquests.command.create") + @SecretCommand + public void testNPC(BukkitCommandActor actor, BQNPC npc) { + Utils.sendMessage(actor.getSender(), npc.toString()); + npc.toggleDebug(); + } + + public enum ItemHologram { + TALK, LAUNCH, NOLAUNCH; + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/commands/CommandsManager.java b/core/src/main/java/fr/skytasul/quests/commands/CommandsManager.java index 45151087..1e4910eb 100644 --- a/core/src/main/java/fr/skytasul/quests/commands/CommandsManager.java +++ b/core/src/main/java/fr/skytasul/quests/commands/CommandsManager.java @@ -1,218 +1,140 @@ - package fr.skytasul.quests.commands; - -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -import org.bukkit.Bukkit; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.command.TabCompleter; -import org.bukkit.entity.Player; - -import fr.skytasul.quests.BeautyQuests; -import fr.skytasul.quests.api.QuestsAPI; -import fr.skytasul.quests.api.npcs.BQNPC; -import fr.skytasul.quests.editors.Editor; -import fr.skytasul.quests.gui.Inventories; -import fr.skytasul.quests.structure.Quest; -import fr.skytasul.quests.structure.pools.QuestPool; -import fr.skytasul.quests.utils.DebugUtils; -import fr.skytasul.quests.utils.Lang; -import fr.skytasul.quests.utils.Utils; - -public class CommandsManager implements CommandExecutor, TabCompleter{ - - public final Map commands = new HashMap<>(); - private Consumer noArgs; - - /** - * @param noArgs RunnableObj(player) who'll be ran if the command is executed without any arguments (can be null) - */ - public CommandsManager(Consumer noArgs){ - this.noArgs = noArgs; - } - - /** - * Register all available commands from an instance of a Class - * @param commandsClassInstance Instance of the Class - */ - public void registerCommandsClass(Object commandsClassInstance){ - for(Method method : commandsClassInstance.getClass().getDeclaredMethods()){ - if (method.isAnnotationPresent(Cmd.class)){ - Cmd cmd = method.getDeclaredAnnotation(Cmd.class); - if (method.getParameterCount() == 1){ - if (method.getParameterTypes()[0] == CommandContext.class){ - this.commands.put(method.getName().toLowerCase(), new InternalCommand(cmd, method, commandsClassInstance)); - continue; - } - } - DebugUtils.logMessage("Error when loading command annotated method " + method.getName() + " in class " + commandsClassInstance.getClass().getName() + ". Needed argument: fr.skytasul.quests.commands.CommandContext"); - } - } - } - - @Override - public boolean onCommand(CommandSender sender, Command command, String label, String[] args){ - String commandString = "/" + label + " " + Utils.buildFromArray(args, 0, " "); - DebugUtils.logMessage(sender.getName() + " issued server command: " + commandString); - - if (args.length == 0){ - if (noArgs != null){ - noArgs.accept(sender); - }else Lang.INCORRECT_SYNTAX.sendWP(sender); - return false; - } - - InternalCommand internal = commands.get(args[0].toLowerCase()); - if (internal == null){ - Lang.COMMAND_DOESNT_EXIST.sendWP(sender); - return false; - } - - Cmd cmd = internal.cmd; - if (cmd.player() && !(sender instanceof Player)){ - Lang.MUST_PLAYER.sendWP(sender); - return false; - } - - if (!cmd.permission().isEmpty() && !hasPermission(sender, cmd.permission(), true)) return false; - - if (args.length - 1 < cmd.min()){ - Lang.INCORRECT_SYNTAX.sendWP(sender); - return false; - } - - if (cmd.player() && cmd.noEditorInventory() && (Inventories.isInSystem((Player) sender) || Editor.hasEditor((Player) sender))){ - Lang.ALREADY_EDITOR.send(sender); - return true; - } - - Object[] argsCmd = new Object[args.length - 1]; - for (int i = 1; i < args.length; i++){ - /*if (i > cmd.args().length){ - Lang.INCORRECT_SYNTAX.sendWP(sender); - return false; - }*/ - String arg = args[i]; - String type = i > cmd.args().length ? "" : cmd.args()[i-1]; - if (type.equals("PLAYERS")){ - Player target = Bukkit.getPlayerExact(arg); - if (target == null){ - Lang.PLAYER_NOT_ONLINE.send(sender, arg); - return false; - } - argsCmd[i-1] = target; - }else if (type.equals("QUESTSID")){ - Integer id = Utils.parseInt(sender, arg); - if (id == null) return false; - Quest qu = QuestsAPI.getQuests().getQuest(id); - if (qu == null){ - Lang.QUEST_INVALID.send(sender, id); - return false; - } - argsCmd[i-1] = qu; - }else if (type.equals("POOLSID")) { - Integer id = Utils.parseInt(sender, arg); - if (id == null) return false; - QuestPool pool = QuestsAPI.getQuestPools().getPool(id); - if (pool == null) { - Lang.POOL_INVALID.send(sender, id); - return false; - } - argsCmd[i - 1] = pool; - }else if (type.equals("NPCSID")){ - Integer id = Utils.parseInt(sender, arg); - if (id == null) return false; - BQNPC npc = QuestsAPI.getNPCsManager().getById(id); - if (npc == null){ - Lang.NPC_DOESNT_EXIST.send(sender, id); - return false; - } - argsCmd[i-1] = npc; - }else { - argsCmd[i-1] = arg; - } - } - - try { - DebugUtils.logMessage(sender.getName() + " invoked method \"" + internal.method.getName() + "\" from command: " + commandString); - internal.method.invoke(internal.commands, new CommandContext(this, sender, argsCmd, label)); - }catch (Exception e) { - Lang.ERROR_OCCURED.send(sender, e.getCause() == null ? e.getClass().getSimpleName() : e.getCause().getClass().getSimpleName()); - BeautyQuests.logger.severe("An exception occured during command execution:", e); - } - - return false; - } - - @Override - public List onTabComplete(CommandSender sender, Command command, String label, String[] args){ - List tmp = new ArrayList<>(); - List find = new ArrayList<>(); - String sel = args[0]; - - if (args.length == 1){ - for (Entry en : commands.entrySet()){ // PERMISSIONS - if (!en.getValue().cmd.hide() && hasPermission(sender, en.getValue().cmd.permission(), false)) find.add(en.getKey()); - } - }else if (args.length >= 2){ - int index = args.length-2; - if (!commands.containsKey(sel)) return tmp; - InternalCommand internal = commands.get(sel); - String[] needed = internal.cmd.args(); - if (needed.length <= index) return tmp; - if (!hasPermission(sender, internal.cmd.permission(), false)) return tmp; - sel = args[index + 1]; - String key = needed[index]; - if (key.equals("QUESTSID")){ - for (Quest quest : QuestsAPI.getQuests()) find.add(Integer.toString(quest.getID())); - }else if (key.equals("POOLSID")) { - for (QuestPool pool : QuestsAPI.getQuestPools().getPools()) find.add(Integer.toString(pool.getID())); - }else if (key.equals("PLAYERS")){ - return null; - }else if (key.equals("NPCSID")){ - find.addAll(QuestsAPI.getNPCsManager().getIDs().stream().map(String::valueOf).collect(Collectors.toList())); - }else if (key.equals("BOOLEAN")) { - find.add("false"); - find.add("true"); - }else { - find.addAll(Arrays.asList(key.split("\\|"))); - } - }else return tmp; - - for (String arg : find){ - if (arg.startsWith(sel)) tmp.add(arg); - } - return tmp; - } - - public static boolean hasPermission(CommandSender sender, String cmd, boolean message) { - if (cmd == null || cmd.isEmpty()) return true; - if (!sender.hasPermission(("beautyquests.command." + cmd))) { - if (message) Lang.PERMISSION_REQUIRED.sendWP(sender, "beautyquests.command." + cmd); - return false; - } - return true; - } - - class InternalCommand{ - Cmd cmd; - Method method; - Object commands; - - InternalCommand(Cmd cmd, Method method, Object commandsClass){ - this.cmd = cmd; - this.method = method; - this.commands = commandsClass; - } - } - -} \ No newline at end of file +package fr.skytasul.quests.commands; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Unmodifiable; + +import fr.skytasul.quests.BeautyQuests; +import fr.skytasul.quests.QuestsConfiguration; +import fr.skytasul.quests.api.QuestsAPI; +import fr.skytasul.quests.api.npcs.BQNPC; +import fr.skytasul.quests.editors.Editor; +import fr.skytasul.quests.gui.Inventories; +import fr.skytasul.quests.scoreboards.Scoreboard; +import fr.skytasul.quests.structure.Quest; +import fr.skytasul.quests.structure.pools.QuestPool; +import fr.skytasul.quests.utils.DebugUtils; +import fr.skytasul.quests.utils.Lang; + +import revxrsal.commands.autocomplete.SuggestionProvider; +import revxrsal.commands.bukkit.BukkitCommandActor; +import revxrsal.commands.bukkit.core.BukkitHandler; +import revxrsal.commands.command.CommandActor; +import revxrsal.commands.command.ExecutableCommand; +import revxrsal.commands.exception.CommandErrorException; +import revxrsal.commands.orphan.OrphanCommand; +import revxrsal.commands.orphan.Orphans; + +public class CommandsManager { + + private static String[] COMMAND_ALIASES = { "quests", "quest", "bq", "beautyquests", "bquests" }; + + private BukkitHandler handler; + private boolean locked = false; + + public CommandsManager() { + handler = new BukkitHandler(BeautyQuests.getInstance()); + handler.setMessagePrefix(QuestsConfiguration.getPrefix()); + handler.failOnTooManyArguments(); + + handler.registerValueResolver(Quest.class, context -> { + int id = context.popInt(); + Quest quest = QuestsAPI.getQuests().getQuest(id); + if (quest == null) + throw new CommandErrorException(Lang.QUEST_INVALID.format(id)); + return quest; + }); + handler.getAutoCompleter().registerParameterSuggestions(Quest.class, + SuggestionProvider.of(() -> QuestsAPI.getQuests().getQuests() + .stream() + .map(quest -> Integer.toString(quest.getID())) + .collect(Collectors.toList()))); + + handler.registerValueResolver(QuestPool.class, context -> { + int id = context.popInt(); + QuestPool pool = QuestsAPI.getQuestPools().getPool(id); + if (pool == null) + throw new CommandErrorException(Lang.POOL_INVALID.format(id)); + return pool; + }); + handler.getAutoCompleter().registerParameterSuggestions(QuestPool.class, + SuggestionProvider.of(() -> QuestsAPI.getQuestPools().getPools() + .stream() + .map(pool -> Integer.toString(pool.getID())) + .collect(Collectors.toList()))); + + handler.registerValueResolver(BQNPC.class, context -> { + int id = context.popInt(); + BQNPC npc = QuestsAPI.getNPCsManager().getById(id); + if (npc == null) + throw new CommandErrorException(Lang.NPC_DOESNT_EXIST.format(id)); + return npc; + }); + handler.getAutoCompleter().registerParameterSuggestions(BQNPC.class, + SuggestionProvider.of(() -> QuestsAPI.getNPCsManager().getIDs() + .stream() + .map(String::valueOf) + .collect(Collectors.toList()))); + + handler.registerCondition((@NotNull CommandActor actor, @NotNull ExecutableCommand command, @NotNull @Unmodifiable List arguments) -> { + if (command.hasAnnotation(OutsideEditor.class)) { + BukkitCommandActor bukkitActor = (BukkitCommandActor) actor; + if (bukkitActor.isPlayer() && (Inventories.isInSystem(bukkitActor.getAsPlayer()) || Editor.hasEditor(bukkitActor.getAsPlayer()))) + throw new CommandErrorException(Lang.ALREADY_EDITOR.toString()); + } + }); + + handler.setHelpWriter((command, actor) -> { + if (!command.hasPermission(actor)) return null; + for (Lang lang : Lang.values()) { + if (lang.getPath().startsWith("msg.command.help.")) { + String cmdKey = lang.getPath().substring(17); + if (cmdKey.equalsIgnoreCase(command.getName())) return lang.format(command.getPath().get(0)); + } + } + return null; + }); + + handler.registerContextResolver(Scoreboard.class, context -> { + return BeautyQuests.getInstance().getScoreboardManager().getPlayerScoreboard(context.getResolvedArgument(Player.class)); + }); + } + + public BukkitHandler getHandler() { + return handler; + } + + public void initializeCommands() { + handler.register(new CommandsRoot()); + + registerCommands("", new CommandsAdmin(), new CommandsPlayer(), new CommandsPlayerManagement()); + registerCommands("scoreboard", new CommandsScoreboard()); + } + + public void registerCommands(String subpath, OrphanCommand... commands) { + Orphans path; + if (subpath == null || subpath.isEmpty()) { + path = Orphans.path(COMMAND_ALIASES); + }else { + path = Orphans.path(Arrays.stream(COMMAND_ALIASES).map(x -> x + " " + subpath).toArray(String[]::new)); + } + handler.register(Arrays.stream(commands).map(path::handler).toArray()); + if (locked) BeautyQuests.logger.warning("Registered commands after final locking."); + } + + public void lockCommands() { + if (locked) throw new IllegalStateException("Commands manager already locked."); + locked = true; + handler.registerBrigadier(); + if (handler.isBrigadierSupported()) DebugUtils.logMessage("Brigadier supported!"); + } + + public void unload() { + handler.unregisterAllCommands(); + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/commands/CommandsPlayer.java b/core/src/main/java/fr/skytasul/quests/commands/CommandsPlayer.java new file mode 100644 index 00000000..fcbca2e4 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/commands/CommandsPlayer.java @@ -0,0 +1,59 @@ +package fr.skytasul.quests.commands; + +import java.util.Optional; + +import org.bukkit.entity.Player; + +import fr.skytasul.quests.BeautyQuests; +import fr.skytasul.quests.api.stages.AbstractStage; +import fr.skytasul.quests.gui.quests.PlayerListGUI; +import fr.skytasul.quests.players.PlayerAccount; +import fr.skytasul.quests.players.PlayerQuestDatas; +import fr.skytasul.quests.players.PlayersManager; +import fr.skytasul.quests.rewards.CheckpointReward; +import fr.skytasul.quests.structure.Quest; +import fr.skytasul.quests.structure.QuestBranch; +import fr.skytasul.quests.utils.Lang; + +import revxrsal.commands.annotation.Default; +import revxrsal.commands.annotation.Subcommand; +import revxrsal.commands.bukkit.BukkitCommandActor; +import revxrsal.commands.bukkit.annotation.CommandPermission; +import revxrsal.commands.command.ExecutableCommand; +import revxrsal.commands.exception.CommandErrorException; +import revxrsal.commands.orphan.OrphanCommand; + +public class CommandsPlayer implements OrphanCommand { + + @Default + @CommandPermission ("beautyquests.command.listPlayer") + public void menu(BukkitCommandActor actor, ExecutableCommand command, @revxrsal.commands.annotation.Optional String subcommand) { + if (subcommand != null) throw new revxrsal.commands.exception.InvalidSubcommandException(command.getPath(), subcommand); + PlayerAccount acc = PlayersManager.getPlayerAccount(actor.requirePlayer()); + if (acc == null) { + BeautyQuests.logger.severe("Player " + actor.getName() + " has got no account. This is a CRITICAL issue."); + throw new CommandErrorException("no player datas"); + }else new PlayerListGUI(acc).create(actor.getAsPlayer()); + } + + @Subcommand ("checkpoint") + @CommandPermission ("beautyquests.command.checkpoint") + public void checkpoint(Player player, Quest quest) { + PlayerAccount account = PlayersManager.getPlayerAccount(player); + if (account.hasQuestDatas(quest)) { + PlayerQuestDatas datas = account.getQuestDatas(quest); + QuestBranch branch = quest.getBranchesManager().getBranch(datas.getBranch()); + int max = datas.isInEndingStages() ? branch.getStageSize() : datas.getStage(); + for (int id = max - 1; id >= 0; id--) { + AbstractStage stage = branch.getRegularStage(id); + Optional optionalCheckpoint = stage.getRewards().stream().filter(CheckpointReward.class::isInstance).findAny().map(CheckpointReward.class::cast); + if (optionalCheckpoint.isPresent()) { + optionalCheckpoint.get().applies(player); + return; + } + } + Lang.COMMAND_CHECKPOINT_NO.send(player, quest.getName()); + }else Lang.COMMAND_CHECKPOINT_NOT_STARTED.send(player); + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/commands/CommandsPlayerManagement.java b/core/src/main/java/fr/skytasul/quests/commands/CommandsPlayerManagement.java new file mode 100644 index 00000000..5152fcda --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/commands/CommandsPlayerManagement.java @@ -0,0 +1,296 @@ +package fr.skytasul.quests.commands; + +import java.util.ArrayList; + +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.permissions.Permission; + +import fr.skytasul.quests.api.QuestsAPI; +import fr.skytasul.quests.api.events.accounts.PlayerAccountResetEvent; +import fr.skytasul.quests.api.stages.AbstractStage; +import fr.skytasul.quests.api.stages.types.Dialogable; +import fr.skytasul.quests.gui.quests.PlayerListGUI; +import fr.skytasul.quests.gui.quests.QuestsListGUI; +import fr.skytasul.quests.options.OptionStartDialog; +import fr.skytasul.quests.players.PlayerAccount; +import fr.skytasul.quests.players.PlayerPoolDatas; +import fr.skytasul.quests.players.PlayerQuestDatas; +import fr.skytasul.quests.players.PlayersManager; +import fr.skytasul.quests.structure.BranchesManager; +import fr.skytasul.quests.structure.Quest; +import fr.skytasul.quests.structure.QuestBranch; +import fr.skytasul.quests.structure.pools.QuestPool; +import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.types.DialogRunner; + +import revxrsal.commands.annotation.Optional; +import revxrsal.commands.annotation.Range; +import revxrsal.commands.annotation.Subcommand; +import revxrsal.commands.annotation.Switch; +import revxrsal.commands.bukkit.BukkitCommandActor; +import revxrsal.commands.bukkit.BukkitCommandPermission; +import revxrsal.commands.bukkit.annotation.CommandPermission; +import revxrsal.commands.command.ExecutableCommand; +import revxrsal.commands.exception.CommandErrorException; +import revxrsal.commands.exception.NoPermissionException; +import revxrsal.commands.orphan.OrphanCommand; + +public class CommandsPlayerManagement implements OrphanCommand { + + private BukkitCommandPermission startOtherPermission = new BukkitCommandPermission(new Permission("beautyquests.command.start.other")); + private BukkitCommandPermission cancelOtherPermission = new BukkitCommandPermission(new Permission("beautyquests.command.cancel.other")); + + @Subcommand ("finishAll") + @CommandPermission ("beautyquests.command.finish") + public void finishAll(BukkitCommandActor actor, Player player) { + PlayerAccount acc = PlayersManager.getPlayerAccount(player); + int success = 0; + int errors = 0; + for (Quest q : QuestsAPI.getQuests().getQuestsStarted(acc)) { + try { + q.finish(player); + success++; + }catch (Throwable ex) { + ex.printStackTrace(); + errors++; + } + } + Lang.LEAVE_ALL_RESULT.send(actor.getSender(), success, errors); + } + + @Subcommand ("finish") + @CommandPermission ("beautyquests.command.finish") + public void finish(BukkitCommandActor actor, Player player, Quest quest) { + try { + quest.finish(player); + Lang.LEAVE_ALL_RESULT.send(actor.getSender(), 1, 0); + }catch (Throwable ex) { + ex.printStackTrace(); + Lang.LEAVE_ALL_RESULT.send(actor.getSender(), 1, 1); + } + } + + @Subcommand ("setStage") + @CommandPermission ("beautyquests.command.setStage") + public void setStage( + BukkitCommandActor actor, + Player player, + Quest quest, + @Range (min = 0, max = 14) @Optional Integer branchID, + @Range (min = 0, max = 14) @Optional Integer stageID) { + PlayerAccount acc = PlayersManager.getPlayerAccount(player); + BranchesManager manager = quest.getBranchesManager(); // syntax: no arg: next or start | 1 arg: start branch | 2 args: set branch stage + + PlayerQuestDatas datas = acc.getQuestDatasIfPresent(quest); + if (branchID == null && (datas == null || !datas.hasStarted())) { // start quest + quest.start(player); + Lang.START_QUEST.send(actor.getSender(), quest.getName(), acc.debugName()); + return; + } + if (datas == null) datas = acc.getQuestDatas(quest); // creates quest datas + + QuestBranch currentBranch = manager.getBranch(datas.getBranch()); + + if (branchID == null) { // next + if (!datas.isInEndingStages()) { + currentBranch.finishStage(player, currentBranch.getRegularStage(datas.getStage())); + Lang.COMMAND_SETSTAGE_NEXT.send(actor.getSender()); + }else Lang.COMMAND_SETSTAGE_NEXT_UNAVAILABLE.send(actor.getSender()); + }else { + QuestBranch branch = manager.getBranch(branchID); + if (branch == null) + throw new CommandErrorException(Lang.COMMAND_SETSTAGE_BRANCH_DOESNTEXIST.format(branchID)); + + if (stageID != null) { + if (currentBranch == null) + throw new CommandErrorException(Lang.ERROR_OCCURED.format("player " + acc.debugName() + " has not started quest")); + if (currentBranch.getRegularStages().size() <= stageID) + throw new CommandErrorException(Lang.COMMAND_SETSTAGE_STAGE_DOESNTEXIST.format(stageID)); + } + Lang.COMMAND_SETSTAGE_SET.send(actor.getSender(), stageID); + if (currentBranch != null) { + if (datas.isInEndingStages()) { + for (AbstractStage stage : currentBranch.getEndingStages().keySet()) stage.end(acc); + }else { + currentBranch.getRegularStage(datas.getStage()).end(acc); + } + } + if (stageID == null) { // start branch + branch.start(acc); + }else { // set stage in branch + datas.setBranch(branchID); + branch.setStage(acc, stageID); + } + QuestsAPI.propagateQuestsHandlers(handler -> handler.questUpdated(acc, player, quest)); + } + } + + @Subcommand ("startDialog") + @CommandPermission ("beautyquests.command.setStage") + public void startDialog(BukkitCommandActor actor, Player player, Quest quest) { + PlayerAccount acc = PlayersManager.getPlayerAccount(player); + PlayerQuestDatas datas = acc.getQuestDatasIfPresent(quest); + + DialogRunner runner = null; + if (datas == null || !quest.hasStarted(acc)) { + if (quest.hasOption(OptionStartDialog.class)) { + runner = quest.getOption(OptionStartDialog.class).getDialogRunner(); + } + }else { + if (datas.isInEndingStages() || datas.isInQuestEnd()) { + Lang.COMMAND_STARTDIALOG_IMPOSSIBLE.send(actor.getSender()); + return; + }else { + AbstractStage stage = quest.getBranchesManager().getBranch(datas.getBranch()).getRegularStage(datas.getStage()); + if (stage instanceof Dialogable) { + runner = ((Dialogable) stage).getDialogRunner(); + } + } + } + + if (runner == null) { + Lang.COMMAND_STARTDIALOG_NO.send(actor.getSender()); + }else { + if (runner.isPlayerInDialog(player)) { + Lang.COMMAND_STARTDIALOG_ALREADY.send(actor.getSender()); + }else { + runner.handleNext(player); + Lang.COMMAND_STARTDIALOG_SUCCESS.send(actor.getSender(), player.getName(), quest.getID()); + } + } + } + + @Subcommand ("resetPlayer") + @CommandPermission ("beautyquests.command.resetPlayer") + public void resetPlayer(BukkitCommandActor actor, Player player) { + PlayerAccount acc = PlayersManager.getPlayerAccount(player); + int quests = 0, pools = 0; + for (PlayerQuestDatas questDatas : new ArrayList<>(acc.getQuestsDatas())) { + Quest quest = questDatas.getQuest(); + if (quest != null) { + quest.resetPlayer(acc); + }else acc.removeQuestDatas(questDatas.getQuestID()); + quests++; + } + for (PlayerPoolDatas poolDatas : new ArrayList<>(acc.getPoolDatas())) { + QuestPool pool = poolDatas.getPool(); + if (pool != null) { + pool.resetPlayer(acc); + }else acc.removePoolDatas(poolDatas.getPoolID()); + pools++; + } + Bukkit.getPluginManager().callEvent(new PlayerAccountResetEvent(player, acc)); + if (acc.isCurrent()) Lang.DATA_REMOVED.send(player, quests, actor.getName(), pools); + Lang.DATA_REMOVED_INFO.send(actor.getSender(), quests, player.getName(), pools); + } + + @Subcommand ("resetPlayerQuest") + @CommandPermission ("beautyquests.command.resetPlayer") + public void resetPlayerQuest(BukkitCommandActor actor, Player player, @Optional Quest quest) { + PlayerAccount acc = PlayersManager.getPlayerAccount(player); + if (quest != null) { + reset(actor.getSender(), player, acc, quest); + }else { + new QuestsListGUI(obj -> { + reset(actor.getSender(), player, acc, obj); + }, acc, true, false, true).create(actor.requirePlayer()); + } + } + + private void reset(CommandSender sender, Player target, PlayerAccount acc, Quest qu) { + qu.resetPlayer(acc); + if (acc.isCurrent()) Lang.DATA_QUEST_REMOVED.send(target, qu.getName(), sender.getName()); + Lang.DATA_QUEST_REMOVED_INFO.send(sender, target.getName(), qu.getName()); + } + + @Subcommand ("resetPlayerPool") + @CommandPermission ("beautyquests.command.resetPlayer") + public void resetPlayerPool(BukkitCommandActor actor, Player player, QuestPool pool, @Switch boolean timer) { + PlayerAccount acc = PlayersManager.getPlayerAccount(player); + if (timer) { + pool.resetPlayerTimer(acc); + Lang.POOL_RESET_TIMER.send(actor.getSender(), pool.getID(), player.getName()); + }else { + pool.resetPlayer(acc); + Lang.POOL_RESET_FULL.send(actor.getSender(), pool.getID(), player.getName()); + } + } + + @Subcommand ("resetQuest") + @CommandPermission ("beautyquests.command.resetQuest") + public void resetQuest(BukkitCommandActor actor, Quest quest) { + int amount = 0; + for (Player p : Bukkit.getOnlinePlayers()) { + if (quest.resetPlayer(PlayersManager.getPlayerAccount(p))) amount++; + } + amount += PlayersManager.manager.removeQuestDatas(quest); + Lang.QUEST_PLAYERS_REMOVED.send(actor.getSender(), amount); + } + + @Subcommand ("seePlayer") + @CommandPermission ("beautyquests.command.seePlayer") + public void seePlayer(Player actor, Player player) { + new PlayerListGUI(PlayersManager.getPlayerAccount(player), false).create(actor); + } + + @Subcommand ("start") + @CommandPermission ("beautyquests.command.start") + public void start(BukkitCommandActor actor, ExecutableCommand command, Player player, @Optional Quest quest, @CommandPermission ("beautyquests.command.start.other") @Switch boolean overrideRequirements) { + if (actor.isPlayer() && (actor.getAsPlayer() != player)) { + // the executor wants to start a quest for somebody else + if (!startOtherPermission.canExecute(actor)) + throw new NoPermissionException(command, startOtherPermission); + } + + PlayerAccount acc = PlayersManager.getPlayerAccount(player); + + if (quest == null) { + new QuestsListGUI(obj -> { + start(actor.getSender(), player, acc, obj, overrideRequirements); + }, acc, false, true, false).create(actor.requirePlayer()); + }else { + start(actor.getSender(), player, acc, quest, overrideRequirements); + } + } + + private void start(CommandSender sender, Player player, PlayerAccount acc, Quest quest, boolean overrideRequirements) { + if (!overrideRequirements && !quest.isLauncheable(player, acc, true)) { + Lang.START_QUEST_NO_REQUIREMENT.send(sender, quest.getName()); + return; + } + quest.start(player); + Lang.START_QUEST.send(sender, quest.getName(), acc.abstractAcc.getIdentifier()); + } + + @Subcommand ("cancel") + @CommandPermission ("beautyquests.command.cancel") + public void cancel(BukkitCommandActor actor, ExecutableCommand command, Player player, @Optional Quest quest) { + if (actor.isPlayer() && (actor.getAsPlayer() != player)) { + // the executor wants to start a quest for somebody else + if (!cancelOtherPermission.canExecute(actor)) + throw new NoPermissionException(command, cancelOtherPermission); + } + + PlayerAccount acc = PlayersManager.getPlayerAccount(player); + + if (quest == null) { + new QuestsListGUI(obj -> { + cancel(actor.getSender(), player, acc, obj); + }, acc, true, false, false).create(actor.requirePlayer()); + }else { + cancel(actor.getSender(), player, acc, quest); + } + } + + private void cancel(CommandSender sender, Player player, PlayerAccount acc, Quest quest) { + if (!sender.hasPermission(cancelOtherPermission.getPermission()) && !quest.isCancellable()) { + Lang.CANCEL_QUEST_UNAVAILABLE.send(sender, quest.getName()); + return; + } + quest.cancelPlayer(acc); + Lang.CANCEL_QUEST.send(sender, quest.getName()); + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/commands/CommandsRoot.java b/core/src/main/java/fr/skytasul/quests/commands/CommandsRoot.java new file mode 100644 index 00000000..554586f2 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/commands/CommandsRoot.java @@ -0,0 +1,31 @@ +package fr.skytasul.quests.commands; + +import fr.skytasul.quests.BeautyQuests; +import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.Utils; + +import revxrsal.commands.annotation.Command; +import revxrsal.commands.annotation.Description; +import revxrsal.commands.annotation.Subcommand; +import revxrsal.commands.bukkit.BukkitCommandActor; +import revxrsal.commands.bukkit.annotation.CommandPermission; +import revxrsal.commands.help.CommandHelp; + +@Command ({ "quests", "quest", "bq", "beautyquests", "bquests" }) +@Description ("Main command for quests") +@CommandPermission ("beautyquests.command") +public class CommandsRoot { + + @Subcommand ("help") + public void help(BukkitCommandActor actor, CommandHelp helpEntries) { + Lang.COMMAND_HELP.sendWP(actor.getSender()); + helpEntries.forEach(help -> Utils.sendMessageWP(actor.getSender(), help)); + } + + @Subcommand ("version") + @CommandPermission ("beautyquests.command.version") + public void version(BukkitCommandActor actor) { + actor.reply("§eBeautyQuests version : §6§l" + BeautyQuests.getInstance().getDescription().getVersion()); + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/commands/CommandsScoreboard.java b/core/src/main/java/fr/skytasul/quests/commands/CommandsScoreboard.java new file mode 100644 index 00000000..06e03afc --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/commands/CommandsScoreboard.java @@ -0,0 +1,78 @@ +package fr.skytasul.quests.commands; + +import org.bukkit.entity.Player; + +import fr.skytasul.quests.BeautyQuests; +import fr.skytasul.quests.scoreboards.Scoreboard; +import fr.skytasul.quests.utils.Lang; + +import revxrsal.commands.annotation.Default; +import revxrsal.commands.annotation.Optional; +import revxrsal.commands.annotation.Range; +import revxrsal.commands.annotation.Subcommand; +import revxrsal.commands.bukkit.BukkitCommandActor; +import revxrsal.commands.bukkit.annotation.CommandPermission; +import revxrsal.commands.command.ExecutableCommand; +import revxrsal.commands.orphan.OrphanCommand; + +public class CommandsScoreboard implements OrphanCommand { + + @Default + @CommandPermission ("beautyquests.command.scoreboard.toggle") + public void scoreboardToggle(Player player, ExecutableCommand command, Scoreboard scoreboard, @Optional String subcommand) { + if (subcommand != null) throw new revxrsal.commands.exception.InvalidSubcommandException(command.getPath(), subcommand); + if (scoreboard.isForceHidden()) { + scoreboard.show(true); + Lang.COMMAND_SCOREBOARD_OWN_SHOWN.send(player); + }else { + scoreboard.hide(true); + Lang.COMMAND_SCOREBOARD_OWN_HIDDEN.send(player); + } + } + + @Subcommand ("setline") + @CommandPermission ("beautyquests.command.scoreboard") + public void setline(BukkitCommandActor actor, Player player, Scoreboard scoreboard, @Range (min = 0) int line, String text) { + scoreboard.setCustomLine(line, text); + Lang.COMMAND_SCOREBOARD_LINESET.send(actor.getSender(), line); + } + + @Subcommand ("removeline") + @CommandPermission ("beautyquests.command.scoreboard") + public void removeline(BukkitCommandActor actor, Player player, Scoreboard scoreboard, @Range (min = 0) int line) { + if (scoreboard.removeLine(line)) { + Lang.COMMAND_SCOREBOARD_LINEREMOVE.send(actor.getSender(), line); + }else Lang.COMMAND_SCOREBOARD_LINENOEXIST.send(actor.getSender(), line); + } + + @Subcommand ("resetline") + @CommandPermission ("beautyquests.command.scoreboard") + public void resetline(BukkitCommandActor actor, Player player, Scoreboard scoreboard, @Range (min = 0) int line) { + if (scoreboard.resetLine(line)) { + Lang.COMMAND_SCOREBOARD_LINERESET.send(actor.getSender(), line); + }else Lang.COMMAND_SCOREBOARD_LINENOEXIST.send(actor.getSender(), line); + } + + @Subcommand ("resetall") + @CommandPermission ("beautyquests.command.scoreboard") + public void resetall(BukkitCommandActor actor, Player player) { + BeautyQuests.getInstance().getScoreboardManager().removePlayerScoreboard(player); + BeautyQuests.getInstance().getScoreboardManager().create(player); + Lang.COMMAND_SCOREBOARD_RESETALL.send(actor.getSender(), player.getName()); + } + + @Subcommand ("show") + @CommandPermission ("beautyquests.command.scoreboard") + public void show(BukkitCommandActor actor, Player player, Scoreboard scoreboard) { + scoreboard.show(true); + Lang.COMMAND_SCOREBOARD_SHOWN.send(actor.getSender(), player.getName()); + } + + @Subcommand ("hide") + @CommandPermission ("beautyquests.command.scoreboard") + public void hide(BukkitCommandActor actor, Player player, Scoreboard scoreboard) { + scoreboard.hide(true); + Lang.COMMAND_SCOREBOARD_HIDDEN.send(actor.getSender(), player.getName()); + } + +} \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/commands/OutsideEditor.java b/core/src/main/java/fr/skytasul/quests/commands/OutsideEditor.java new file mode 100644 index 00000000..d8e8f7fc --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/commands/OutsideEditor.java @@ -0,0 +1,11 @@ +package fr.skytasul.quests.commands; + +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention (RUNTIME) +@Target (METHOD) +public @interface OutsideEditor {} diff --git a/core/src/main/java/fr/skytasul/quests/gui/quests/QuestsListGUI.java b/core/src/main/java/fr/skytasul/quests/gui/quests/QuestsListGUI.java index 4101f832..e82204a1 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/quests/QuestsListGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/quests/QuestsListGUI.java @@ -25,7 +25,7 @@ public QuestsListGUI(Consumer run, PlayerAccount acc, boolean started, bo super(Lang.INVENTORY_QUESTS_LIST.toString(), DyeColor.CYAN, new ArrayList<>(), null, Quest::getName); if (acc != null){ if (started) super.objects.addAll(QuestsAPI.getQuests().getQuestsStarted(acc)); - if (notStarted) super.objects.addAll(QuestsAPI.getQuests().getQuestsNotStarted(acc, false, false)); + if (notStarted) super.objects.addAll(QuestsAPI.getQuests().getQuestsNotStarted(acc, false, true)); if (finished) super.objects.addAll(QuestsAPI.getQuests().getQuestsFinished(acc, false)); }else super.objects.addAll(QuestsAPI.getQuests().getQuests()); this.run = run; diff --git a/core/src/main/java/fr/skytasul/quests/scoreboards/Scoreboard.java b/core/src/main/java/fr/skytasul/quests/scoreboards/Scoreboard.java index 23c5c5ba..da0542c1 100644 --- a/core/src/main/java/fr/skytasul/quests/scoreboards/Scoreboard.java +++ b/core/src/main/java/fr/skytasul/quests/scoreboards/Scoreboard.java @@ -201,6 +201,7 @@ public void refreshQuestsLines(boolean updateBoard) { } private void updateBoard(boolean update, boolean time) { + if (board == null && !time) return; List linesStrings = new ArrayList<>(lines.size()); for (int i = 0; i < lines.size(); i++) { Line line = lines.get(i); @@ -218,7 +219,7 @@ private void updateBoard(boolean update, boolean time) { linesStrings.add("§c§lline error"); } } - if (update) board.updateLines(linesStrings); + if (update && board != null) board.updateLines(linesStrings); } public void setCustomLine(int id, String value){ diff --git a/core/src/main/java/fr/skytasul/quests/utils/Database.java b/core/src/main/java/fr/skytasul/quests/utils/Database.java index a6fcef4f..29eb781b 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Database.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Database.java @@ -14,7 +14,7 @@ import fr.skytasul.quests.BeautyQuests; -public class Database { +public class Database implements Closeable { private ConfigurationSection config; private String databaseName; @@ -77,7 +77,8 @@ public ConfigurationSection getConfig() { return config; } - public void closeConnection() { + @Override + public void close() { if (source instanceof Closeable) { try { ((Closeable) source).close(); diff --git a/core/src/main/java/fr/skytasul/quests/utils/Lang.java b/core/src/main/java/fr/skytasul/quests/utils/Lang.java index b1ba4a26..c5b91310 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Lang.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Lang.java @@ -110,7 +110,7 @@ public enum Lang implements Locale { PLAYER_NOT_ONLINE("msg.playerNotOnline"), PLAYER_DATA_NOT_FOUND("msg.playerDataNotFound"), // 0: player name - VERSION_REQUIRED("msg.versionRequired"), // 0: version + VERSION_REQUIRED("msg.versionRequired", ErrorPrefix), // 0: version RESTART_SERVER("msg.restartServer"), diff --git a/core/src/main/resources/locales/en_US.yml b/core/src/main/resources/locales/en_US.yml index 7bff8922..dddcd0f6 100644 --- a/core/src/main/resources/locales/en_US.yml +++ b/core/src/main/resources/locales/en_US.yml @@ -109,7 +109,7 @@ msg: downloadTranslations: syntax: '§cYou must specify a language to download. Example: "/quests downloadTranslations en_US".' notFound: '§cLanguage {0} not found for version {1}.' - exists: '§cThe file {0} already exists. Append "true" to your command to overwrite it. (/quests downloadTranslations true)' + exists: '§cThe file {0} already exists. Append "-overwrite" to your command to overwrite it. (/quests downloadTranslations -overwrite)' downloaded: '§aLanguage {0} has been downloaded! §7You must now edit the file "/plugins/BeautyQuests/config.yml" to change the value of §ominecraftTranslationsFile§7 with {0}, then restart the server.' checkpoint: diff --git a/core/src/main/resources/plugin.yml b/core/src/main/resources/plugin.yml index c7cdacec..ef5f40bd 100644 --- a/core/src/main/resources/plugin.yml +++ b/core/src/main/resources/plugin.yml @@ -36,11 +36,11 @@ softdepend: - Sentinel - AdvancedSpawners -commands: - beautyquests: - description: Main command for quests - permission: beautyquests.command - aliases: [bquests, quests, beautyquests, bq, quest] +#commands: +# beautyquests: +# description: Main command for quests +# permission: beautyquests.command +# aliases: [bquests, quests, beautyquests, bq, quest] permissions: beautyquests.*: From 662e36166dd1ba48d8eeb9b18b43fb38d58ae3a1 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sat, 27 Aug 2022 14:33:57 +0200 Subject: [PATCH 054/111] :sparkles: Added new "quest visibility" option * this option maps 4 visibilities (the 3 menu tabs + dynmap/bluemap) to a yes/no state * removed the old "hide" option in favor of this new option * migration will happen automatically --- .../quests/gui/creation/FinishGUI.java | 15 ++- .../skytasul/quests/options/OptionHide.java | 18 --- .../quests/options/OptionVisibility.java | 127 ++++++++++++++++++ .../fr/skytasul/quests/structure/Quest.java | 5 +- .../quests/structure/QuestsManager.java | 7 +- .../java/fr/skytasul/quests/utils/Lang.java | 13 +- .../fr/skytasul/quests/utils/XMaterial.java | 14 ++ .../maps/AbstractMapIntegration.java | 5 +- core/src/main/resources/locales/en_US.yml | 11 +- 9 files changed, 177 insertions(+), 38 deletions(-) delete mode 100644 core/src/main/java/fr/skytasul/quests/options/OptionHide.java create mode 100644 core/src/main/java/fr/skytasul/quests/options/OptionVisibility.java diff --git a/core/src/main/java/fr/skytasul/quests/gui/creation/FinishGUI.java b/core/src/main/java/fr/skytasul/quests/gui/creation/FinishGUI.java index f3b6f11f..a774e3ca 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/creation/FinishGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/creation/FinishGUI.java @@ -1,6 +1,7 @@ package fr.skytasul.quests.gui.creation; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; @@ -119,7 +120,7 @@ public void update() { XMaterial type = enabled ? XMaterial.GOLD_INGOT : XMaterial.NETHER_BRICK; String itemName = (enabled ? ChatColor.GOLD : ChatColor.DARK_PURPLE).toString() + (session.isEdition() ? Lang.edit : Lang.create).toString(); String itemLore = QuestOption.formatDescription(Lang.createLore.toString()) + (enabled ? " §a✔" : " §c✖"); - String[] lore = Boolean.TRUE.equals(keepPlayerDatas) ? new String[] { itemLore } : new String[] { itemLore, "", Lang.resetLore.toString() }; + String[] lore = keepPlayerDatas == null || keepPlayerDatas.booleanValue() ? new String[] { itemLore } : new String[] { itemLore, "", Lang.resetLore.toString() }; ItemStack item = inv.getItem(slot); @@ -319,18 +320,18 @@ public static void initialize(){ QuestsAPI.registerQuestOption(new QuestOptionCreator<>("confirmMessage", 15, OptionConfirmMessage.class, OptionConfirmMessage::new, null)); QuestsAPI.registerQuestOption(new QuestOptionCreator<>("hologramText", 17, OptionHologramText.class, OptionHologramText::new, Lang.HologramText.toString())); QuestsAPI.registerQuestOption(new QuestOptionCreator<>("bypassLimit", 18, OptionBypassLimit.class, OptionBypassLimit::new, false)); - QuestsAPI.registerQuestOption(new QuestOptionCreator<>("hideNoRequirements", 19, OptionHideNoRequirements.class, OptionHideNoRequirements::new, false)); - QuestsAPI.registerQuestOption(new QuestOptionCreator<>("startableFromGUI", 20, OptionStartable.class, OptionStartable::new, false)); - QuestsAPI.registerQuestOption(new QuestOptionCreator<>("failOnDeath", 21, OptionFailOnDeath.class, OptionFailOnDeath::new, false)); - QuestsAPI.registerQuestOption(new QuestOptionCreator<>("cancellable", 22, OptionCancellable.class, OptionCancellable::new, true)); - QuestsAPI.registerQuestOption(new QuestOptionCreator<>("cancelActions", 23, OptionCancelRewards.class, OptionCancelRewards::new, new ArrayList<>())); + QuestsAPI.registerQuestOption(new QuestOptionCreator<>("startableFromGUI", 19, OptionStartable.class, OptionStartable::new, false)); + QuestsAPI.registerQuestOption(new QuestOptionCreator<>("failOnDeath", 20, OptionFailOnDeath.class, OptionFailOnDeath::new, false)); + QuestsAPI.registerQuestOption(new QuestOptionCreator<>("cancellable", 21, OptionCancellable.class, OptionCancellable::new, true)); + QuestsAPI.registerQuestOption(new QuestOptionCreator<>("cancelActions", 22, OptionCancelRewards.class, OptionCancelRewards::new, new ArrayList<>())); QuestsAPI.registerQuestOption(new QuestOptionCreator<>("hologramLaunch", 25, OptionHologramLaunch.class, OptionHologramLaunch::new, QuestsConfiguration.getHoloLaunchItem())); QuestsAPI.registerQuestOption(new QuestOptionCreator<>("hologramLaunchNo", 26, OptionHologramLaunchNo.class, OptionHologramLaunchNo::new, QuestsConfiguration.getHoloLaunchNoItem())); QuestsAPI.registerQuestOption(new QuestOptionCreator<>("scoreboard", 27, OptionScoreboardEnabled.class, OptionScoreboardEnabled::new, true)); - QuestsAPI.registerQuestOption(new QuestOptionCreator<>("hide", 28, OptionHide.class, OptionHide::new, false, "hid")); + QuestsAPI.registerQuestOption(new QuestOptionCreator<>("hideNoRequirements", 28, OptionHideNoRequirements.class, OptionHideNoRequirements::new, false)); QuestsAPI.registerQuestOption(new QuestOptionCreator<>("auto", 29, OptionAutoQuest.class, OptionAutoQuest::new, false)); QuestsAPI.registerQuestOption(new QuestOptionCreator<>("repeatable", 30, OptionRepeatable.class, OptionRepeatable::new, false, "multiple")); QuestsAPI.registerQuestOption(new QuestOptionCreator<>("timer", 31, OptionTimer.class, OptionTimer::new, QuestsConfiguration.getTimeBetween())); + QuestsAPI.registerQuestOption(new QuestOptionCreator<>("visibility", 32, OptionVisibility.class, OptionVisibility::new, Arrays.asList(OptionVisibility.VisibilityLocation.values()), "hid", "hide")); QuestsAPI.registerQuestOption(new QuestOptionCreator<>("endSound", 34, OptionEndSound.class, OptionEndSound::new, QuestsConfiguration.getFinishSound())); QuestsAPI.registerQuestOption(new QuestOptionCreator<>("firework", 35, OptionFirework.class, OptionFirework::new, QuestsConfiguration.getDefaultFirework())); QuestsAPI.registerQuestOption(new QuestOptionCreator<>("requirements", 36, OptionRequirements.class, OptionRequirements::new, new ArrayList<>())); diff --git a/core/src/main/java/fr/skytasul/quests/options/OptionHide.java b/core/src/main/java/fr/skytasul/quests/options/OptionHide.java deleted file mode 100644 index 07ef87fb..00000000 --- a/core/src/main/java/fr/skytasul/quests/options/OptionHide.java +++ /dev/null @@ -1,18 +0,0 @@ -package fr.skytasul.quests.options; - -import fr.skytasul.quests.api.options.QuestOptionBoolean; -import fr.skytasul.quests.utils.Lang; - -public class OptionHide extends QuestOptionBoolean { - - @Override - public String getName() { - return Lang.hide.toString(); - } - - @Override - public String getDescription() { - return Lang.hideLore.toString(); - } - -} diff --git a/core/src/main/java/fr/skytasul/quests/options/OptionVisibility.java b/core/src/main/java/fr/skytasul/quests/options/OptionVisibility.java new file mode 100644 index 00000000..4cb5185d --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/options/OptionVisibility.java @@ -0,0 +1,127 @@ +package fr.skytasul.quests.options; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumMap; +import java.util.List; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import fr.skytasul.quests.api.options.OptionSet; +import fr.skytasul.quests.api.options.QuestOption; +import fr.skytasul.quests.gui.CustomInventory; +import fr.skytasul.quests.gui.ItemUtils; +import fr.skytasul.quests.gui.creation.FinishGUI; +import fr.skytasul.quests.options.OptionVisibility.VisibilityLocation; +import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.Utils; +import fr.skytasul.quests.utils.XMaterial; + +public class OptionVisibility extends QuestOption> { + + @Override + public Object save() { + return getValue().stream().map(VisibilityLocation::name).collect(Collectors.toList()); + } + + @Override + public void load(ConfigurationSection config, String key) { + if (config.isBoolean(key)) { + setValue(Collections.emptyList()); // migration from before 0.20, where it was the "hide" option + }else { + setValue(config.getStringList(key).stream().map(VisibilityLocation::valueOf).collect(Collectors.toList())); + } + } + + @Override + public List cloneValue(List value) { + return new ArrayList<>(value); + } + + private String[] getLore() { + return new String[] { formatDescription(Lang.optionVisibilityLore.toString()), "", formatValue(getValue().stream().map(VisibilityLocation::getName).collect(Collectors.joining(", "))) }; + } + + @Override + public ItemStack getItemStack(OptionSet options) { + return ItemUtils.item(XMaterial.SPYGLASS.or(XMaterial.BOOKSHELF), Lang.optionVisibility.toString(), getLore()); + } + + @Override + public void click(FinishGUI gui, Player p, ItemStack item, int slot, ClickType click) { + new VisibilityGUI(() -> { + ItemUtils.lore(item, getLore()); + gui.reopen(p); + }).create(p); + } + + class VisibilityGUI implements CustomInventory { + + private EnumMap locations = new EnumMap<>(VisibilityLocation.class); + private Runnable reopen; + + public VisibilityGUI(Runnable reopen) { + this.reopen = reopen; + } + + @Override + public Inventory open(Player p) { + Inventory inv = Bukkit.createInventory(null, InventoryType.HOPPER, Lang.INVENTORY_VISIBILITY.toString()); + + for (int i = 0; i < 4; i++) { + VisibilityLocation loc = VisibilityLocation.values()[i]; + boolean visible = getValue().contains(loc); + locations.put(loc, visible); + inv.setItem(i, ItemUtils.itemSwitch(loc.getName(), visible)); + } + inv.setItem(4, ItemUtils.itemDone); + + return p.openInventory(inv).getTopInventory(); + } + + @Override + public boolean onClick(Player p, Inventory inv, ItemStack current, int slot, ClickType click) { + if (slot > 0 && slot < 4) { + locations.put(VisibilityLocation.values()[slot], ItemUtils.toggle(current)); + }else if (slot == 4) { + setValue(locations.entrySet().stream().filter(Entry::getValue).map(Entry::getKey).collect(Collectors.toList())); + reopen.run(); + } + return true; + } + + @Override + public CloseBehavior onClose(Player p, Inventory inv) { + Utils.runSync(reopen); + return CloseBehavior.NOTHING; + } + + } + + public enum VisibilityLocation { + TAB_NOT_STARTED(Lang.visibility_notStarted.toString()), + TAB_IN_PROGRESS(Lang.visibility_inProgress.toString()), + TAB_FINISHED(Lang.visibility_finished.toString()), + MAPS(Lang.visibility_maps.toString()); + + private final String name; + + private VisibilityLocation(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/structure/Quest.java b/core/src/main/java/fr/skytasul/quests/structure/Quest.java index a5991301..46ffa84c 100644 --- a/core/src/main/java/fr/skytasul/quests/structure/Quest.java +++ b/core/src/main/java/fr/skytasul/quests/structure/Quest.java @@ -34,6 +34,7 @@ import fr.skytasul.quests.gui.misc.ConfirmGUI; import fr.skytasul.quests.gui.quests.PlayerListGUI.Category; import fr.skytasul.quests.options.*; +import fr.skytasul.quests.options.OptionVisibility.VisibilityLocation; import fr.skytasul.quests.players.AdminMode; import fr.skytasul.quests.players.PlayerAccount; import fr.skytasul.quests.players.PlayerQuestDatas; @@ -163,8 +164,8 @@ public boolean isRepeatable() { return getOptionValueOrDef(OptionRepeatable.class); } - public boolean isHidden() { - return getOptionValueOrDef(OptionHide.class); + public boolean isHidden(VisibilityLocation location) { + return !getOptionValueOrDef(OptionVisibility.class).contains(location); } public boolean isHiddenWhenRequirementsNotMet() { diff --git a/core/src/main/java/fr/skytasul/quests/structure/QuestsManager.java b/core/src/main/java/fr/skytasul/quests/structure/QuestsManager.java index 687ed3a9..e3a6f438 100644 --- a/core/src/main/java/fr/skytasul/quests/structure/QuestsManager.java +++ b/core/src/main/java/fr/skytasul/quests/structure/QuestsManager.java @@ -17,6 +17,7 @@ import fr.skytasul.quests.api.npcs.BQNPC; import fr.skytasul.quests.options.OptionStartable; import fr.skytasul.quests.options.OptionStarterNPC; +import fr.skytasul.quests.options.OptionVisibility.VisibilityLocation; import fr.skytasul.quests.players.PlayerAccount; import fr.skytasul.quests.players.PlayerQuestDatas; import fr.skytasul.quests.utils.Utils; @@ -132,7 +133,7 @@ public List getQuestsStarted(PlayerAccount acc, boolean hide, boolean wit .map(PlayerQuestDatas::getQuest) .filter(Objects::nonNull) .filter(quest -> !quest.isRemoved()) - .filter(quest -> !hide || !quest.isHidden()) + .filter(quest -> !hide || !quest.isHidden(VisibilityLocation.TAB_IN_PROGRESS)) .filter(quest -> !withoutScoreboard || quest.isScoreboardEnabled()) .collect(Collectors.toList()); } @@ -159,7 +160,7 @@ public int getStartedSize(PlayerAccount acc) { public List getQuestsFinished(PlayerAccount acc, boolean hide) { return quests .stream() - .filter(quest -> !(hide && quest.isHidden()) && quest.hasFinished(acc)) + .filter(quest -> !(hide && quest.isHidden(VisibilityLocation.TAB_FINISHED)) && quest.hasFinished(acc)) .collect(Collectors.toList()); } @@ -167,7 +168,7 @@ public List getQuestsNotStarted(PlayerAccount acc, boolean hide, boolean return quests .stream() .filter(quest -> { - if (hide && quest.isHidden()) return false; + if (hide && quest.isHidden(VisibilityLocation.TAB_NOT_STARTED)) return false; if (quest.hasStarted(acc)) return false; if (!quest.hasFinished(acc)) return true; return clickableAndRedoable && quest.isRepeatable() && quest.getOptionValueOrDef(OptionStartable.class) && quest.testTimer(acc, false); diff --git a/core/src/main/java/fr/skytasul/quests/utils/Lang.java b/core/src/main/java/fr/skytasul/quests/utils/Lang.java index b1ba4a26..05e43d58 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Lang.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Lang.java @@ -421,8 +421,6 @@ public enum Lang implements Locale { startableFromGUILore("inv.details.startableFromGUILore"), scoreboard("inv.details.scoreboardItem"), scoreboardLore("inv.details.scoreboardItemLore"), - hide("inv.details.hideItem"), - hideLore("inv.details.hideItemLore"), hideNoRequirements("inv.details.hideNoRequirementsItem"), hideNoRequirementsLore("inv.details.hideNoRequirementsItemLore"), bypass("inv.details.bypassLimit"), @@ -487,6 +485,8 @@ public enum Lang implements Locale { optionFirework("inv.details.firework"), optionFireworkLore("inv.details.fireworkLore"), optionFireworkDrop("inv.details.fireworkLoreDrop"), + optionVisibility("inv.details.visibility"), + optionVisibilityLore("inv.details.visibilityLore"), keepDatas("inv.details.keepDatas"), keepDatasLore("inv.details.keepDatasLore"), resetLore("inv.details.loreReset"), @@ -622,8 +622,7 @@ public enum Lang implements Locale { poolItemAvoidDuplicates("inv.poolsManage.poolAvoidDuplicates"), poolItemQuestsList("inv.poolsManage.poolQuestsList"), // 0: size, 1: quests poolEdit("inv.poolsManage.edit"), - poolChoose( - "inv.poolsManage.choose"), + poolChoose("inv.poolsManage.choose"), poolCreate("inv.poolsManage.create"), INVENTORY_POOL_CREATE("inv.poolCreation.name"), @@ -673,6 +672,12 @@ public enum Lang implements Locale { INVENTORY_DAMAGE_CAUSES_LIST("inv.damageCausesList.name"), + INVENTORY_VISIBILITY("inv.visibility.name"), + visibility_notStarted("inv.visibility.notStarted"), + visibility_inProgress("inv.visibility.inProgress"), + visibility_finished("inv.visibility.finished"), + visibility_maps("inv.visibility.maps"), + BOOK_NAME("inv.listBook.questName"), BOOK_STARTER("inv.listBook.questStarter"), BOOK_REWARDS("inv.listBook.questRewards"), diff --git a/core/src/main/java/fr/skytasul/quests/utils/XMaterial.java b/core/src/main/java/fr/skytasul/quests/utils/XMaterial.java index 2dc3c868..6bde6370 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/XMaterial.java +++ b/core/src/main/java/fr/skytasul/quests/utils/XMaterial.java @@ -2152,6 +2152,20 @@ public boolean isSupported() { return this.material != null; } + /** + * Checks if this material is supported in the current version and + * returns itself if yes. + *

+ * In the other case, the alternate material will get returned, + * no matter if it is supported or not. + * + * @param alternateMaterial the material to get if this one is not supported. + * @return this material or the {@code alternateMaterial} if not supported. + */ + public XMaterial or(XMaterial alternateMaterial) { + return isSupported() ? this : alternateMaterial; + } + /** * This method is needed due to Java enum initialization limitations. * It's really inefficient yes, but it's only used for initialization. diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/maps/AbstractMapIntegration.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/maps/AbstractMapIntegration.java index 219789ec..d2fc2d7e 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/compatibility/maps/AbstractMapIntegration.java +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/maps/AbstractMapIntegration.java @@ -6,6 +6,7 @@ import fr.skytasul.quests.api.QuestsAPI; import fr.skytasul.quests.api.QuestsHandler; import fr.skytasul.quests.options.OptionStarterNPC; +import fr.skytasul.quests.options.OptionVisibility.VisibilityLocation; import fr.skytasul.quests.structure.Quest; import fr.skytasul.quests.utils.DebugUtils; @@ -23,7 +24,7 @@ private void initializeQuests() { @Override public void questLoaded(Quest quest) { if (!quest.hasOption(OptionStarterNPC.class)) return; - if (quest.isHidden()) { + if (quest.isHidden(VisibilityLocation.MAPS)) { DebugUtils.logMessage("No marker created for quest " + quest.getID() + ": quest is hidden"); return; } @@ -38,7 +39,7 @@ public void questLoaded(Quest quest) { @Override public void questUnload(Quest quest) { - if (!quest.isHidden() && quest.hasOption(OptionStarterNPC.class)) removeMarker(quest); + if (!quest.isHidden(VisibilityLocation.MAPS) && quest.hasOption(OptionStarterNPC.class)) removeMarker(quest); } protected abstract void initializeMarkers(Runnable initializeQuests); diff --git a/core/src/main/resources/locales/en_US.yml b/core/src/main/resources/locales/en_US.yml index 7bff8922..c253c64c 100644 --- a/core/src/main/resources/locales/en_US.yml +++ b/core/src/main/resources/locales/en_US.yml @@ -443,8 +443,6 @@ inv: startableFromGUILore: Allows the player to start the quest from the Quests Menu. scoreboardItem: Enable scoreboard scoreboardItemLore: If disabled, the quest will not be tracked through the scoreboard. - hideItem: Hide menu and dynmap - hideItemLore: If enabled, the quest will not be displayed on the dynmap nor in the Quests Menu. hideNoRequirementsItem: Hide when requirements not met hideNoRequirementsItemLore: If enabled, the quest will not be displayed in the Quests Menu when the requirements are not met. bypassLimit: Don't count quest limit @@ -501,6 +499,9 @@ inv: firework: §dEnding Firework fireworkLore: Firework launched when the player finishes the quest fireworkLoreDrop: Drop your custom firework here + visibility: §bQuest visibility + visibilityLore: Choose in which tabs of the menu will the quest be shown, and if + the quest is visible on dynamic maps. keepDatas: Preserve players datas keepDatasLore: |- Force the plugin to preserve players datas, even though stages have been edited. @@ -709,6 +710,12 @@ inv: name: Damage cause damageCausesList: name: Damage causes list + visibility: + name: Quest visibility + notStarted: '"Not started" menu tab' + inProgress: '"In progress" menu tab' + finished: '"Finished" menu tab' + maps: 'Maps (such as dynmap or BlueMap)' scoreboard: name: §6§lQuests From b58e64bed26859c0edd7e14f6846aeeb09d6c82e Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sat, 27 Aug 2022 16:17:55 +0200 Subject: [PATCH 055/111] :sparkles: Added player selectors for some of the commands --- core/pom.xml | 8 +- .../quests/commands/CommandsAdmin.java | 4 +- .../commands/CommandsPlayerManagement.java | 136 ++++++++++-------- 3 files changed, 81 insertions(+), 67 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index 309ef728..63cc7dd8 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -268,14 +268,14 @@ provided - io.github.Revxrsal + com.github.Revxrsal.Lamp bukkit - 3.0.8 + d72483065c - io.github.Revxrsal + com.github.Revxrsal.Lamp common - 3.0.8 + d72483065c diff --git a/core/src/main/java/fr/skytasul/quests/commands/CommandsAdmin.java b/core/src/main/java/fr/skytasul/quests/commands/CommandsAdmin.java index 79aa0efb..40ab553c 100644 --- a/core/src/main/java/fr/skytasul/quests/commands/CommandsAdmin.java +++ b/core/src/main/java/fr/skytasul/quests/commands/CommandsAdmin.java @@ -39,7 +39,9 @@ import fr.skytasul.quests.utils.Utils; import fr.skytasul.quests.utils.nms.NMS; +import revxrsal.commands.annotation.Flag; import revxrsal.commands.annotation.Optional; +import revxrsal.commands.annotation.Range; import revxrsal.commands.annotation.SecretCommand; import revxrsal.commands.annotation.Subcommand; import revxrsal.commands.annotation.Switch; @@ -53,7 +55,7 @@ public class CommandsAdmin implements OrphanCommand { @Subcommand ("create") @CommandPermission (value = "beautyquests.command.create") @OutsideEditor - public void create(Player player, @Optional Integer id) { + public void create(Player player, @Optional @Flag @Range (min = 0) Integer id) { QuestCreationSession session = new QuestCreationSession(); if (id != null) { if (QuestsAPI.getQuests().getQuest(id) != null) diff --git a/core/src/main/java/fr/skytasul/quests/commands/CommandsPlayerManagement.java b/core/src/main/java/fr/skytasul/quests/commands/CommandsPlayerManagement.java index 5152fcda..46a182b6 100644 --- a/core/src/main/java/fr/skytasul/quests/commands/CommandsPlayerManagement.java +++ b/core/src/main/java/fr/skytasul/quests/commands/CommandsPlayerManagement.java @@ -7,6 +7,7 @@ import org.bukkit.entity.Player; import org.bukkit.permissions.Permission; +import fr.skytasul.quests.BeautyQuests; import fr.skytasul.quests.api.QuestsAPI; import fr.skytasul.quests.api.events.accounts.PlayerAccountResetEvent; import fr.skytasul.quests.api.stages.AbstractStage; @@ -31,6 +32,7 @@ import revxrsal.commands.annotation.Switch; import revxrsal.commands.bukkit.BukkitCommandActor; import revxrsal.commands.bukkit.BukkitCommandPermission; +import revxrsal.commands.bukkit.EntitySelector; import revxrsal.commands.bukkit.annotation.CommandPermission; import revxrsal.commands.command.ExecutableCommand; import revxrsal.commands.exception.CommandErrorException; @@ -44,31 +46,37 @@ public class CommandsPlayerManagement implements OrphanCommand { @Subcommand ("finishAll") @CommandPermission ("beautyquests.command.finish") - public void finishAll(BukkitCommandActor actor, Player player) { - PlayerAccount acc = PlayersManager.getPlayerAccount(player); - int success = 0; - int errors = 0; - for (Quest q : QuestsAPI.getQuests().getQuestsStarted(acc)) { - try { - q.finish(player); - success++; - }catch (Throwable ex) { - ex.printStackTrace(); - errors++; + public void finishAll(BukkitCommandActor actor, EntitySelector players) { + for (Player player : players) { + PlayerAccount acc = PlayersManager.getPlayerAccount(player); + int success = 0; + int errors = 0; + for (Quest q : QuestsAPI.getQuests().getQuestsStarted(acc)) { + try { + q.finish(player); + success++; + }catch (Exception ex) { + BeautyQuests.logger.severe("An error occurred while finishing quest " + q.getID(), ex); + errors++; + } } + Lang.LEAVE_ALL_RESULT.send(actor.getSender(), success, errors); } - Lang.LEAVE_ALL_RESULT.send(actor.getSender(), success, errors); } @Subcommand ("finish") @CommandPermission ("beautyquests.command.finish") - public void finish(BukkitCommandActor actor, Player player, Quest quest) { - try { - quest.finish(player); - Lang.LEAVE_ALL_RESULT.send(actor.getSender(), 1, 0); - }catch (Throwable ex) { - ex.printStackTrace(); - Lang.LEAVE_ALL_RESULT.send(actor.getSender(), 1, 1); + public void finish(BukkitCommandActor actor, EntitySelector players, Quest quest, @Switch boolean force) { + for (Player player : players) { + try { + if (force || quest.hasStarted(PlayersManager.getPlayerAccount(player))) { + quest.finish(player); + Lang.LEAVE_ALL_RESULT.send(actor.getSender(), 1, 0); + } + }catch (Exception ex) { + BeautyQuests.logger.severe("An error occurred while finishing quest " + quest.getID(), ex); + Lang.LEAVE_ALL_RESULT.send(actor.getSender(), 1, 1); + } } } @@ -164,26 +172,28 @@ public void startDialog(BukkitCommandActor actor, Player player, Quest quest) { @Subcommand ("resetPlayer") @CommandPermission ("beautyquests.command.resetPlayer") - public void resetPlayer(BukkitCommandActor actor, Player player) { - PlayerAccount acc = PlayersManager.getPlayerAccount(player); - int quests = 0, pools = 0; - for (PlayerQuestDatas questDatas : new ArrayList<>(acc.getQuestsDatas())) { - Quest quest = questDatas.getQuest(); - if (quest != null) { - quest.resetPlayer(acc); - }else acc.removeQuestDatas(questDatas.getQuestID()); - quests++; - } - for (PlayerPoolDatas poolDatas : new ArrayList<>(acc.getPoolDatas())) { - QuestPool pool = poolDatas.getPool(); - if (pool != null) { - pool.resetPlayer(acc); - }else acc.removePoolDatas(poolDatas.getPoolID()); - pools++; + public void resetPlayer(BukkitCommandActor actor, EntitySelector players) { + for (Player player : players) { + PlayerAccount acc = PlayersManager.getPlayerAccount(player); + int quests = 0, pools = 0; + for (PlayerQuestDatas questDatas : new ArrayList<>(acc.getQuestsDatas())) { + Quest quest = questDatas.getQuest(); + if (quest != null) { + quest.resetPlayer(acc); + }else acc.removeQuestDatas(questDatas.getQuestID()); + quests++; + } + for (PlayerPoolDatas poolDatas : new ArrayList<>(acc.getPoolDatas())) { + QuestPool pool = poolDatas.getPool(); + if (pool != null) { + pool.resetPlayer(acc); + }else acc.removePoolDatas(poolDatas.getPoolID()); + pools++; + } + Bukkit.getPluginManager().callEvent(new PlayerAccountResetEvent(player, acc)); + if (acc.isCurrent()) Lang.DATA_REMOVED.send(player, quests, actor.getName(), pools); + Lang.DATA_REMOVED_INFO.send(actor.getSender(), quests, player.getName(), pools); } - Bukkit.getPluginManager().callEvent(new PlayerAccountResetEvent(player, acc)); - if (acc.isCurrent()) Lang.DATA_REMOVED.send(player, quests, actor.getName(), pools); - Lang.DATA_REMOVED_INFO.send(actor.getSender(), quests, player.getName(), pools); } @Subcommand ("resetPlayerQuest") @@ -237,21 +247,22 @@ public void seePlayer(Player actor, Player player) { @Subcommand ("start") @CommandPermission ("beautyquests.command.start") - public void start(BukkitCommandActor actor, ExecutableCommand command, Player player, @Optional Quest quest, @CommandPermission ("beautyquests.command.start.other") @Switch boolean overrideRequirements) { - if (actor.isPlayer() && (actor.getAsPlayer() != player)) { - // the executor wants to start a quest for somebody else - if (!startOtherPermission.canExecute(actor)) + public void start(BukkitCommandActor actor, ExecutableCommand command, EntitySelector players, @Optional Quest quest, @CommandPermission ("beautyquests.command.start.other") @Switch boolean overrideRequirements) { + if (actor.isPlayer() && !startOtherPermission.canExecute(actor)) { + if (players.isEmpty() || players.size() > 1 || (players.get(0) != actor.getAsPlayer())) throw new NoPermissionException(command, startOtherPermission); } - PlayerAccount acc = PlayersManager.getPlayerAccount(player); - - if (quest == null) { - new QuestsListGUI(obj -> { - start(actor.getSender(), player, acc, obj, overrideRequirements); - }, acc, false, true, false).create(actor.requirePlayer()); - }else { - start(actor.getSender(), player, acc, quest, overrideRequirements); + for (Player player : players) { + PlayerAccount acc = PlayersManager.getPlayerAccount(player); + + if (quest == null) { + new QuestsListGUI(obj -> { + start(actor.getSender(), player, acc, obj, overrideRequirements); + }, acc, false, true, false).create(actor.requirePlayer()); + }else { + start(actor.getSender(), player, acc, quest, overrideRequirements); + } } } @@ -266,21 +277,22 @@ private void start(CommandSender sender, Player player, PlayerAccount acc, Quest @Subcommand ("cancel") @CommandPermission ("beautyquests.command.cancel") - public void cancel(BukkitCommandActor actor, ExecutableCommand command, Player player, @Optional Quest quest) { - if (actor.isPlayer() && (actor.getAsPlayer() != player)) { - // the executor wants to start a quest for somebody else - if (!cancelOtherPermission.canExecute(actor)) + public void cancel(BukkitCommandActor actor, ExecutableCommand command, EntitySelector players, @Optional Quest quest) { + if (actor.isPlayer() && !cancelOtherPermission.canExecute(actor)) { + if (players.isEmpty() || players.size() > 1 || (players.get(0) != actor.getAsPlayer())) throw new NoPermissionException(command, cancelOtherPermission); } - PlayerAccount acc = PlayersManager.getPlayerAccount(player); - - if (quest == null) { - new QuestsListGUI(obj -> { - cancel(actor.getSender(), player, acc, obj); - }, acc, true, false, false).create(actor.requirePlayer()); - }else { - cancel(actor.getSender(), player, acc, quest); + for (Player player : players) { + PlayerAccount acc = PlayersManager.getPlayerAccount(player); + + if (quest == null) { + new QuestsListGUI(obj -> { + cancel(actor.getSender(), player, acc, obj); + }, acc, true, false, false).create(actor.requirePlayer()); + }else { + cancel(actor.getSender(), player, acc, quest); + } } } From 8374997624bad7bf3f3fb785695d86db6f33bba3 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sat, 27 Aug 2022 21:04:07 +0200 Subject: [PATCH 056/111] :globe_with_meridians: :bug: Fixed a bug + updated FR locale --- .../skytasul/quests/commands/CommandsAdmin.java | 4 ++-- .../commands/CommandsPlayerManagement.java | 6 +++--- core/src/main/resources/locales/en_US.yml | 2 +- core/src/main/resources/locales/fr_FR.yml | 16 +++++++++++----- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/commands/CommandsAdmin.java b/core/src/main/java/fr/skytasul/quests/commands/CommandsAdmin.java index 40ab553c..b0df6f61 100644 --- a/core/src/main/java/fr/skytasul/quests/commands/CommandsAdmin.java +++ b/core/src/main/java/fr/skytasul/quests/commands/CommandsAdmin.java @@ -41,7 +41,6 @@ import revxrsal.commands.annotation.Flag; import revxrsal.commands.annotation.Optional; -import revxrsal.commands.annotation.Range; import revxrsal.commands.annotation.SecretCommand; import revxrsal.commands.annotation.Subcommand; import revxrsal.commands.annotation.Switch; @@ -55,9 +54,10 @@ public class CommandsAdmin implements OrphanCommand { @Subcommand ("create") @CommandPermission (value = "beautyquests.command.create") @OutsideEditor - public void create(Player player, @Optional @Flag @Range (min = 0) Integer id) { + public void create(Player player, @Optional @Flag Integer id) { QuestCreationSession session = new QuestCreationSession(); if (id != null) { + if (id.intValue() < 0) throw new CommandErrorException(Lang.NUMBER_NEGATIVE.toString()); if (QuestsAPI.getQuests().getQuest(id) != null) throw new CommandErrorException("Invalid quest ID: another quest exists with ID {0}", id); diff --git a/core/src/main/java/fr/skytasul/quests/commands/CommandsPlayerManagement.java b/core/src/main/java/fr/skytasul/quests/commands/CommandsPlayerManagement.java index 46a182b6..2d94fd0b 100644 --- a/core/src/main/java/fr/skytasul/quests/commands/CommandsPlayerManagement.java +++ b/core/src/main/java/fr/skytasul/quests/commands/CommandsPlayerManagement.java @@ -288,15 +288,15 @@ public void cancel(BukkitCommandActor actor, ExecutableCommand command, EntitySe if (quest == null) { new QuestsListGUI(obj -> { - cancel(actor.getSender(), player, acc, obj); + cancel(actor.getSender(), acc, obj); }, acc, true, false, false).create(actor.requirePlayer()); }else { - cancel(actor.getSender(), player, acc, quest); + cancel(actor.getSender(), acc, quest); } } } - private void cancel(CommandSender sender, Player player, PlayerAccount acc, Quest quest) { + private void cancel(CommandSender sender, PlayerAccount acc, Quest quest) { if (!sender.hasPermission(cancelOtherPermission.getPermission()) && !quest.isCancellable()) { Lang.CANCEL_QUEST_UNAVAILABLE.send(sender, quest.getName()); return; diff --git a/core/src/main/resources/locales/en_US.yml b/core/src/main/resources/locales/en_US.yml index c5ef1362..a6fda10f 100644 --- a/core/src/main/resources/locales/en_US.yml +++ b/core/src/main/resources/locales/en_US.yml @@ -148,7 +148,7 @@ msg: resetQuest: §6Removed datas of the quest for {0} players. startQuest: '§6You have forced starting of quest {0} (Player UUID: {1}).' startQuestNoRequirements: '§cThe player does not meet the requirements for the quest {0}... - Append "true" at the end of your command to bypass the requirements check.' + Append "-overrideRequirements" at the end of your command to bypass the requirements check.' cancelQuest: §6You have cancelled the quest {0}. cancelQuestUnavailable: §cThe quest {0} can't be cancelled. backupCreated: §6You have successfully created backups of all quests and player diff --git a/core/src/main/resources/locales/fr_FR.yml b/core/src/main/resources/locales/fr_FR.yml index 3df4b75a..75b88015 100644 --- a/core/src/main/resources/locales/fr_FR.yml +++ b/core/src/main/resources/locales/fr_FR.yml @@ -94,7 +94,7 @@ msg: downloadTranslations: syntax: '§cVous devez spécifier une langue à télécharger. Exemple: "/quests downloadTranslations fr_FR".' notFound: '§cLangue {0} introuvable pour la version {1}.' - exists: '§cLe fichier {0} existe déjà. Ajoutez "true" à votre commande pour l''écraser. (/quests downloadTranslations true)' + exists: '§cLe fichier {0} existe déjà. Ajoutez "-overwrite" à votre commande pour l''écraser. (/quests downloadTranslations -overwrite)' downloaded: '§aLa langue {0} a été téléchargée ! §7Vous devez maintenant modifier le fichier "/plugins/BeautyQuests/config.yml" pour changer la valeur de §ominecraftTranslationsFile§7 avec {0}, puis redémarrer le serveur.' checkpoint: noCheckpoint: '§cAucun checkpoint trouvé pour la quête {0}.' @@ -129,7 +129,7 @@ msg: remover: '§6Les données de la quête {1} du joueur {0} ont bien été supprimées.' resetQuest: '§6Les données de la quête ont été supprimées pour {0} joueurs.' startQuest: '§6Vous avez forcé le début de la quête {0} (UUID du joueur : {1}).' - startQuestNoRequirements: '§cLe joueur ne remplit pas les conditions pour la quête {0}... Ajoutez "true" à la fin de votre commande pour ne pas effectuer la vérification des conditions.' + startQuestNoRequirements: '§cLe joueur ne remplit pas les conditions pour la quête {0}... Ajoutez "-overrideRequirements" à la fin de votre commande pour ne pas effectuer la vérification des conditions.' cancelQuest: '§6Vous avez annulé la quête {0}.' cancelQuestUnavailable: '§cLa quête {0} ne peut être annulée.' backupCreated: '§6Vous avez créé une sauvegarde des quêtes et des données joueurs avec succès.' @@ -265,7 +265,7 @@ msg: list: '§aListe de tous les MythicMobs :' isntMythicMob: '§cCe Mythic Mob n''existe pas.' disabled: '§cMythicMob est désactivé.' - epicBossDoesntExist: '§cCet EpicBoss n''existe pas.' + advancedSpawnersMob: 'Écrivez le nom du mob de AdvancedSpawners à tuer :' textList: syntax: '§cSyntaxe correcte : ' added: '§aTexte "§7{0}§a" ajouté.' @@ -411,8 +411,6 @@ inv: startableFromGUILore: Permet au joueur de commencer la quête depuis son menu de Quêtes. scoreboardItem: Activer le scoreboard scoreboardItemLore: Si désactivée, la quête ne sera pas affichée dans le scoreboard. - hideItem: Cacher la quête du menu et de la dynmap - hideItemLore: Si activé, la quête ne sera pas affichée sur la dynmap ni dans le menu de Quêtes. hideNoRequirementsItem: Masquer lorsque les conditions ne sont pas remplies hideNoRequirementsItemLore: Si activé, la quête ne sera pas affichée dans le menu des quêtes lorsque les conditions ne sont pas remplies. bypassLimit: Ne pas compter la limite de quêtes @@ -469,6 +467,8 @@ inv: firework: '§dFeu d''artifice de fin' fireworkLore: Feu d'artifice lancé lorsque le joueur termine la quête fireworkLoreDrop: Déposez votre feu d'artifice personnalisé ici + visibility: '§bVisibilité de la quête' + visibilityLore: Choisir dans quels onglets du menu seront affichés la quête et si la quête est visible sur les cartes dynamiques. keepDatas: Conserver les données des joueurs keepDatasLore: |- Forcer le plugin à préserver les données des joueurs, même si les étapes ont été modifiées. @@ -672,6 +672,12 @@ inv: name: Cause de dégât damageCausesList: name: Liste des causes de dégât + visibility: + name: Visibilité de la quête + notStarted: 'Onglet du menu "Non commencées"' + inProgress: 'Onglet du menu "En cours"' + finished: 'Onglet du menu "Terminées"' + maps: 'Cartes (dynmap, BlueMap...)' scoreboard: name: '§6§lQuêtes' noLaunched: '§cAucune quête en cours.' From 7ccd3565d33b7f09920725fd7ca7bd138e067f0b Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Tue, 30 Aug 2022 11:12:46 +0200 Subject: [PATCH 057/111] :bug: Fixed pool hologram, visibility option, reload command --- core/src/main/java/fr/skytasul/quests/api/npcs/BQNPC.java | 4 +++- .../java/fr/skytasul/quests/commands/CommandsManager.java | 2 +- .../java/fr/skytasul/quests/options/OptionVisibility.java | 2 +- .../java/fr/skytasul/quests/structure/pools/QuestPool.java | 3 ++- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/api/npcs/BQNPC.java b/core/src/main/java/fr/skytasul/quests/api/npcs/BQNPC.java index da1f10db..f81ec134 100644 --- a/core/src/main/java/fr/skytasul/quests/api/npcs/BQNPC.java +++ b/core/src/main/java/fr/skytasul/quests/api/npcs/BQNPC.java @@ -186,7 +186,7 @@ public void run() { if (hologramText.canAppear && hologramText.visible) hologramText.refresh(en); if (hologramLaunch.canAppear) hologramLaunch.refresh(en); if (hologramLaunchNo.canAppear) hologramLaunchNo.refresh(en); - if (hologramPool.canAppear && hologramPool.visible) hologramPool.refresh(en); + if (hologramPool.canAppear) hologramPool.refresh(en); } }.runTaskTimer(BeautyQuests.getInstance(), 20L, 1L); } @@ -344,6 +344,8 @@ public String toString() { hologramsInfo = "holograms:"; hologramsInfo += "\n- text=" + hologramText.toString(); hologramsInfo += "\n- launch=" + hologramLaunch.toString(); + hologramsInfo += "\n- launchNo=" + hologramLaunchNo.toString(); + hologramsInfo += "\n- pool=" + hologramPool.toString(); } return npcInfo + " " + hologramsInfo; } diff --git a/core/src/main/java/fr/skytasul/quests/commands/CommandsManager.java b/core/src/main/java/fr/skytasul/quests/commands/CommandsManager.java index 1e4910eb..27b8c204 100644 --- a/core/src/main/java/fr/skytasul/quests/commands/CommandsManager.java +++ b/core/src/main/java/fr/skytasul/quests/commands/CommandsManager.java @@ -127,7 +127,7 @@ public void registerCommands(String subpath, OrphanCommand... commands) { } public void lockCommands() { - if (locked) throw new IllegalStateException("Commands manager already locked."); + if (locked) return; locked = true; handler.registerBrigadier(); if (handler.isBrigadierSupported()) DebugUtils.logMessage("Brigadier supported!"); diff --git a/core/src/main/java/fr/skytasul/quests/options/OptionVisibility.java b/core/src/main/java/fr/skytasul/quests/options/OptionVisibility.java index 4cb5185d..4d95e0ff 100644 --- a/core/src/main/java/fr/skytasul/quests/options/OptionVisibility.java +++ b/core/src/main/java/fr/skytasul/quests/options/OptionVisibility.java @@ -89,7 +89,7 @@ public Inventory open(Player p) { @Override public boolean onClick(Player p, Inventory inv, ItemStack current, int slot, ClickType click) { - if (slot > 0 && slot < 4) { + if (slot >= 0 && slot < 4) { locations.put(VisibilityLocation.values()[slot], ItemUtils.toggle(current)); }else if (slot == 4) { setValue(locations.entrySet().stream().filter(Entry::getValue).map(Entry::getKey).collect(Collectors.toList())); diff --git a/core/src/main/java/fr/skytasul/quests/structure/pools/QuestPool.java b/core/src/main/java/fr/skytasul/quests/structure/pools/QuestPool.java index 29a8a2a9..21da45ca 100644 --- a/core/src/main/java/fr/skytasul/quests/structure/pools/QuestPool.java +++ b/core/src/main/java/fr/skytasul/quests/structure/pools/QuestPool.java @@ -225,7 +225,8 @@ public String give(Player p) { }); } } - return "started quest(s) #" + started.stream().map(x -> Integer.toString(x.getID())).collect(Collectors.joining(", ")); + //return "started quest(s) #" + started.stream().map(x -> Integer.toString(x.getID())).collect(Collectors.joining(", ")); + return null; } List replenishQuests(PlayerPoolDatas datas) { From 9d4d948216c831b9d9462ea91225a853af507fe8 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Thu, 1 Sep 2022 22:43:20 +0200 Subject: [PATCH 058/111] :art: :memo: Added some javadoc, updated format of QuestsAPI --- core/pom.xml | 7 ++++++ .../fr/skytasul/quests/api/QuestsAPI.java | 11 +++++++++- .../api/comparison/ItemComparisonMap.java | 4 ++-- .../api/objects/QuestObjectCreator.java | 3 ++- .../api/serializable/SerializableCreator.java | 6 +++++ .../serializable/SerializableRegistry.java | 11 ++++++++++ .../skytasul/quests/api/stages/StageType.java | 10 +++++++++ .../quests/api/stages/StageTypeRegistry.java | 2 +- .../quests/api/stages/types/Locatable.java | 22 ++++++++++++++++++- .../quests/gui/creation/stages/Line.java | 9 +++++++- .../quests/gui/misc/ItemComparisonGUI.java | 2 +- .../fr/skytasul/quests/stages/StageChat.java | 2 +- pom.xml | 15 ++++++++++++- 13 files changed, 94 insertions(+), 10 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index 63cc7dd8..ec9f84e5 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -68,6 +68,13 @@ true + + maven-javadoc-plugin + 3.4.1 + + false + + diff --git a/core/src/main/java/fr/skytasul/quests/api/QuestsAPI.java b/core/src/main/java/fr/skytasul/quests/api/QuestsAPI.java index 84192ee5..c8950b39 100644 --- a/core/src/main/java/fr/skytasul/quests/api/QuestsAPI.java +++ b/core/src/main/java/fr/skytasul/quests/api/QuestsAPI.java @@ -30,12 +30,16 @@ import fr.skytasul.quests.utils.DebugUtils; import fr.skytasul.quests.utils.Lang; +/** + * This class contains most of the useful accessors to fetch data from BeautyQuests + * and methods to implement custom behaviors. + */ public final class QuestsAPI { private static final QuestObjectsRegistry requirements = new QuestObjectsRegistry<>("requirements", Lang.INVENTORY_REQUIREMENTS.toString()); private static final QuestObjectsRegistry rewards = new QuestObjectsRegistry<>("rewards", Lang.INVENTORY_REWARDS.toString()); private static final StageTypeRegistry stages = new StageTypeRegistry(); - public static final List itemComparisons = new LinkedList<>(); + private static final List itemComparisons = new LinkedList<>(); private static BQNPCsManager npcsManager = null; private static AbstractHolograms hologramsManager = null; @@ -76,7 +80,12 @@ public static void registerQuestOption(QuestOptionCreator creator) { DebugUtils.logMessage("Quest option registered (id: " + creator.id + ")"); } + public static List getItemComparisons() { + return itemComparisons; + } + public static void registerItemComparison(ItemComparison comparison) { + Validate.isTrue(itemComparisons.stream().noneMatch(x -> x.getID().equals(comparison.getID())), "This item comparison was already registerd"); itemComparisons.add(comparison); DebugUtils.logMessage("Item comparison registered (id: " + comparison.getID() + ")"); } diff --git a/core/src/main/java/fr/skytasul/quests/api/comparison/ItemComparisonMap.java b/core/src/main/java/fr/skytasul/quests/api/comparison/ItemComparisonMap.java index ac6ca4ef..7799a08b 100644 --- a/core/src/main/java/fr/skytasul/quests/api/comparison/ItemComparisonMap.java +++ b/core/src/main/java/fr/skytasul/quests/api/comparison/ItemComparisonMap.java @@ -33,7 +33,7 @@ public void setNotDefaultComparisons(ConfigurationSection section) { this.notDefault = (Map) section.getValues(false); effective = new ArrayList<>(); - for (ItemComparison comp : QuestsAPI.itemComparisons) { + for (ItemComparison comp : QuestsAPI.getItemComparisons()) { if (section.getBoolean(comp.getID(), comp.isEnabledByDefault())) effective.add(comp); } } @@ -42,7 +42,7 @@ public void setNotDefaultComparisons(Map comparisons) { this.notDefault = comparisons; effective = new ArrayList<>(); - for (ItemComparison comp : QuestsAPI.itemComparisons) { + for (ItemComparison comp : QuestsAPI.getItemComparisons()) { Boolean bool = notDefault.get(comp.getID()); if (Boolean.FALSE.equals(bool)) continue; if (!comp.isEnabledByDefault() && !Boolean.TRUE.equals(bool)) continue; diff --git a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectCreator.java b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectCreator.java index ab4e113b..ded73102 100644 --- a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectCreator.java +++ b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectCreator.java @@ -29,7 +29,8 @@ public QuestObjectCreator(String id, Class clazz, ItemStack item, S * @param item ItemStack shown in {@link QuestObjectGUI} * @param newObjectSupplier lambda returning an instance of this Object ({@link T}::new) * @param multiple can the object be present multiple times - * @param allowedLocations if present, specifies where the object can be used. If no location specified, the + * @param allowedLocations if present, specifies where the object can be used. + * If no location is specified, then all locations are accepted. */ public QuestObjectCreator(String id, Class clazz, ItemStack item, Supplier newObjectSupplier, boolean multiple, QuestObjectLocation... allowedLocations) { super(id, clazz, newObjectSupplier); diff --git a/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableCreator.java b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableCreator.java index 71cc7431..611a3f5b 100644 --- a/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableCreator.java +++ b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableCreator.java @@ -8,6 +8,12 @@ public class SerializableCreator { private final Class clazz; private final Supplier newObjectSupplier; + /** + * Creates a new creator for the serializable object of type <T> + * @param id unique string id for the serializable object type + * @param clazz class of the serializable object type + * @param newObjectSupplier function used to instanciate a serializable object + */ public SerializableCreator(String id, Class clazz, Supplier newObjectSupplier) { this.id = id; this.clazz = clazz; diff --git a/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableRegistry.java b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableRegistry.java index eca67c71..058eb347 100644 --- a/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableRegistry.java +++ b/core/src/main/java/fr/skytasul/quests/api/serializable/SerializableRegistry.java @@ -6,6 +6,13 @@ import fr.skytasul.quests.utils.DebugUtils; +/** + * This class is a registry for types of objects that can be serialized + * into Spigot configuration system. + * + * @param type of serializable object + * @param type of the creator associated with the serializable objects + */ public class SerializableRegistry> implements Iterable { protected final String id; @@ -19,6 +26,10 @@ public String getID() { return id; } + /** + * Registers a new type of serializable object. + * @param creator object that will be used to instanciate objects of type <T> + */ public void register(C creator) { if (creators.stream().anyMatch(x -> x.getID().equals(creator.getID()))) throw new IllegalStateException("A creator with the same id " + creator.getID() + " has been registered."); diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/StageType.java b/core/src/main/java/fr/skytasul/quests/api/stages/StageType.java index 1d4d403b..d3c07897 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/StageType.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/StageType.java @@ -26,6 +26,16 @@ public class StageType { private final SerializableRegistry, SerializableCreator>> optionsRegistry; + /** + * Creates a stage type. + * + * @param id unique string id for this stage + * @param clazz class of this stage + * @param name proper name of this stage + * @param loader function which instanciates and loads values of a previously saved stage + * @param item item representing this stage in the Stages GUI + * @param creationSupplier function creating a stage creation context + */ public StageType(String id, Class clazz, String name, StageLoader loader, ItemStack item, StageCreationSupplier creationSupplier) { this(id, clazz, name, loader, item, creationSupplier, new String[0]); } diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/StageTypeRegistry.java b/core/src/main/java/fr/skytasul/quests/api/stages/StageTypeRegistry.java index 0c51cf6a..4143f385 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/StageTypeRegistry.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/StageTypeRegistry.java @@ -14,7 +14,7 @@ public class StageTypeRegistry implements Iterable> { private List> types = new LinkedList<>(); /** - * Register new stage type into the plugin + * Registers new stage type into the plugin. * @param type StageType instance */ public void register(StageType type) { diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java index 374342fc..4ea861c1 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/Locatable.java @@ -43,7 +43,7 @@ default boolean isShown(Player player) { /** * Indicates if the Located instances gotten from {@link PreciseLocatable#getLocated()} - * and {@link MultipleLocatable#getNearbyLocated(fr.skytasul.quests.api.stages.types.Locatable.MultipleLocatable.NearbyFetcher)} + * and {@link MultipleLocatable#getNearbyLocated(MultipleLocatable.NearbyFetcher)} * can be safely retrieved from an asynchronous thread. * * @return true only if the Located fetch operations can @@ -59,6 +59,13 @@ default boolean canBeFetchedAsynchronously() { */ interface PreciseLocatable extends Locatable { + /** + * Gets the uniquely located object. + *

+ * The result should be consistent, which means that calling it twice without + * having something else changed in the game state would return the same value. + * @return the located object + */ Located getLocated(); } @@ -69,8 +76,18 @@ interface PreciseLocatable extends Locatable { */ interface MultipleLocatable extends Locatable { + /** + * Gets a {@link Spliterator} of all targets in the region specified by + * the {@link NearbyFetcher} parameter. + * @param fetcher describes the region from where the targets must be found + * @return a Spliterator which allows iterating through the targets + */ Spliterator getNearbyLocated(NearbyFetcher fetcher); + /** + * This POJO contains informations on the region from where + * the {@link MultipleLocatable} object has to find its targets. + */ interface NearbyFetcher { Location getCenter(); @@ -140,6 +157,9 @@ public boolean isTargeting(LocatedType type) { LocatedType[] types() default { LocatedType.ENTITY, LocatedType.BLOCK, LocatedType.OTHER }; } + /** + * Represents something that is locatable on a world. + */ interface Located { Location getLocation(); diff --git a/core/src/main/java/fr/skytasul/quests/gui/creation/stages/Line.java b/core/src/main/java/fr/skytasul/quests/gui/creation/stages/Line.java index d3a803ca..f766b088 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/creation/stages/Line.java +++ b/core/src/main/java/fr/skytasul/quests/gui/creation/stages/Line.java @@ -74,9 +74,16 @@ public int setItem(int slot, ItemStack is, StageRunnable click, boolean override * @param newItem the new item inserted */ public void editItem(int slot, ItemStack newItem){ + editItem(slot, newItem, false); + } + + public void editItem(int slot, ItemStack newItem, boolean refresh) { Pair last = items.get(slot); if (last == null) return; - items.set(slot, new Pair(newItem, last.getValue())); + items.set(slot, new Pair<>(newItem, last.getValue())); + if (refresh) { + setItems(activePage); + } } /** diff --git a/core/src/main/java/fr/skytasul/quests/gui/misc/ItemComparisonGUI.java b/core/src/main/java/fr/skytasul/quests/gui/misc/ItemComparisonGUI.java index 7e0cf126..5b522a3e 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/misc/ItemComparisonGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/misc/ItemComparisonGUI.java @@ -21,7 +21,7 @@ public class ItemComparisonGUI extends PagedGUI { private ItemComparisonMap comparisons; public ItemComparisonGUI(ItemComparisonMap comparisons, Runnable validate) { - super(Lang.INVENTORY_ITEM_COMPARISONS.toString(), DyeColor.LIME, QuestsAPI.itemComparisons, x -> validate.run(), null); + super(Lang.INVENTORY_ITEM_COMPARISONS.toString(), DyeColor.LIME, QuestsAPI.getItemComparisons(), x -> validate.run(), null); this.comparisons = comparisons; } diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageChat.java b/core/src/main/java/fr/skytasul/quests/stages/StageChat.java index f8227bdf..b5c193da 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageChat.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageChat.java @@ -77,7 +77,7 @@ private boolean check(String message, Player p) { if (placeholders) message = Utils.finalFormat(p, message, true); if (!(ignoreCase ? message.equalsIgnoreCase(text) : message.equals(text))) return false; if (!hasStarted(p)) return false; - if (canUpdate(p)) Utils.runSync(() -> finishStage(p)); + if (canUpdate(p)) finishStage(p); return true; } diff --git a/pom.xml b/pom.xml index a66bcc10..734b4117 100644 --- a/pom.xml +++ b/pom.xml @@ -1,5 +1,6 @@ - 4.0.0 @@ -15,6 +16,7 @@ UTF-8 1.8 1.8 + true unknown 0.20 @@ -41,4 +43,15 @@ v1_19_R1 dist + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.4.1 + + + + \ No newline at end of file From 18ebb5332fc10c153917457b033d10b9c73dad86 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Fri, 2 Sep 2022 22:13:56 +0200 Subject: [PATCH 059/111] :recycle: Cleaned up some GUI-related code --- .../api/comparison/ItemComparisonMap.java | 4 ++ .../api/objects/QuestObjectClickEvent.java | 16 +++--- .../requirements/TargetNumberRequirement.java | 6 +-- .../quests/gui/misc/DamageCausesGUI.java | 17 +------ .../fr/skytasul/quests/gui/misc/ItemGUI.java | 4 ++ .../gui/particles/ParticleEffectGUI.java | 2 +- .../quests/gui/particles/ParticleListGUI.java | 40 +++++---------- .../quests/gui/templates/PagedGUI.java | 9 +++- .../quests/gui/templates/StaticPagedGUI.java | 51 +++++++++++++++++++ .../quests/requirements/ClassRequirement.java | 1 - .../requirements/FactionRequirement.java | 1 - .../requirements/JobLevelRequirement.java | 6 +-- .../quests/requirements/MoneyRequirement.java | 6 +-- .../requirements/PermissionsRequirement.java | 1 - .../requirements/PlaceholderRequirement.java | 7 +-- .../quests/requirements/QuestRequirement.java | 6 +-- .../requirements/RegionRequirement.java | 5 +- .../logical/LogicalOrRequirement.java | 1 - .../quests/rewards/CheckpointReward.java | 1 - .../quests/rewards/CommandReward.java | 1 - .../skytasul/quests/rewards/ItemReward.java | 3 +- .../quests/rewards/MessageReward.java | 6 +-- .../skytasul/quests/rewards/MoneyReward.java | 6 +-- .../quests/rewards/PermissionReward.java | 1 - .../skytasul/quests/rewards/RandomReward.java | 2 - .../quests/rewards/RemoveItemsReward.java | 6 +-- .../rewards/RequirementDependentReward.java | 1 - .../quests/rewards/TeleportationReward.java | 6 +-- .../skytasul/quests/rewards/TitleReward.java | 8 +-- .../skytasul/quests/rewards/WaitReward.java | 6 +-- .../fr/skytasul/quests/rewards/XPReward.java | 6 +-- .../quests/utils/LevenshteinComparator.java | 4 ++ 32 files changed, 115 insertions(+), 125 deletions(-) create mode 100644 core/src/main/java/fr/skytasul/quests/gui/templates/StaticPagedGUI.java diff --git a/core/src/main/java/fr/skytasul/quests/api/comparison/ItemComparisonMap.java b/core/src/main/java/fr/skytasul/quests/api/comparison/ItemComparisonMap.java index 7799a08b..15394563 100644 --- a/core/src/main/java/fr/skytasul/quests/api/comparison/ItemComparisonMap.java +++ b/core/src/main/java/fr/skytasul/quests/api/comparison/ItemComparisonMap.java @@ -54,6 +54,10 @@ public Map getNotDefault() { return notDefault; } + public boolean isDefault() { + return notDefault.isEmpty(); + } + public List getEffective() { return effective; } diff --git a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectClickEvent.java b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectClickEvent.java index 1166a91b..4d0bb6f8 100644 --- a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectClickEvent.java +++ b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectClickEvent.java @@ -1,7 +1,5 @@ package fr.skytasul.quests.api.objects; -import java.util.List; - import org.bukkit.entity.Player; import org.bukkit.event.inventory.ClickType; import org.bukkit.inventory.ItemStack; @@ -16,9 +14,9 @@ public class QuestObjectClickEvent { private final ItemStack item; private final ClickType click; private final boolean creation; - private final Object clickedObject; + private final QuestObject clickedObject; - public QuestObjectClickEvent(Player player, QuestObjectGUI gui, ItemStack item, ClickType click, boolean creation, Object clickedObject) { + public QuestObjectClickEvent(Player player, QuestObjectGUI gui, ItemStack item, ClickType click, boolean creation, QuestObject clickedObject) { this.player = player; this.gui = gui; this.item = item; @@ -48,6 +46,7 @@ public boolean isInCreation() { } public void reopenGUI() { + updateItemLore(); gui.reopen(); } @@ -56,12 +55,13 @@ public void cancel() { gui.reopen(); } - public void updateItemLore(String... lore) { - ItemUtils.lore(item, lore); + public void remove() { + gui.remove(clickedObject); + gui.reopen(); } - public void updateItemLore(List lore) { - ItemUtils.lore(item, lore); + public void updateItemLore() { + ItemUtils.lore(item, clickedObject.getLore()); } } diff --git a/core/src/main/java/fr/skytasul/quests/api/requirements/TargetNumberRequirement.java b/core/src/main/java/fr/skytasul/quests/api/requirements/TargetNumberRequirement.java index 5c7f1fe4..3847b78a 100644 --- a/core/src/main/java/fr/skytasul/quests/api/requirements/TargetNumberRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/api/requirements/TargetNumberRequirement.java @@ -79,13 +79,9 @@ public void itemClick(QuestObjectClickEvent event) { Lang.COMPARISON_TYPE.send(event.getPlayer(), ComparisonMethod.getComparisonParser().getNames(), ComparisonMethod.GREATER_OR_EQUAL.name().toLowerCase()); new TextEditor<>(event.getPlayer(), null, comp -> { this.comparison = comp == null ? ComparisonMethod.GREATER_OR_EQUAL : comp; - event.updateItemLore(getLore()); event.reopenGUI(); }, ComparisonMethod.getComparisonParser()).passNullIntoEndConsumer().enter(); - }, () -> { - event.getGUI().remove(this); - event.reopenGUI(); - }, new NumberParser<>(numberClass(), true)).enter(); + }, event::remove, new NumberParser<>(numberClass(), true)).enter(); } } \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/gui/misc/DamageCausesGUI.java b/core/src/main/java/fr/skytasul/quests/gui/misc/DamageCausesGUI.java index f0035a23..795cb533 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/misc/DamageCausesGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/misc/DamageCausesGUI.java @@ -8,12 +8,11 @@ import org.bukkit.DyeColor; import org.bukkit.event.entity.EntityDamageEvent.DamageCause; -import org.bukkit.event.inventory.ClickType; import org.bukkit.inventory.ItemStack; import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.gui.templates.ListGUI; -import fr.skytasul.quests.gui.templates.PagedGUI; +import fr.skytasul.quests.gui.templates.StaticPagedGUI; import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.XMaterial; @@ -58,19 +57,7 @@ public ItemStack getObjectItemStack(DamageCause object) { @Override public void createObject(Function callback) { - new PagedGUI(Lang.INVENTORY_DAMAGE_CAUSES_LIST.toString(), DyeColor.ORANGE, MAPPED_ITEMS.keySet(), null, DamageCause::name) { - - @Override - public ItemStack getItemStack(DamageCause object) { - return MAPPED_ITEMS.get(object); - } - - @Override - public void click(DamageCause existing, ItemStack item, ClickType clickType) { - callback.apply(existing); - } - - }.sortValues(DamageCause::name).create(p); + new StaticPagedGUI(Lang.INVENTORY_DAMAGE_CAUSES_LIST.toString(), DyeColor.ORANGE, MAPPED_ITEMS, cause -> callback.apply(cause), DamageCause::name).create(p); } } diff --git a/core/src/main/java/fr/skytasul/quests/gui/misc/ItemGUI.java b/core/src/main/java/fr/skytasul/quests/gui/misc/ItemGUI.java index 36b22a9c..659050c6 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/misc/ItemGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/misc/ItemGUI.java @@ -26,6 +26,7 @@ public ItemGUI(Consumer end, Runnable cancel) { this.cancel = cancel; } + @Override public Inventory open(Player p){ Inventory inv = Bukkit.createInventory(null, InventoryType.DROPPER, Lang.INVENTORY_ITEM.toString()); @@ -39,6 +40,7 @@ public Inventory open(Player p){ return p.openInventory(inv).getTopInventory(); } + @Override public boolean onClick(Player p, Inventory inv, ItemStack current, int slot, ClickType click){ if (slot != 4) return true; new ItemCreatorGUI((obj) -> { @@ -47,7 +49,9 @@ public boolean onClick(Player p, Inventory inv, ItemStack current, int slot, Cli return true; } + @Override public boolean onClickCursor(Player p, Inventory inv, ItemStack current, ItemStack cursor, int slot){ + if (slot != 4) return true; p.setItemOnCursor(null); end.accept(cursor); return false; diff --git a/core/src/main/java/fr/skytasul/quests/gui/particles/ParticleEffectGUI.java b/core/src/main/java/fr/skytasul/quests/gui/particles/ParticleEffectGUI.java index 63dbb673..132d675e 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/particles/ParticleEffectGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/particles/ParticleEffectGUI.java @@ -106,7 +106,7 @@ public boolean onClick(Player p, Inventory inv, ItemStack current, int slot, Cli } } ParticleEffectGUI.this.create(p); - }).create(p); + }).allowCancel().create(p); break; case SLOT_COLOR: diff --git a/core/src/main/java/fr/skytasul/quests/gui/particles/ParticleListGUI.java b/core/src/main/java/fr/skytasul/quests/gui/particles/ParticleListGUI.java index 29114a95..23aa122b 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/particles/ParticleListGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/particles/ParticleListGUI.java @@ -1,45 +1,33 @@ package fr.skytasul.quests.gui.particles; +import java.util.Map; import java.util.function.Consumer; +import java.util.function.Function; +import java.util.stream.Collectors; import org.bukkit.DyeColor; import org.bukkit.Particle; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import fr.skytasul.quests.api.options.QuestOption; import fr.skytasul.quests.gui.ItemUtils; -import fr.skytasul.quests.gui.templates.PagedGUI; +import fr.skytasul.quests.gui.templates.StaticPagedGUI; import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.ParticleEffect; -import fr.skytasul.quests.utils.Utils; import fr.skytasul.quests.utils.XMaterial; -public class ParticleListGUI extends PagedGUI { - private Consumer end; +public class ParticleListGUI extends StaticPagedGUI { - public ParticleListGUI(Consumer end) { - super(Lang.INVENTORY_PARTICLE_LIST.toString(), DyeColor.MAGENTA, ParticleEffectGUI.PARTICLES, null, Particle::name); - this.end = end; - } - - @Override - public ItemStack getItemStack(Particle object) { - boolean colorable = ParticleEffect.canHaveColor(object); - String[] lore = colorable ? new String[] { QuestOption.formatDescription(Lang.particle_colored.toString()) } : new String[0]; - return ItemUtils.item(colorable ? XMaterial.MAP : XMaterial.PAPER, "§e" + object.name(), lore); - } + private static final Map PARTICLES = ParticleEffectGUI.PARTICLES + .stream().collect(Collectors.toMap(Function.identity(), particle -> { + boolean colorable = ParticleEffect.canHaveColor(particle); + String[] lore = colorable ? new String[] { QuestOption.formatDescription(Lang.particle_colored.toString()) } : new String[0]; + return ItemUtils.item(colorable ? XMaterial.MAP : XMaterial.PAPER, "§e" + particle.name(), lore); + })); - @Override - public void click(Particle existing, ItemStack item, ClickType clickType) { - end.accept(existing); + public ParticleListGUI(Consumer end) { + super(Lang.INVENTORY_PARTICLE_LIST.toString(), DyeColor.MAGENTA, PARTICLES, end, Particle::name); + sortValuesByName(); } - @Override - public CloseBehavior onClose(Player p, Inventory inv) { - Utils.runSync(() -> end.accept(null)); - return CloseBehavior.REMOVE; - } } \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/gui/templates/PagedGUI.java b/core/src/main/java/fr/skytasul/quests/gui/templates/PagedGUI.java index a390a11b..46029b92 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/templates/PagedGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/templates/PagedGUI.java @@ -6,6 +6,7 @@ import java.util.function.Consumer; import java.util.function.Function; +import org.apache.commons.lang.Validate; import org.bukkit.Bukkit; import org.bukkit.DyeColor; import org.bukkit.entity.Player; @@ -41,7 +42,7 @@ public abstract class PagedGUI implements CustomInventory { protected List objects; protected Consumer> validate; private ItemStack validationItem = ItemUtils.itemDone; - private LevenshteinComparator comparator; + protected LevenshteinComparator comparator; protected PagedGUI(String name, DyeColor color, Collection objects) { this(name, color, objects, null, null); @@ -84,6 +85,12 @@ public PagedGUI setValidate(Consumer> validate, ItemStack validationI return this; } + public PagedGUI sortValuesByName() { + Validate.notNull(comparator); + sortValues(comparator.getFunction()); + return this; + } + public > PagedGUI sortValues(Function mapper) { objects.sort((o1, o2) -> { C map1; diff --git a/core/src/main/java/fr/skytasul/quests/gui/templates/StaticPagedGUI.java b/core/src/main/java/fr/skytasul/quests/gui/templates/StaticPagedGUI.java new file mode 100644 index 00000000..5129fb80 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/gui/templates/StaticPagedGUI.java @@ -0,0 +1,51 @@ +package fr.skytasul.quests.gui.templates; + +import java.util.Map; +import java.util.Map.Entry; +import java.util.function.Consumer; +import java.util.function.Function; + +import org.bukkit.DyeColor; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import fr.skytasul.quests.utils.Utils; + +public class StaticPagedGUI extends PagedGUI> { + + protected final Consumer clicked; + private boolean cancelAllowed = false; + + public StaticPagedGUI(String name, DyeColor color, Map objects, Consumer clicked, Function nameMapper) { + super(name, color, objects.entrySet(), null, nameMapper == null ? null : entry -> nameMapper.apply(entry.getKey())); + this.clicked = clicked; + } + + public StaticPagedGUI allowCancel() { + cancelAllowed = true; + return this; + } + + @Override + public ItemStack getItemStack(Entry object) { + return object.getValue(); + } + + @Override + public void click(Entry existing, ItemStack item, ClickType clickType) { + clicked.accept(existing.getKey()); + } + + @Override + public CloseBehavior onClose(Player p, Inventory inv) { + if (cancelAllowed) { + Utils.runSync(() -> clicked.accept(null)); + return CloseBehavior.NOTHING; + }else { + return CloseBehavior.REOPEN; + } + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/requirements/ClassRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/ClassRequirement.java index f8416d19..fc9e142c 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/ClassRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/ClassRequirement.java @@ -93,7 +93,6 @@ public void click(RPGClass existing, ItemStack item, ClickType clickType) { @Override public void finish(List objects) { classes = objects; - event.updateItemLore(getLore()); event.reopenGUI(); } diff --git a/core/src/main/java/fr/skytasul/quests/requirements/FactionRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/FactionRequirement.java index 1b856292..f0a76ecd 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/FactionRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/FactionRequirement.java @@ -89,7 +89,6 @@ public void click(Faction existing, ItemStack item, ClickType clickType) { @Override public void finish(List objects) { factions = objects; - event.updateItemLore(getLore()); event.reopenGUI(); } diff --git a/core/src/main/java/fr/skytasul/quests/requirements/JobLevelRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/JobLevelRequirement.java index bbb71d88..1ffb32b5 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/JobLevelRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/JobLevelRequirement.java @@ -62,12 +62,8 @@ public String[] getLore() { @Override public void itemClick(QuestObjectClickEvent event) { Lang.CHOOSE_JOB_REQUIRED.send(event.getPlayer()); - new TextEditor(event.getPlayer(), () -> { - if (jobName == null) event.getGUI().remove(this); - event.reopenGUI(); - }, obj -> { + new TextEditor(event.getPlayer(), event::cancel, obj -> { jobName = obj; - event.updateItemLore(getLore()); super.itemClick(event); }).useStrippedMessage().enter(); } diff --git a/core/src/main/java/fr/skytasul/quests/requirements/MoneyRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/MoneyRequirement.java index 8f02e7bb..244ac7f7 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/MoneyRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/MoneyRequirement.java @@ -54,12 +54,8 @@ public String[] getLore() { @Override public void itemClick(QuestObjectClickEvent event) { Lang.CHOOSE_MONEY_REQUIRED.send(event.getPlayer()); - new TextEditor<>(event.getPlayer(), () -> { - if (money == 0) event.getGUI().remove(this); - event.reopenGUI(); - }, obj -> { + new TextEditor<>(event.getPlayer(), event::cancel, obj -> { this.money = obj; - event.updateItemLore(getLore()); event.reopenGUI(); }, new NumberParser<>(Double.class, true, true)).enter(); } diff --git a/core/src/main/java/fr/skytasul/quests/requirements/PermissionsRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/PermissionsRequirement.java index 5c0e0eda..c44ba0b5 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/PermissionsRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/PermissionsRequirement.java @@ -74,7 +74,6 @@ public void finish(List objects) { Lang.CHOOSE_PERM_REQUIRED_MESSAGE.send(p); new TextEditor(p, event::reopenGUI, obj -> { message = obj; - event.updateItemLore(getLore()); event.reopenGUI(); }).passNullIntoEndConsumer().enter(); } diff --git a/core/src/main/java/fr/skytasul/quests/requirements/PlaceholderRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/PlaceholderRequirement.java index dfd2a1ae..b2886129 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/PlaceholderRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/PlaceholderRequirement.java @@ -124,10 +124,7 @@ public String[] getLore() { @Override public void itemClick(QuestObjectClickEvent event) { Lang.CHOOSE_PLACEHOLDER_REQUIRED_IDENTIFIER.send(event.getPlayer()); - new TextEditor(event.getPlayer(), () -> { - if (rawPlaceholder == null) event.getGUI().remove(this); - event.reopenGUI(); - }, id -> { + new TextEditor(event.getPlayer(), event::cancel, id -> { setPlaceholder(id); Lang.CHOOSE_PLACEHOLDER_REQUIRED_VALUE.send(event.getPlayer(), id); new TextEditor(event.getPlayer(), () -> { @@ -140,11 +137,9 @@ public void itemClick(QuestObjectClickEvent event) { Lang.COMPARISON_TYPE.send(event.getPlayer(), ComparisonMethod.getComparisonParser().getNames(), ComparisonMethod.EQUALS.name().toLowerCase()); new TextEditor<>(event.getPlayer(), null, comp -> { this.comparison = comp == null ? ComparisonMethod.EQUALS : comp; - event.updateItemLore(getLore()); event.reopenGUI(); }, ComparisonMethod.getComparisonParser()).passNullIntoEndConsumer().enter(); }catch (NumberFormatException __) { - event.updateItemLore(getLore()); event.reopenGUI(); } }).enter(); diff --git a/core/src/main/java/fr/skytasul/quests/requirements/QuestRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/QuestRequirement.java index 4974f278..b337c8d1 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/QuestRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/QuestRequirement.java @@ -61,15 +61,11 @@ public void itemClick(QuestObjectClickEvent event) { new ChooseQuestGUI(QuestsAPI.getQuests().getQuests(), quest -> { this.questId = quest.getID(); - event.updateItemLore(getLore()); event.reopenGUI(); }) { @Override public fr.skytasul.quests.gui.CustomInventory.CloseBehavior onClose(Player p, org.bukkit.inventory.Inventory inv) { - Utils.runSync(() -> { - event.getGUI().remove(QuestRequirement.this); - event.reopenGUI(); - }); + Utils.runSync(event::remove); return CloseBehavior.NOTHING; } }.create(event.getPlayer()); diff --git a/core/src/main/java/fr/skytasul/quests/requirements/RegionRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/RegionRequirement.java index 8465f76b..e941737c 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/RegionRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/RegionRequirement.java @@ -54,12 +54,11 @@ public void itemClick(QuestObjectClickEvent event) { if (region != null) { this.worldName = p.getWorld().getName(); this.regionName = region.getId(); - event.updateItemLore(getLore()); + event.reopenGUI(); }else { Utils.sendMessage(p, Lang.REGION_DOESNT_EXIST.toString()); - event.getGUI().remove(this); + event.remove(); } - event.reopenGUI(); }).useStrippedMessage().enter(); } diff --git a/core/src/main/java/fr/skytasul/quests/requirements/logical/LogicalOrRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/logical/LogicalOrRequirement.java index cb875f32..c603a7cc 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/logical/LogicalOrRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/logical/LogicalOrRequirement.java @@ -47,7 +47,6 @@ public String[] getLore() { public void itemClick(QuestObjectClickEvent event) { QuestsAPI.getRequirements().createGUI(QuestObjectLocation.OTHER, requirements -> { this.requirements = requirements; - event.updateItemLore(getLore()); event.reopenGUI(); }, requirements).create(event.getPlayer()); } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/CheckpointReward.java b/core/src/main/java/fr/skytasul/quests/rewards/CheckpointReward.java index af0071f1..4e393d7f 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/CheckpointReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/CheckpointReward.java @@ -64,7 +64,6 @@ public String[] getLore() { public void itemClick(QuestObjectClickEvent event) { QuestsAPI.getRewards().createGUI(Lang.INVENTORY_CHECKPOINT_ACTIONS.toString(), QuestObjectLocation.CHECKPOINT, rewards -> { actions = rewards; - event.updateItemLore(getLore()); event.reopenGUI(); }, actions, null).create(event.getPlayer()); } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/CommandReward.java b/core/src/main/java/fr/skytasul/quests/rewards/CommandReward.java index 5ce2931f..9d244794 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/CommandReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/CommandReward.java @@ -75,7 +75,6 @@ public ItemStack getObjectItemStack(Command cmd) { @Override public void finish(List objects) { commands = objects; - event.updateItemLore(getLore()); event.reopenGUI(); } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/ItemReward.java b/core/src/main/java/fr/skytasul/quests/rewards/ItemReward.java index 54f29d97..667b6868 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/ItemReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/ItemReward.java @@ -52,8 +52,7 @@ public String[] getLore() { public void itemClick(QuestObjectClickEvent event) { new ItemsGUI(items -> { this.items = items; - event.updateItemLore(getLore()); - event.getGUI().reopen(); + event.reopenGUI(); }, items).create(event.getPlayer()); } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/MessageReward.java b/core/src/main/java/fr/skytasul/quests/rewards/MessageReward.java index 3014306e..5f9d17d5 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/MessageReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/MessageReward.java @@ -40,12 +40,8 @@ public String[] getLore() { @Override public void itemClick(QuestObjectClickEvent event) { Lang.WRITE_MESSAGE.send(event.getPlayer()); - new TextEditor(event.getPlayer(), () -> { - if (text == null) event.getGUI().remove(this); - event.reopenGUI(); - }, obj -> { + new TextEditor(event.getPlayer(), event::cancel, obj -> { this.text = obj; - event.updateItemLore(getLore()); event.reopenGUI(); }).enter(); } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/MoneyReward.java b/core/src/main/java/fr/skytasul/quests/rewards/MoneyReward.java index 093b56b9..6e8db9af 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/MoneyReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/MoneyReward.java @@ -49,12 +49,8 @@ public String[] getLore() { @Override public void itemClick(QuestObjectClickEvent event) { Lang.CHOOSE_MONEY_REWARD.send(event.getPlayer()); - new TextEditor<>(event.getPlayer(), () -> { - if (money == 0) event.getGUI().remove(this); - event.reopenGUI(); - }, obj -> { + new TextEditor<>(event.getPlayer(), event::cancel, obj -> { money = obj; - event.updateItemLore(getLore()); event.reopenGUI(); }, new NumberParser<>(Double.class, false, true)).enter(); } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/PermissionReward.java b/core/src/main/java/fr/skytasul/quests/rewards/PermissionReward.java index 6c75f2a9..d777329c 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/PermissionReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/PermissionReward.java @@ -47,7 +47,6 @@ public String[] getLore() { public void itemClick(QuestObjectClickEvent event) { new PermissionListGUI(permissions, permissions -> { PermissionReward.this.permissions = permissions; - event.updateItemLore(getLore()); event.reopenGUI(); }).create(event.getPlayer()); } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/RandomReward.java b/core/src/main/java/fr/skytasul/quests/rewards/RandomReward.java index 03755812..8a639d64 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/RandomReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/RandomReward.java @@ -100,7 +100,6 @@ public void itemClick(QuestObjectClickEvent event) { if (event.isInCreation() || event.getClick().isLeftClick()) { QuestsAPI.getRewards().createGUI(QuestObjectLocation.OTHER, rewards -> { this.rewards = rewards; - event.updateItemLore(getLore()); event.reopenGUI(); }, rewards).create(event.getPlayer()); }else if (event.getClick().isRightClick()) { @@ -109,7 +108,6 @@ public void itemClick(QuestObjectClickEvent event) { Lang.REWARD_EDITOR_RANDOM_MAX.send(event.getPlayer()); new TextEditor<>(event.getPlayer(), event::reopenGUI, max -> { setMinMax(min, max == null ? min : max); - event.updateItemLore(getLore()); event.reopenGUI(); }, NumberParser.INTEGER_PARSER_STRICT_POSITIVE).passNullIntoEndConsumer().enter(); }, NumberParser.INTEGER_PARSER_POSITIVE).enter(); diff --git a/core/src/main/java/fr/skytasul/quests/rewards/RemoveItemsReward.java b/core/src/main/java/fr/skytasul/quests/rewards/RemoveItemsReward.java index e7466807..e09ba2b9 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/RemoveItemsReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/RemoveItemsReward.java @@ -68,14 +68,10 @@ public void itemClick(QuestObjectClickEvent event) { if (event.isInCreation() || event.getClick().isLeftClick()) { new ItemsGUI(items -> { this.items = items; - event.updateItemLore(getLore()); event.reopenGUI(); }, items).create(event.getPlayer()); }else if (event.getClick().isRightClick()) { - new ItemComparisonGUI(comparisons, () -> { - event.updateItemLore(getLore()); - event.reopenGUI(); - }).create(event.getPlayer()); + new ItemComparisonGUI(comparisons, event::reopenGUI).create(event.getPlayer()); } } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/RequirementDependentReward.java b/core/src/main/java/fr/skytasul/quests/rewards/RequirementDependentReward.java index 2a43f023..6055e1e5 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/RequirementDependentReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/RequirementDependentReward.java @@ -129,7 +129,6 @@ public boolean onClick(Player p, Inventory inv, ItemStack current, int slot, Cli }, rewards).create(p); break; case 4: - event.updateItemLore(getLore()); event.reopenGUI(); break; } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/TeleportationReward.java b/core/src/main/java/fr/skytasul/quests/rewards/TeleportationReward.java index 9f32284f..7e737be7 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/TeleportationReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/TeleportationReward.java @@ -42,12 +42,8 @@ public String[] getLore() { @Override public void itemClick(QuestObjectClickEvent event) { Lang.MOVE_TELEPORT_POINT.send(event.getPlayer()); - new WaitClick(event.getPlayer(), () -> { - if (teleportation == null) event.getGUI().remove(this); - event.reopenGUI(); - }, NPCGUI.validMove.clone(), () -> { + new WaitClick(event.getPlayer(), event::cancel, NPCGUI.validMove.clone(), () -> { teleportation = event.getPlayer().getLocation(); - event.updateItemLore(getLore()); event.reopenGUI(); }).enter(); } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/TitleReward.java b/core/src/main/java/fr/skytasul/quests/rewards/TitleReward.java index 0182fb3b..cdd3e2c2 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/TitleReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/TitleReward.java @@ -30,9 +30,11 @@ public String[] getLore() { public void itemClick(QuestObjectClickEvent event) { new TitleGUI(newTitle -> { if (newTitle == null) { - if (title == null) event.getGUI().remove(this); - }else title = newTitle; - event.updateItemLore(getLore()); + event.cancel(); + return; + } + + title = newTitle; event.reopenGUI(); }).edit(title).create(event.getPlayer()); } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/WaitReward.java b/core/src/main/java/fr/skytasul/quests/rewards/WaitReward.java index 21aae546..12df3bbb 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/WaitReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/WaitReward.java @@ -36,12 +36,8 @@ public String[] getLore() { @Override public void itemClick(QuestObjectClickEvent event) { Lang.REWARD_EDITOR_WAIT.send(event.getPlayer()); - new TextEditor<>(event.getPlayer(), () -> { - if (delay == 0) event.getGUI().remove(this); - event.reopenGUI(); - }, obj -> { + new TextEditor<>(event.getPlayer(), event::cancel, obj -> { delay = obj; - event.updateItemLore(getLore()); event.reopenGUI(); }, NumberParser.INTEGER_PARSER_STRICT_POSITIVE).enter(); } diff --git a/core/src/main/java/fr/skytasul/quests/rewards/XPReward.java b/core/src/main/java/fr/skytasul/quests/rewards/XPReward.java index cd8f6b36..94f1cd29 100644 --- a/core/src/main/java/fr/skytasul/quests/rewards/XPReward.java +++ b/core/src/main/java/fr/skytasul/quests/rewards/XPReward.java @@ -52,13 +52,9 @@ public String[] getLore() { @Override public void itemClick(QuestObjectClickEvent event) { Utils.sendMessage(event.getPlayer(), Lang.XP_GAIN.toString(), exp); - new TextEditor<>(event.getPlayer(), () -> { - if (exp == 0) event.getGUI().remove(this); - event.reopenGUI(); - }, obj -> { + new TextEditor<>(event.getPlayer(), event::cancel, obj -> { Utils.sendMessage(event.getPlayer(), Lang.XP_EDITED.toString(), exp, obj); exp = obj; - event.updateItemLore(getLore()); event.reopenGUI(); }, NumberParser.INTEGER_PARSER_STRICT_POSITIVE).enter(); } diff --git a/core/src/main/java/fr/skytasul/quests/utils/LevenshteinComparator.java b/core/src/main/java/fr/skytasul/quests/utils/LevenshteinComparator.java index b81edf16..4ba91530 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/LevenshteinComparator.java +++ b/core/src/main/java/fr/skytasul/quests/utils/LevenshteinComparator.java @@ -16,6 +16,10 @@ public LevenshteinComparator setReference(String reference) { this.reference = reference; return this; } + + public Function getFunction() { + return function; + } @Override public int compare(T o1, T o2) { From f8b42ee1d461c8f5f3bf8fb8c1b15727e03b3d1a Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Fri, 2 Sep 2022 22:14:42 +0200 Subject: [PATCH 060/111] :sparkles: Added equipment requirement --- .../quests/gui/creation/QuestObjectGUI.java | 4 + .../requirements/EquipmentRequirement.java | 110 ++++++++++++++++++ .../java/fr/skytasul/quests/utils/Lang.java | 3 + core/src/main/resources/locales/en_US.yml | 3 + 4 files changed, 120 insertions(+) create mode 100644 core/src/main/java/fr/skytasul/quests/requirements/EquipmentRequirement.java diff --git a/core/src/main/java/fr/skytasul/quests/gui/creation/QuestObjectGUI.java b/core/src/main/java/fr/skytasul/quests/gui/creation/QuestObjectGUI.java index 4d475c7b..95e8276c 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/creation/QuestObjectGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/creation/QuestObjectGUI.java @@ -22,6 +22,7 @@ import fr.skytasul.quests.gui.ItemUtils; import fr.skytasul.quests.gui.templates.ListGUI; import fr.skytasul.quests.gui.templates.PagedGUI; +import fr.skytasul.quests.requirements.EquipmentRequirement; import fr.skytasul.quests.requirements.LevelRequirement; import fr.skytasul.quests.requirements.PermissionsRequirement; import fr.skytasul.quests.requirements.QuestRequirement; @@ -32,6 +33,7 @@ import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.Utils; import fr.skytasul.quests.utils.XMaterial; +import fr.skytasul.quests.utils.nms.NMS; public class QuestObjectGUI extends ListGUI { @@ -117,6 +119,8 @@ public static void initialize(){ QuestsAPI.getRequirements().register(new RequirementCreator("levelRequired", LevelRequirement.class, ItemUtils.item(XMaterial.EXPERIENCE_BOTTLE, Lang.RLevel.toString()), LevelRequirement::new)); QuestsAPI.getRequirements().register(new RequirementCreator("permissionRequired", PermissionsRequirement.class, ItemUtils.item(XMaterial.PAPER, Lang.RPermissions.toString()), PermissionsRequirement::new)); QuestsAPI.getRequirements().register(new RequirementCreator("scoreboardRequired", ScoreboardRequirement.class, ItemUtils.item(XMaterial.COMMAND_BLOCK, Lang.RScoreboard.toString()), ScoreboardRequirement::new)); + if (NMS.getMCVersion() >= 9) + QuestsAPI.getRequirements().register(new RequirementCreator("equipmentRequired", EquipmentRequirement.class, ItemUtils.item(XMaterial.CHAINMAIL_HELMET, Lang.REquipment.toString()), EquipmentRequirement::new)); } } \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/requirements/EquipmentRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/EquipmentRequirement.java new file mode 100644 index 00000000..6b507c2b --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/requirements/EquipmentRequirement.java @@ -0,0 +1,110 @@ +package fr.skytasul.quests.requirements; + +import java.util.Map; +import java.util.function.Consumer; + +import org.bukkit.DyeColor; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; + +import com.google.common.collect.ImmutableMap; + +import fr.skytasul.quests.api.comparison.ItemComparisonMap; +import fr.skytasul.quests.api.objects.QuestObjectClickEvent; +import fr.skytasul.quests.api.options.QuestOption; +import fr.skytasul.quests.api.requirements.AbstractRequirement; +import fr.skytasul.quests.gui.ItemUtils; +import fr.skytasul.quests.gui.misc.ItemComparisonGUI; +import fr.skytasul.quests.gui.misc.ItemGUI; +import fr.skytasul.quests.gui.templates.StaticPagedGUI; +import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.MinecraftNames; +import fr.skytasul.quests.utils.XMaterial; + +public class EquipmentRequirement extends AbstractRequirement { + + private EquipmentSlot slot; + private ItemStack item; + private ItemComparisonMap comparisons; + + public EquipmentRequirement() {} + + public EquipmentRequirement(EquipmentSlot slot, ItemStack item, ItemComparisonMap comparisons) { + this.slot = slot; + this.item = item; + this.comparisons = comparisons; + } + + @Override + public boolean test(Player p) { + return comparisons.isSimilar(p.getInventory().getItem(slot), item); + } + + @Override + public AbstractRequirement clone() { + return new EquipmentRequirement(slot, item, comparisons); + } + + @Override + public String[] getLore() { + return new String[] { + QuestOption.formatNullableValue(slot.name() + " > " + MinecraftNames.getMaterialName(item)), + "", + Lang.RemoveMid.toString() }; + } + + @Override + public void itemClick(QuestObjectClickEvent event) { + if (event.isInCreation()) comparisons = new ItemComparisonMap(); + + new EquipmentSlotGUI(newSlot -> { + if (newSlot == null) { + event.cancel(); + return; + } + + new ItemGUI(newItem -> { + slot = newSlot; + item = newItem; + + new ItemComparisonGUI(comparisons, event::reopenGUI).create(event.getPlayer()); + + }, event::cancel).create(event.getPlayer()); + + }).allowCancel().create(event.getPlayer()); + } + + @Override + public void save(ConfigurationSection section) { + section.set("slot", slot.name()); + section.set("item", item); + if (!comparisons.isDefault()) section.set("comparisons", comparisons.getNotDefault()); + } + + @Override + public void load(ConfigurationSection section) { + slot = EquipmentSlot.valueOf(section.getString("slot")); + item = section.getItemStack("item"); + comparisons = section.contains("comparisons") ? new ItemComparisonMap(section.getConfigurationSection("comparisons")) : new ItemComparisonMap(); + } + + public static class EquipmentSlotGUI extends StaticPagedGUI{ + + private static final Map OBJECTS = ImmutableMap.builder() + .put(EquipmentSlot.HAND, ItemUtils.item(XMaterial.GOLDEN_SWORD, "§6Main hand")) + .put(EquipmentSlot.OFF_HAND, ItemUtils.item(XMaterial.SHIELD, "§eOff hand")) + .put(EquipmentSlot.FEET, ItemUtils.item(XMaterial.IRON_BOOTS, "§bFeet")) + .put(EquipmentSlot.LEGS, ItemUtils.item(XMaterial.IRON_LEGGINGS, "§bLegs")) + .put(EquipmentSlot.CHEST, ItemUtils.item(XMaterial.ELYTRA, "§bChest")) + .put(EquipmentSlot.HEAD, ItemUtils.item(XMaterial.TURTLE_HELMET.or(XMaterial.IRON_HELMET), "§bHead")) + .build(); + + public EquipmentSlotGUI(Consumer clicked) { + super(Lang.INVENTORY_EQUIPMENT_SLOTS.toString(), DyeColor.BROWN, OBJECTS, clicked, EquipmentSlot::name); + } + + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/utils/Lang.java b/core/src/main/java/fr/skytasul/quests/utils/Lang.java index e3094035..c58d1347 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Lang.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Lang.java @@ -678,6 +678,8 @@ public enum Lang implements Locale { visibility_finished("inv.visibility.finished"), visibility_maps("inv.visibility.maps"), + INVENTORY_EQUIPMENT_SLOTS("inv.equipmentSlots.name"), + BOOK_NAME("inv.listBook.questName"), BOOK_STARTER("inv.listBook.questStarter"), BOOK_REWARDS("inv.listBook.questRewards"), @@ -782,6 +784,7 @@ public enum Lang implements Locale { RQuest("misc.requirement.quest"), RSkillLvl("misc.requirement.mcMMOSkillLevel"), RMoney("misc.requirement.money"), + REquipment("misc.requirement.equipment"), BucketWater("misc.bucket.water"), BucketLava("misc.bucket.lava"), diff --git a/core/src/main/resources/locales/en_US.yml b/core/src/main/resources/locales/en_US.yml index a6fda10f..8e8ff255 100644 --- a/core/src/main/resources/locales/en_US.yml +++ b/core/src/main/resources/locales/en_US.yml @@ -716,6 +716,8 @@ inv: inProgress: '"In progress" menu tab' finished: '"Finished" menu tab' maps: 'Maps (such as dynmap or BlueMap)' + equipmentSlots: + name: Equipment slots scoreboard: name: §6§lQuests @@ -819,6 +821,7 @@ misc: quest: §aQuest required mcMMOSkillLevel: §dSkill level required money: §dMoney required + equipment: §eEquipment required bucket: water: Water bucket lava: Lava bucket From cbfa2fc1b1e828928844ac243c84aa4c434a12cb Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 4 Sep 2022 10:01:58 +0200 Subject: [PATCH 061/111] :sparkles: Added "Deal Damage" stage type --- .../java/fr/skytasul/quests/api/mobs/Mob.java | 12 +- .../quests/editors/checkers/NumberParser.java | 1 + .../quests/gui/creation/stages/StagesGUI.java | 2 + .../quests/gui/mobs/MobSelectionGUI.java | 44 +++++ .../skytasul/quests/gui/mobs/MobsListGUI.java | 26 +-- .../quests/requirements/MoneyRequirement.java | 2 +- .../quests/stages/StageDealDamage.java | 183 ++++++++++++++++++ .../java/fr/skytasul/quests/utils/Lang.java | 8 + core/src/main/resources/locales/en_US.yml | 9 + 9 files changed, 262 insertions(+), 25 deletions(-) create mode 100644 core/src/main/java/fr/skytasul/quests/gui/mobs/MobSelectionGUI.java create mode 100644 core/src/main/java/fr/skytasul/quests/stages/StageDealDamage.java diff --git a/core/src/main/java/fr/skytasul/quests/api/mobs/Mob.java b/core/src/main/java/fr/skytasul/quests/api/mobs/Mob.java index c85aee03..73909769 100644 --- a/core/src/main/java/fr/skytasul/quests/api/mobs/Mob.java +++ b/core/src/main/java/fr/skytasul/quests/api/mobs/Mob.java @@ -15,13 +15,13 @@ import fr.skytasul.quests.utils.Utils; import fr.skytasul.quests.utils.XMaterial; -public class Mob implements Cloneable { +public class Mob implements Cloneable { - protected final MobFactory factory; - protected final Data data; + protected final MobFactory factory; + protected final D data; protected String customName; - public Mob(MobFactory factory, Data data) { + public Mob(MobFactory factory, D data) { Validate.notNull(factory, "Mob factory cannot be null"); Validate.notNull(data, "Mob data cannot be null"); this.factory = factory; @@ -81,9 +81,9 @@ public boolean equals(Object obj) { } @Override - public Mob clone(){ + public Mob clone() { try { - return (Mob) super.clone(); + return (Mob) super.clone(); }catch (CloneNotSupportedException e) { e.printStackTrace(); return null; diff --git a/core/src/main/java/fr/skytasul/quests/editors/checkers/NumberParser.java b/core/src/main/java/fr/skytasul/quests/editors/checkers/NumberParser.java index 7cf75fc6..5ac85b15 100644 --- a/core/src/main/java/fr/skytasul/quests/editors/checkers/NumberParser.java +++ b/core/src/main/java/fr/skytasul/quests/editors/checkers/NumberParser.java @@ -11,6 +11,7 @@ public class NumberParser implements AbstractParser { public static final NumberParser INTEGER_PARSER = new NumberParser<>(Integer.class, false, false); public static final NumberParser INTEGER_PARSER_POSITIVE = new NumberParser<>(Integer.class, true, false); public static final NumberParser INTEGER_PARSER_STRICT_POSITIVE = new NumberParser<>(Integer.class, true, true); + public static final NumberParser DOUBLE_PARSER_STRICT_POSITIVE = new NumberParser<>(Double.class, true, true); private Class numberType; private boolean positive; diff --git a/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java b/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java index 6fd1e156..6205ba29 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java @@ -327,6 +327,7 @@ private void editBranch(QuestBranch branch){ private static final ItemStack stageBreed = ItemUtils.item(XMaterial.WHEAT, Lang.stageBreedAnimals.toString()); private static final ItemStack stageTame = ItemUtils.item(XMaterial.CARROT, Lang.stageTameAnimals.toString()); private static final ItemStack stageDeath = ItemUtils.item(XMaterial.SKELETON_SKULL, Lang.stageDeath.toString()); + private static final ItemStack stageDealDamage = ItemUtils.item(XMaterial.REDSTONE, Lang.stageDealDamage.toString()); public static void initialize(){ DebugUtils.logMessage("Initlializing default stage types."); @@ -348,5 +349,6 @@ public static void initialize(){ QuestsAPI.getStages().register(new StageType<>("BREED", StageBreed.class, Lang.Breed.name(), StageBreed::deserialize, stageBreed, StageBreed.Creator::new)); QuestsAPI.getStages().register(new StageType<>("TAME", StageTame.class, Lang.Tame.name(), StageTame::deserialize, stageTame, StageTame.Creator::new)); QuestsAPI.getStages().register(new StageType<>("DEATH", StageDeath.class, Lang.Death.name(), StageDeath::deserialize, stageDeath, StageDeath.Creator::new)); + QuestsAPI.getStages().register(new StageType<>("DEAL_DAMAGE", StageDealDamage.class, Lang.DealDamage.name(), StageDealDamage::deserialize, stageDealDamage, StageDealDamage.Creator::new)); } } \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/gui/mobs/MobSelectionGUI.java b/core/src/main/java/fr/skytasul/quests/gui/mobs/MobSelectionGUI.java new file mode 100644 index 00000000..b9e9b655 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/gui/mobs/MobSelectionGUI.java @@ -0,0 +1,44 @@ +package fr.skytasul.quests.gui.mobs; + +import java.util.function.Consumer; + +import org.bukkit.DyeColor; +import org.bukkit.entity.Player; +import org.bukkit.event.inventory.ClickType; +import org.bukkit.inventory.Inventory; +import org.bukkit.inventory.ItemStack; + +import fr.skytasul.quests.api.mobs.Mob; +import fr.skytasul.quests.api.mobs.MobFactory; +import fr.skytasul.quests.gui.templates.PagedGUI; +import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.Utils; + +public class MobSelectionGUI extends PagedGUI> { + + private Consumer> end; + + public MobSelectionGUI(Consumer> end) { + super(Lang.INVENTORY_MOBSELECT.toString(), DyeColor.LIME, MobFactory.factories); + this.end = end; + } + + @Override + public ItemStack getItemStack(MobFactory object) { + return object.getFactoryItem(); + } + + @Override + public void click(MobFactory existing, ItemStack item, ClickType clickType) { + existing.itemClick(p, mobData -> { + end.accept(mobData == null ? null : new Mob(existing, mobData)); + }); + } + + @Override + public CloseBehavior onClose(Player p, Inventory inv) { + Utils.runSync(() -> end.accept(null)); + return CloseBehavior.REMOVE; + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/gui/mobs/MobsListGUI.java b/core/src/main/java/fr/skytasul/quests/gui/mobs/MobsListGUI.java index c1a3ca4e..b48993bb 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/mobs/MobsListGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/mobs/MobsListGUI.java @@ -7,20 +7,17 @@ import java.util.function.Consumer; import org.bukkit.Bukkit; -import org.bukkit.DyeColor; import org.bukkit.entity.Player; import org.bukkit.event.inventory.ClickType; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import fr.skytasul.quests.api.mobs.Mob; -import fr.skytasul.quests.api.mobs.MobFactory; import fr.skytasul.quests.editors.TextEditor; import fr.skytasul.quests.editors.checkers.NumberParser; import fr.skytasul.quests.gui.CustomInventory; import fr.skytasul.quests.gui.Inventories; import fr.skytasul.quests.gui.ItemUtils; -import fr.skytasul.quests.gui.templates.PagedGUI; import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.XMaterial; @@ -38,6 +35,7 @@ public CustomInventory openLastInv(Player p) { return this; } + @Override public Inventory open(Player p){ inv = Bukkit.createInventory(null, 9, Lang.INVENTORY_MOBS.toString()); @@ -57,6 +55,7 @@ public void setMobsFromMap(Map, Integer>> map) { } } + @Override public boolean onClick(Player p, Inventory inv, ItemStack current, int slot, ClickType click){ if (slot == 8){ Inventories.closeAndExit(p); @@ -65,22 +64,13 @@ public boolean onClick(Player p, Inventory inv, ItemStack current, int slot, Cli } Entry, Integer> mobEntry = mobs.get(slot); if (mobEntry == null) { - new PagedGUI>(Lang.INVENTORY_MOBSELECT.toString(), DyeColor.LIME, MobFactory.factories) { - public ItemStack getItemStack(MobFactory object) { - return object.getFactoryItem(); - } - - @SuppressWarnings ("rawtypes") - public void click(MobFactory existing, ItemStack item, ClickType clickType) { - existing.itemClick(p, (obj) -> { - Inventories.put(p, openLastInv(p), MobsListGUI.this.inv); - if (obj == null) return; - Mob mob = new Mob(existing, obj); - MobsListGUI.this.inv.setItem(slot, mob.createItemStack(1)); - mobs.put(slot, new AbstractMap.SimpleEntry<>(mob, 1)); - }); + new MobSelectionGUI(mob -> { + if (mob != null) { + inv.setItem(slot, mob.createItemStack(1)); + mobs.put(slot, new AbstractMap.SimpleEntry<>(mob, 1)); } - }.create(p); + Inventories.put(p, openLastInv(p), MobsListGUI.this.inv); + }).create(p); }else { if (click == ClickType.SHIFT_LEFT) { Lang.MOB_NAME.send(p); diff --git a/core/src/main/java/fr/skytasul/quests/requirements/MoneyRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/MoneyRequirement.java index 244ac7f7..8790ec67 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/MoneyRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/MoneyRequirement.java @@ -57,7 +57,7 @@ public void itemClick(QuestObjectClickEvent event) { new TextEditor<>(event.getPlayer(), event::cancel, obj -> { this.money = obj; event.reopenGUI(); - }, new NumberParser<>(Double.class, true, true)).enter(); + }, NumberParser.DOUBLE_PARSER_STRICT_POSITIVE).enter(); } @Override diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageDealDamage.java b/core/src/main/java/fr/skytasul/quests/stages/StageDealDamage.java new file mode 100644 index 00000000..2d669f21 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/stages/StageDealDamage.java @@ -0,0 +1,183 @@ +package fr.skytasul.quests.stages; + +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +import org.bukkit.DyeColor; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.entity.EntityDamageByEntityEvent; +import org.bukkit.inventory.ItemStack; + +import fr.skytasul.quests.api.mobs.Mob; +import fr.skytasul.quests.api.options.QuestOption; +import fr.skytasul.quests.api.stages.AbstractStage; +import fr.skytasul.quests.api.stages.StageCreation; +import fr.skytasul.quests.editors.TextEditor; +import fr.skytasul.quests.editors.checkers.NumberParser; +import fr.skytasul.quests.gui.ItemUtils; +import fr.skytasul.quests.gui.creation.stages.Line; +import fr.skytasul.quests.gui.mobs.MobSelectionGUI; +import fr.skytasul.quests.gui.templates.ListGUI; +import fr.skytasul.quests.players.PlayerAccount; +import fr.skytasul.quests.players.PlayersManager; +import fr.skytasul.quests.structure.QuestBranch; +import fr.skytasul.quests.structure.QuestBranch.Source; +import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.XMaterial; + +@SuppressWarnings ("rawtypes") +public class StageDealDamage extends AbstractStage { + + private final double damage; + private final List targetMobs; + + private final String targetMobsString; + + public StageDealDamage(QuestBranch branch, double damage, List targetMobs) { + super(branch); + this.damage = damage; + this.targetMobs = targetMobs; + + targetMobsString = getTargetMobsString(targetMobs); + } + + private static String getTargetMobsString(List targetMobs) { + if (targetMobs == null || targetMobs.isEmpty()) return Lang.EntityTypeAny.toString(); + return targetMobs.stream().map(Mob::getName).collect(Collectors.joining(", ")); + } + + @Override + protected void initPlayerDatas(PlayerAccount acc, Map datas) { + datas.put("amount", damage); + } + + @EventHandler (priority = EventPriority.MONITOR) + public void onDamage(EntityDamageByEntityEvent event) { + if (!(event.getDamager() instanceof Player)) return; + + if (targetMobs != null && !targetMobs.isEmpty() + && targetMobs.stream().noneMatch(mob -> mob.appliesEntity(event.getEntity()))) return; + + Player player = (Player) event.getDamager(); + PlayerAccount account = PlayersManager.getPlayerAccount(player); + + if (!branch.hasStageLaunched(account, this)) return; + if (!canUpdate(player)) return; + + double amount = getData(account, "amount"); + amount -= event.getFinalDamage(); + if (amount <= 0) { + finishStage(player); + }else { + updateObjective(account, player, "amount", amount); + } + } + + @Override + protected String descriptionLine(PlayerAccount acc, Source source) { + return (targetMobs == null || targetMobs.isEmpty() ? Lang.SCOREBOARD_DEAL_DAMAGE_ANY : Lang.SCOREBOARD_DEAL_DAMAGE_MOBS).format(descriptionFormat(acc, source)); + } + + @Override + protected Object[] descriptionFormat(PlayerAccount acc, Source source) { + return new Object[] { (Supplier) () -> Integer.toString(super.getData(acc, "amount").intValue()), targetMobsString }; + } + + @Override + protected void serialize(ConfigurationSection section) { + section.set("damage", damage); + if (targetMobs != null && !targetMobs.isEmpty()) + section.set("targetMobs", targetMobs.stream().map(Mob::serialize).collect(Collectors.toList())); + } + + public static StageDealDamage deserialize(ConfigurationSection section, QuestBranch branch) { + return new StageDealDamage(branch, + section.getDouble("damage"), + section.contains("targetMobs") ? section.getMapList("targetMobs").stream().map(map -> Mob.deserialize((Map) map)).collect(Collectors.toList()) : null); + } + + public static class Creator extends StageCreation { + + private static final int SLOT_DAMAGE = 6; + private static final int SLOT_MOBS = 7; + + private double damage; + private List targetMobs; + + public Creator(Line line, boolean ending) { + super(line, ending); + + line.setItem(SLOT_DAMAGE, ItemUtils.item(XMaterial.REDSTONE, Lang.stageDealDamageValue.toString()), (p, item) -> { + Lang.DAMAGE_AMOUNT.send(p); + new TextEditor<>(p, () -> reopenGUI(p, false), newDamage -> { + setDamage(newDamage); + reopenGUI(p, false); + }, NumberParser.DOUBLE_PARSER_STRICT_POSITIVE).enter(); + }); + + line.setItem(SLOT_MOBS, ItemUtils.item(XMaterial.BLAZE_SPAWN_EGG, Lang.stageDealDamageMobs.toString(), QuestOption.formatNullableValue(Lang.EntityTypeAny.toString(), true)), (p, item) -> { + new ListGUI(Lang.stageDealDamageMobs.toString(), DyeColor.RED, targetMobs == null ? Collections.emptyList() : targetMobs) { + + @Override + public void finish(List objects) { + setTargetMobs(objects.isEmpty() ? null : objects); + reopenGUI(p, true); + } + + @Override + public ItemStack getObjectItemStack(Mob object) { + return object.createItemStack(1); + } + + @Override + public void createObject(Function callback) { + new MobSelectionGUI(callback::apply).create(p); + } + + }.create(p); + }); + } + + public void setDamage(double damage) { + this.damage = damage; + line.editItem(SLOT_DAMAGE, ItemUtils.lore(line.getItem(SLOT_DAMAGE), QuestOption.formatNullableValue(Double.toString(damage)))); + } + + public void setTargetMobs(List targetMobs) { + this.targetMobs = targetMobs; + boolean noMobs = targetMobs == null || targetMobs.isEmpty(); + line.editItem(SLOT_MOBS, ItemUtils.lore(line.getItem(SLOT_MOBS), QuestOption.formatNullableValue(getTargetMobsString(targetMobs), noMobs))); + } + + @Override + public void edit(StageDealDamage stage) { + super.edit(stage); + setDamage(stage.damage); + setTargetMobs(stage.targetMobs); + } + + @Override + public void start(Player p) { + super.start(p); + Lang.DAMAGE_AMOUNT.send(p); + new TextEditor<>(p, removeAndReopen(p, false), newDamage -> { + setDamage(newDamage); + reopenGUI(p, false); + }, NumberParser.DOUBLE_PARSER_STRICT_POSITIVE).enter(); + } + + @Override + protected StageDealDamage finishStage(QuestBranch branch) { + return new StageDealDamage(branch, damage, targetMobs); + } + + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/utils/Lang.java b/core/src/main/java/fr/skytasul/quests/utils/Lang.java index c58d1347..305f3cba 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Lang.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Lang.java @@ -208,6 +208,7 @@ public enum Lang implements Locale { BLOCK_TAGS("msg.editor.blockTag"), // 0: available block tags BUCKET_AMOUNT("msg.editor.typeBucketAmount"), + DAMAGE_AMOUNT("msg.editor.typeDamageAmount", EditorPrefix), LOCATION_GO("msg.editor.goToLocation"), LOCATION_RADIUS("msg.editor.typeLocationRadius"), @@ -358,6 +359,7 @@ public enum Lang implements Locale { stageBreedAnimals("inv.create.breedAnimals"), stageTameAnimals("inv.create.tameAnimals"), stageDeath("inv.create.death"), + stageDealDamage("inv.create.dealDamage"), stageText("inv.create.NPCText"), dialogLines("inv.create.dialogLines"), // 0: lines stageNPCSelect("inv.create.NPCSelect"), @@ -400,6 +402,9 @@ public enum Lang implements Locale { stageDeathCauseAny("inv.create.stage.death.anyCause"), stageDeathCausesSet("inv.create.stage.death.setCauses"), // 0: causes amount + stageDealDamageValue("inv.create.stage.dealDamage.damage"), + stageDealDamageMobs("inv.create.stage.dealDamage.targetMobs"), + INVENTORY_STAGES("inv.stages.name"), nextPage("inv.stages.nextPage"), laterPage("inv.stages.laterPage"), @@ -715,6 +720,8 @@ public enum Lang implements Locale { SCOREBOARD_LOCATION("scoreboard.stage.location"), // 0: x, 1: y, 2: z, 3: world SCOREBOARD_PLAY_TIME("scoreboard.stage.playTimeFormatted"), // 0: remaining time SCOREBOARD_DIE("scoreboard.stage.die"), + SCOREBOARD_DEAL_DAMAGE_ANY("scoreboard.stage.dealDamage.any"), // 0: damage + SCOREBOARD_DEAL_DAMAGE_MOBS("scoreboard.stage.dealDamage.mobs"), // 0: damage, 1: mobs /* Indications */ @@ -762,6 +769,7 @@ public enum Lang implements Locale { Breed("misc.stageType.breedAnimals"), Tame("misc.stageType.tameAnimals"), Death("misc.stageType.die"), + DealDamage("misc.stageType.dealDamage"), ComparisonEquals("misc.comparison.equals"), ComparisonDifferent("misc.comparison.different"), diff --git a/core/src/main/resources/locales/en_US.yml b/core/src/main/resources/locales/en_US.yml index 8e8ff255..e9381e84 100644 --- a/core/src/main/resources/locales/en_US.yml +++ b/core/src/main/resources/locales/en_US.yml @@ -204,6 +204,7 @@ msg: blockData: '§aWrite the blockdata (available blockdatas: §7{0}§a):' blockTag: '§aWrite the block tag (available tags: §7{0}§a):' typeBucketAmount: '§aWrite the amount of buckets to fill:' + typeDamageAmount: 'Write the amount of damage player have to deal:' goToLocation: §aGo to the wanted location for the stage. typeLocationRadius: '§aWrite the required distance from the location:' typeGameTicks: '§aWrite the required game ticks:' @@ -370,6 +371,7 @@ inv: breedAnimals: §aBreed animals tameAnimals: §aTame animals death: §cDie + dealDamage: §cDeal damage to mobs NPCText: §eEdit dialog dialogLines: '{0} lines' NPCSelect: §eChoose or create NPC @@ -412,6 +414,9 @@ inv: causes: '§aSet death causes §d(ADVANCED)' anyCause: Any death cause setCauses: '{0} death cause(s)' + dealDamage: + damage: '§eDamage to deal' + targetMobs: '§cMobs to damage' stages: name: Create stages nextPage: §eNext page @@ -746,6 +751,9 @@ scoreboard: breed: §eBreed §6{0} tame: §eTame §6{0} die: §cDie + dealDamage: + any: §cDeal {0} damage + mobs: §cDeal {0} damage to {1} indication: startQuest: §7Do you want to start the quest {0}? closeInventory: §7Are you sure you want to close the GUI? @@ -799,6 +807,7 @@ misc: breedAnimals: Breed animals tameAnimals: Tame animals die: Die + dealDamage: Deal damage comparison: equals: equal to {0} different: different to {0} From 0923d793727b8ee73a9a6fa261d56438ef0da4c8 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 4 Sep 2022 21:10:27 +0200 Subject: [PATCH 062/111] :bug: Bugfixes with Equipment requirement and Deal Damage stage * fixed projectile hits not being accounted for Deal Damage stage * fixed an exception with Equipment requirement creation * fixed item for mobs in Deal Damage stage --- .../java/fr/skytasul/quests/api/mobs/Mob.java | 28 ++++++++----------- .../api/objects/QuestObjectClickEvent.java | 5 ++++ .../quests/gui/misc/ItemCreatorGUI.java | 2 +- .../skytasul/quests/gui/mobs/MobsListGUI.java | 21 +++++++++++--- .../requirements/EquipmentRequirement.java | 1 + .../quests/stages/StageDealDamage.java | 14 ++++++++-- 6 files changed, 46 insertions(+), 25 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/api/mobs/Mob.java b/core/src/main/java/fr/skytasul/quests/api/mobs/Mob.java index 73909769..f54058f6 100644 --- a/core/src/main/java/fr/skytasul/quests/api/mobs/Mob.java +++ b/core/src/main/java/fr/skytasul/quests/api/mobs/Mob.java @@ -1,17 +1,12 @@ package fr.skytasul.quests.api.mobs; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; import org.apache.commons.lang.Validate; import org.bukkit.entity.Entity; -import org.bukkit.inventory.ItemStack; import fr.skytasul.quests.BeautyQuests; -import fr.skytasul.quests.gui.ItemUtils; -import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.Utils; import fr.skytasul.quests.utils.XMaterial; @@ -28,6 +23,14 @@ public Mob(MobFactory factory, D data) { this.data = data; } + public MobFactory getFactory() { + return factory; + } + + public D getData() { + return data; + } + public String getName() { return customName == null ? factory.getName(data) : customName; } @@ -36,22 +39,13 @@ public void setCustomName(String customName) { this.customName = customName; } - public ItemStack createItemStack(int amount) { - List lore = new ArrayList<>(); - lore.add(Lang.Amount.format(amount)); - lore.addAll(factory.getDescriptiveLore(data)); - lore.add(""); - lore.add(Lang.click.toString()); - XMaterial mobItem; + public XMaterial getMobItem() { try { - mobItem = Utils.mobItem(factory.getEntityType(data)); + return Utils.mobItem(factory.getEntityType(data)); }catch (Exception ex) { - mobItem = XMaterial.SPONGE; BeautyQuests.logger.warning("Unknow entity type for mob " + factory.getName(data), ex); + return XMaterial.SPONGE; } - ItemStack item = ItemUtils.item(mobItem, getName(), lore); - item.setAmount(Math.min(amount, 64)); - return item; } public boolean applies(Object data) { diff --git a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectClickEvent.java b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectClickEvent.java index 4d0bb6f8..c33235cc 100644 --- a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectClickEvent.java +++ b/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectClickEvent.java @@ -60,6 +60,11 @@ public void remove() { gui.reopen(); } + @Deprecated + public void updateItemLore(String... lore) { + ItemUtils.lore(item, lore); + } + public void updateItemLore() { ItemUtils.lore(item, clickedObject.getLore()); } diff --git a/core/src/main/java/fr/skytasul/quests/gui/misc/ItemCreatorGUI.java b/core/src/main/java/fr/skytasul/quests/gui/misc/ItemCreatorGUI.java index 0246a7d9..e3b3624d 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/misc/ItemCreatorGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/misc/ItemCreatorGUI.java @@ -48,7 +48,7 @@ public Inventory open(Player p) { this.p = p; inv = Bukkit.createInventory(null, 18, Lang.INVENTORY_CREATOR.toString()); - inv.setItem(0, ItemUtils.item(XMaterial.ARROW, Lang.itemType.toString())); + inv.setItem(0, ItemUtils.item(XMaterial.GRASS_BLOCK, Lang.itemType.toString())); inv.setItem(1, ItemUtils.item(XMaterial.REDSTONE, Lang.Amount.format(1))); inv.setItem(2, ItemUtils.itemSwitch(Lang.itemFlags.toString(), false)); inv.setItem(3, ItemUtils.item(XMaterial.NAME_TAG, Lang.itemName.toString())); diff --git a/core/src/main/java/fr/skytasul/quests/gui/mobs/MobsListGUI.java b/core/src/main/java/fr/skytasul/quests/gui/mobs/MobsListGUI.java index b48993bb..5100188b 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/mobs/MobsListGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/mobs/MobsListGUI.java @@ -1,7 +1,9 @@ package fr.skytasul.quests.gui.mobs; import java.util.AbstractMap; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.function.Consumer; @@ -51,7 +53,7 @@ public void setMobsFromMap(Map, Integer>> map) { int id = entry.getKey(); Entry, Integer> mobEntry = entry.getValue(); mobs.put(id, mobEntry); - inv.setItem(id, mobEntry.getKey().createItemStack(mobEntry.getValue())); + inv.setItem(id, createItemStack(mobEntry.getKey(), mobEntry.getValue())); } } @@ -66,7 +68,7 @@ public boolean onClick(Player p, Inventory inv, ItemStack current, int slot, Cli if (mobEntry == null) { new MobSelectionGUI(mob -> { if (mob != null) { - inv.setItem(slot, mob.createItemStack(1)); + inv.setItem(slot, createItemStack(mob, 1)); mobs.put(slot, new AbstractMap.SimpleEntry<>(mob, 1)); } Inventories.put(p, openLastInv(p), MobsListGUI.this.inv); @@ -76,14 +78,14 @@ public boolean onClick(Player p, Inventory inv, ItemStack current, int slot, Cli Lang.MOB_NAME.send(p); new TextEditor<>(p, () -> openLastInv(p), name -> { mobEntry.getKey().setCustomName((String) name); - inv.setItem(slot, mobEntry.getKey().createItemStack(mobEntry.getValue())); + inv.setItem(slot, createItemStack(mobEntry.getKey(), mobEntry.getValue())); openLastInv(p); }).passNullIntoEndConsumer().enter(); }else if (click == ClickType.LEFT) { Lang.MOB_AMOUNT.send(p); new TextEditor<>(p, () -> openLastInv(p), amount -> { mobEntry.setValue(amount); - inv.setItem(slot, mobEntry.getKey().createItemStack(amount)); + inv.setItem(slot, createItemStack(mobEntry.getKey(), amount)); openLastInv(p); }, NumberParser.INTEGER_PARSER_STRICT_POSITIVE).enter(); }else if (click.isRightClick()) { @@ -94,6 +96,17 @@ public boolean onClick(Player p, Inventory inv, ItemStack current, int slot, Cli } return true; } + + private ItemStack createItemStack(Mob mob, int amount) { + List lore = new ArrayList<>(); + lore.add(Lang.Amount.format(amount)); + lore.addAll(mob.getFactory().getDescriptiveLore(mob.getData())); + lore.add(""); + lore.add(Lang.click.toString()); + ItemStack item = ItemUtils.item(mob.getMobItem(), mob.getName(), lore); + item.setAmount(Math.min(amount, 64)); + return item; + } @Override public CloseBehavior onClose(Player p, Inventory inv) { diff --git a/core/src/main/java/fr/skytasul/quests/requirements/EquipmentRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/EquipmentRequirement.java index 6b507c2b..ac2a4e4a 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/EquipmentRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/EquipmentRequirement.java @@ -49,6 +49,7 @@ public AbstractRequirement clone() { @Override public String[] getLore() { + if (slot == null) return null; return new String[] { QuestOption.formatNullableValue(slot.name() + " > " + MinecraftNames.getMaterialName(item)), "", diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageDealDamage.java b/core/src/main/java/fr/skytasul/quests/stages/StageDealDamage.java index 2d669f21..16e6e2b6 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageDealDamage.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageDealDamage.java @@ -10,10 +10,12 @@ import org.bukkit.DyeColor; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.entity.Player; +import org.bukkit.entity.Projectile; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.entity.EntityDamageByEntityEvent; import org.bukkit.inventory.ItemStack; +import org.bukkit.projectiles.ProjectileSource; import fr.skytasul.quests.api.mobs.Mob; import fr.skytasul.quests.api.options.QuestOption; @@ -60,12 +62,18 @@ protected void initPlayerDatas(PlayerAccount acc, Map datas) { @EventHandler (priority = EventPriority.MONITOR) public void onDamage(EntityDamageByEntityEvent event) { - if (!(event.getDamager() instanceof Player)) return; + Player player; + if (event.getDamager() instanceof Projectile) { + ProjectileSource projectileShooter = ((Projectile) event.getDamager()).getShooter(); + if (!(projectileShooter instanceof Player)) return; + player = (Player) projectileShooter; + }else if (event.getDamager() instanceof Player) { + player = (Player) event.getDamager(); + }else return; if (targetMobs != null && !targetMobs.isEmpty() && targetMobs.stream().noneMatch(mob -> mob.appliesEntity(event.getEntity()))) return; - Player player = (Player) event.getDamager(); PlayerAccount account = PlayersManager.getPlayerAccount(player); if (!branch.hasStageLaunched(account, this)) return; @@ -133,7 +141,7 @@ public void finish(List objects) { @Override public ItemStack getObjectItemStack(Mob object) { - return object.createItemStack(1); + return ItemUtils.item(object.getMobItem(), object.getName(), Lang.RemoveMid.toString()); } @Override From f87da8e56667156f9222a663989d7d22cb8b1d7b Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 4 Sep 2022 21:11:52 +0200 Subject: [PATCH 063/111] :globe_with_meridians: Updated FR locale --- core/src/main/resources/locales/fr_FR.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/core/src/main/resources/locales/fr_FR.yml b/core/src/main/resources/locales/fr_FR.yml index 75b88015..37af6512 100644 --- a/core/src/main/resources/locales/fr_FR.yml +++ b/core/src/main/resources/locales/fr_FR.yml @@ -178,6 +178,7 @@ msg: blockData: '§aÉcrivez la blockdata (blockdatas disponibles : {0}) :' blockTag: '§aÉcrivez le tag de bloc (tagsdisponibles: §7{0}§a):' typeBucketAmount: '§aÉcrivez la quantité de seaux à remplir :' + typeDamageAmount: 'Écrivez le nombre de dégâts que le joueur doit infliger :' goToLocation: '§aAllez à l''emplacement souhaité pour l''étape.' typeLocationRadius: '§aÉcrivez la distance requise à partir de cet endroit :' typeGameTicks: '§aEcrivez la quantité de ticks requis :' @@ -338,6 +339,7 @@ inv: breedAnimals: '§aFaire reproduire des animaux' tameAnimals: '§aApprivoiser des animaux' death: '§cMourir' + dealDamage: '§cInfliger des dégâts à des mobs' NPCText: '§eModifier le dialogue' dialogLines: '{0} lignes' NPCSelect: '§eChoisir ou créer un NPC' @@ -380,6 +382,9 @@ inv: causes: '§aDéfinir les causes de la mort §d(AVANCED)' anyCause: Toute cause de mort setCauses: '{0} cause(s) de mort' + dealDamage: + damage: '§eDégâts à infliger' + targetMobs: '§cMobs à heurter' stages: name: Créer les étapes nextPage: '§ePage suivante' @@ -678,6 +683,8 @@ inv: inProgress: 'Onglet du menu "En cours"' finished: 'Onglet du menu "Terminées"' maps: 'Cartes (dynmap, BlueMap...)' + equipmentSlots: + name: Emplacements d'équipement scoreboard: name: '§6§lQuêtes' noLaunched: '§cAucune quête en cours.' @@ -705,6 +712,9 @@ scoreboard: breed: '§eFaites se reproduire §6{0}' tame: '§eApprivoisez §6{0}' die: '§cMourez' + dealDamage: + any: '§cInflige {0} dégâts' + mobs: '§cInflige {0} dégâts à {1}' indication: startQuest: '§7Voulez-vous commencer la quête {0} ?' closeInventory: '§7Êtes-vous sûr de vouloir fermer l''interface graphique ?' @@ -753,6 +763,7 @@ misc: breedAnimals: Faire reproduire des animaux tameAnimals: Apprivoiser des animaux die: Mourir + dealDamage: Infliger des dégâts comparison: equals: égal à {0} different: différent de {0} @@ -775,6 +786,7 @@ misc: quest: '§aQuête requise' mcMMOSkillLevel: '§dNiveau de compétence requis' money: '§dMonnaie requise' + equipment: '§eÉquipement requis' bucket: water: Seau d'eau lava: Seau de lave From 590047081bd59a8b5ed52ff71aa672c4c81a1a06 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Tue, 6 Sep 2022 16:35:09 +0200 Subject: [PATCH 064/111] :sparkles: Added "eat or drink food or potions" stage --- .../quests/gui/creation/stages/StagesGUI.java | 2 + .../requirements/EquipmentRequirement.java | 3 +- .../skytasul/quests/stages/StageEatDrink.java | 62 +++++++++++++++++++ .../java/fr/skytasul/quests/utils/Lang.java | 5 ++ core/src/main/resources/locales/en_US.yml | 5 ++ core/src/main/resources/locales/fr_FR.yml | 5 ++ 6 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/fr/skytasul/quests/stages/StageEatDrink.java diff --git a/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java b/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java index 6205ba29..e3633b7e 100644 --- a/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java +++ b/core/src/main/java/fr/skytasul/quests/gui/creation/stages/StagesGUI.java @@ -328,6 +328,7 @@ private void editBranch(QuestBranch branch){ private static final ItemStack stageTame = ItemUtils.item(XMaterial.CARROT, Lang.stageTameAnimals.toString()); private static final ItemStack stageDeath = ItemUtils.item(XMaterial.SKELETON_SKULL, Lang.stageDeath.toString()); private static final ItemStack stageDealDamage = ItemUtils.item(XMaterial.REDSTONE, Lang.stageDealDamage.toString()); + private static final ItemStack stageEatDrink = ItemUtils.item(XMaterial.COOKED_PORKCHOP, Lang.stageEatDrink.toString()); public static void initialize(){ DebugUtils.logMessage("Initlializing default stage types."); @@ -350,5 +351,6 @@ public static void initialize(){ QuestsAPI.getStages().register(new StageType<>("TAME", StageTame.class, Lang.Tame.name(), StageTame::deserialize, stageTame, StageTame.Creator::new)); QuestsAPI.getStages().register(new StageType<>("DEATH", StageDeath.class, Lang.Death.name(), StageDeath::deserialize, stageDeath, StageDeath.Creator::new)); QuestsAPI.getStages().register(new StageType<>("DEAL_DAMAGE", StageDealDamage.class, Lang.DealDamage.name(), StageDealDamage::deserialize, stageDealDamage, StageDealDamage.Creator::new)); + QuestsAPI.getStages().register(new StageType<>("EAT_DRINK", StageEatDrink.class, Lang.EatDrink.name(), StageEatDrink::new, stageEatDrink, StageEatDrink.Creator::new)); } } \ No newline at end of file diff --git a/core/src/main/java/fr/skytasul/quests/requirements/EquipmentRequirement.java b/core/src/main/java/fr/skytasul/quests/requirements/EquipmentRequirement.java index ac2a4e4a..d6ea7c5b 100644 --- a/core/src/main/java/fr/skytasul/quests/requirements/EquipmentRequirement.java +++ b/core/src/main/java/fr/skytasul/quests/requirements/EquipmentRequirement.java @@ -20,7 +20,6 @@ import fr.skytasul.quests.gui.misc.ItemGUI; import fr.skytasul.quests.gui.templates.StaticPagedGUI; import fr.skytasul.quests.utils.Lang; -import fr.skytasul.quests.utils.MinecraftNames; import fr.skytasul.quests.utils.XMaterial; public class EquipmentRequirement extends AbstractRequirement { @@ -51,7 +50,7 @@ public AbstractRequirement clone() { public String[] getLore() { if (slot == null) return null; return new String[] { - QuestOption.formatNullableValue(slot.name() + " > " + MinecraftNames.getMaterialName(item)), + QuestOption.formatNullableValue(slot.name() + " > " + ItemUtils.getName(item)), "", Lang.RemoveMid.toString() }; } diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageEatDrink.java b/core/src/main/java/fr/skytasul/quests/stages/StageEatDrink.java new file mode 100644 index 00000000..50a96201 --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/stages/StageEatDrink.java @@ -0,0 +1,62 @@ +package fr.skytasul.quests.stages; + +import java.util.Map; +import java.util.Map.Entry; + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.event.EventHandler; +import org.bukkit.event.player.PlayerItemConsumeEvent; +import org.bukkit.inventory.ItemStack; + +import fr.skytasul.quests.api.comparison.ItemComparisonMap; +import fr.skytasul.quests.api.stages.types.AbstractItemStage; +import fr.skytasul.quests.gui.ItemUtils; +import fr.skytasul.quests.gui.creation.stages.Line; +import fr.skytasul.quests.players.PlayerAccount; +import fr.skytasul.quests.players.PlayersManager; +import fr.skytasul.quests.structure.QuestBranch; +import fr.skytasul.quests.structure.QuestBranch.Source; +import fr.skytasul.quests.utils.Lang; +import fr.skytasul.quests.utils.XMaterial; + +public class StageEatDrink extends AbstractItemStage { + + public StageEatDrink(QuestBranch branch, Map> objects, ItemComparisonMap comparisons) { + super(branch, objects, comparisons); + } + + public StageEatDrink(ConfigurationSection section, QuestBranch branch) { + super(branch, section); + } + + @Override + protected String descriptionLine(PlayerAccount acc, Source source) { + return Lang.SCOREBOARD_EAT_DRINK.format(super.descriptionLine(acc, source)); + } + + @EventHandler + public void onItemConsume(PlayerItemConsumeEvent event) { + event(PlayersManager.getPlayerAccount(event.getPlayer()), event.getPlayer(), event.getItem(), 1); + } + + public static class Creator extends AbstractItemStage.Creator { + + private static final ItemStack editItems = ItemUtils.item(XMaterial.COOKED_PORKCHOP, Lang.stageEatDrinkItems.toString()); + + public Creator(Line line, boolean ending) { + super(line, ending); + } + + @Override + protected ItemStack getEditItem() { + return editItems; + } + + @Override + protected StageEatDrink finishStage(QuestBranch branch, Map> itemsMap, ItemComparisonMap comparisons) { + return new StageEatDrink(branch, itemsMap, comparisons); + } + + } + +} diff --git a/core/src/main/java/fr/skytasul/quests/utils/Lang.java b/core/src/main/java/fr/skytasul/quests/utils/Lang.java index 305f3cba..f5b3c67d 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Lang.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Lang.java @@ -360,6 +360,7 @@ public enum Lang implements Locale { stageTameAnimals("inv.create.tameAnimals"), stageDeath("inv.create.death"), stageDealDamage("inv.create.dealDamage"), + stageEatDrink("inv.create.eatDrink"), stageText("inv.create.NPCText"), dialogLines("inv.create.dialogLines"), // 0: lines stageNPCSelect("inv.create.NPCSelect"), @@ -405,6 +406,8 @@ public enum Lang implements Locale { stageDealDamageValue("inv.create.stage.dealDamage.damage"), stageDealDamageMobs("inv.create.stage.dealDamage.targetMobs"), + stageEatDrinkItems("inv.create.stage.eatDrink.items"), + INVENTORY_STAGES("inv.stages.name"), nextPage("inv.stages.nextPage"), laterPage("inv.stages.laterPage"), @@ -722,6 +725,7 @@ public enum Lang implements Locale { SCOREBOARD_DIE("scoreboard.stage.die"), SCOREBOARD_DEAL_DAMAGE_ANY("scoreboard.stage.dealDamage.any"), // 0: damage SCOREBOARD_DEAL_DAMAGE_MOBS("scoreboard.stage.dealDamage.mobs"), // 0: damage, 1: mobs + SCOREBOARD_EAT_DRINK("scoreboard.stage.eatDrink"), // 0: items /* Indications */ @@ -770,6 +774,7 @@ public enum Lang implements Locale { Tame("misc.stageType.tameAnimals"), Death("misc.stageType.die"), DealDamage("misc.stageType.dealDamage"), + EatDrink("misc.stageType.eatDrink"), ComparisonEquals("misc.comparison.equals"), ComparisonDifferent("misc.comparison.different"), diff --git a/core/src/main/resources/locales/en_US.yml b/core/src/main/resources/locales/en_US.yml index e9381e84..da42eb2c 100644 --- a/core/src/main/resources/locales/en_US.yml +++ b/core/src/main/resources/locales/en_US.yml @@ -372,6 +372,7 @@ inv: tameAnimals: §aTame animals death: §cDie dealDamage: §cDeal damage to mobs + eatDrink: §aEat or drink food or potions NPCText: §eEdit dialog dialogLines: '{0} lines' NPCSelect: §eChoose or create NPC @@ -417,6 +418,8 @@ inv: dealDamage: damage: '§eDamage to deal' targetMobs: '§cMobs to damage' + eatDrink: + items: '§eEdit items to eat or drink' stages: name: Create stages nextPage: §eNext page @@ -754,6 +757,7 @@ scoreboard: dealDamage: any: §cDeal {0} damage mobs: §cDeal {0} damage to {1} + eatDrink: §eConsume §6{0} indication: startQuest: §7Do you want to start the quest {0}? closeInventory: §7Are you sure you want to close the GUI? @@ -808,6 +812,7 @@ misc: tameAnimals: Tame animals die: Die dealDamage: Deal damage + eatDrink: Eat or drink comparison: equals: equal to {0} different: different to {0} diff --git a/core/src/main/resources/locales/fr_FR.yml b/core/src/main/resources/locales/fr_FR.yml index 37af6512..b3db015c 100644 --- a/core/src/main/resources/locales/fr_FR.yml +++ b/core/src/main/resources/locales/fr_FR.yml @@ -340,6 +340,7 @@ inv: tameAnimals: '§aApprivoiser des animaux' death: '§cMourir' dealDamage: '§cInfliger des dégâts à des mobs' + eatDrink: '§aManger ou boire de la nourriture ou des potions' NPCText: '§eModifier le dialogue' dialogLines: '{0} lignes' NPCSelect: '§eChoisir ou créer un NPC' @@ -385,6 +386,8 @@ inv: dealDamage: damage: '§eDégâts à infliger' targetMobs: '§cMobs à heurter' + eatDrink: + items: '§eModifier les objets à manger ou à boire' stages: name: Créer les étapes nextPage: '§ePage suivante' @@ -715,6 +718,7 @@ scoreboard: dealDamage: any: '§cInflige {0} dégâts' mobs: '§cInflige {0} dégâts à {1}' + eatDrink: '§eConsomme §6{0}' indication: startQuest: '§7Voulez-vous commencer la quête {0} ?' closeInventory: '§7Êtes-vous sûr de vouloir fermer l''interface graphique ?' @@ -764,6 +768,7 @@ misc: tameAnimals: Apprivoiser des animaux die: Mourir dealDamage: Infliger des dégâts + eatDrink: Manger ou boire comparison: equals: égal à {0} different: différent de {0} From f40554152531cbbccb1727dd5212b80d1b326656 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Wed, 7 Sep 2022 21:58:08 +0200 Subject: [PATCH 065/111] :card_file_box: Additional player account datas are now reset upon "/quests resetPlayer" call --- .../quests/api/data/SQLDataSaver.java | 8 +++++++- .../skytasul/quests/api/data/SavableData.java | 2 +- .../commands/CommandsPlayerManagement.java | 1 + .../quests/players/PlayerAccount.java | 4 ++++ .../quests/players/PlayersManagerDB.java | 20 +++++++++++++++++++ 5 files changed, 33 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/api/data/SQLDataSaver.java b/core/src/main/java/fr/skytasul/quests/api/data/SQLDataSaver.java index dade3e87..aa121302 100644 --- a/core/src/main/java/fr/skytasul/quests/api/data/SQLDataSaver.java +++ b/core/src/main/java/fr/skytasul/quests/api/data/SQLDataSaver.java @@ -62,6 +62,7 @@ public Object convert(Character obj) { private final SQLType sqlType; private final String updateStatement; private final String columnDefinition; + private final String defaultValueString; public SQLDataSaver(SavableData wrappedData, String updateStatement) { this.wrappedData = wrappedData; @@ -79,7 +80,8 @@ public SQLDataSaver(SavableData wrappedData, String updateStatement) { } } - columnDefinition = String.format("`%s` %s%s DEFAULT %s", wrappedData.getColumnName(), sqlType.sqlTypeName, length, Objects.toString(wrappedData.getDefaultValue())); + defaultValueString = Objects.toString(wrappedData.getDefaultValue()); + columnDefinition = String.format("`%s` %s%s DEFAULT %s", wrappedData.getColumnName(), sqlType.sqlTypeName, length, defaultValueString); } public SavableData getWrappedData() { @@ -94,6 +96,10 @@ public String getColumnDefinition() { return columnDefinition; } + public String getDefaultValueString() { + return defaultValueString; + } + public void setInStatement(PreparedStatement statement, int index, T value) throws SQLException { statement.setObject(index, sqlType.convert(value), sqlType.jdbcTypeCode); } diff --git a/core/src/main/java/fr/skytasul/quests/api/data/SavableData.java b/core/src/main/java/fr/skytasul/quests/api/data/SavableData.java index 3fca300f..c9ea7839 100644 --- a/core/src/main/java/fr/skytasul/quests/api/data/SavableData.java +++ b/core/src/main/java/fr/skytasul/quests/api/data/SavableData.java @@ -3,7 +3,7 @@ import java.util.Objects; import java.util.OptionalInt; -public class SavableData { +public final class SavableData { private final String id; private final Class dataType; diff --git a/core/src/main/java/fr/skytasul/quests/commands/CommandsPlayerManagement.java b/core/src/main/java/fr/skytasul/quests/commands/CommandsPlayerManagement.java index 2d94fd0b..982a6d66 100644 --- a/core/src/main/java/fr/skytasul/quests/commands/CommandsPlayerManagement.java +++ b/core/src/main/java/fr/skytasul/quests/commands/CommandsPlayerManagement.java @@ -190,6 +190,7 @@ public void resetPlayer(BukkitCommandActor actor, EntitySelector players }else acc.removePoolDatas(poolDatas.getPoolID()); pools++; } + acc.resetDatas(); Bukkit.getPluginManager().callEvent(new PlayerAccountResetEvent(player, acc)); if (acc.isCurrent()) Lang.DATA_REMOVED.send(player, quests, actor.getName(), pools); Lang.DATA_REMOVED_INFO.send(actor.getSender(), quests, player.getName(), pools); diff --git a/core/src/main/java/fr/skytasul/quests/players/PlayerAccount.java b/core/src/main/java/fr/skytasul/quests/players/PlayerAccount.java index f516bb9f..a88e3665 100644 --- a/core/src/main/java/fr/skytasul/quests/players/PlayerAccount.java +++ b/core/src/main/java/fr/skytasul/quests/players/PlayerAccount.java @@ -126,6 +126,10 @@ public void setData(SavableData data, T value) { additionalDatas.put(data, value); } + public void resetDatas() { + additionalDatas.clear(); + } + @Override public boolean equals(Object arg0) { if (arg0 == this) return true; diff --git a/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java b/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java index 1d54a8ab..245a3616 100644 --- a/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java +++ b/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java @@ -44,6 +44,7 @@ public class PlayersManagerDB extends PlayersManager { private final Map, SQLDataSaver> accountDatas = new HashMap<>(); private String getAccountDatas; + private String resetAccountDatas; /* Accounts statements */ private String getAccountsIDs; @@ -93,6 +94,10 @@ public void addAccountData(SavableData data) { .stream() .map(x -> "`" + x.getColumnName() + "`") .collect(Collectors.joining(", ", "SELECT ", " FROM " + ACCOUNTS_TABLE + " WHERE `id` = ?")); + resetAccountDatas = accountDatas.values() + .stream() + .map(x -> "`" + x.getWrappedData().getColumnName() + "` = " + x.getDefaultValueString()) + .collect(Collectors.joining(", ", "UPDATE " + ACCOUNTS_TABLE + " SET ", " WHERE `id` = ?")); } private synchronized void retrievePlayerDatas(PlayerAccount acc) { @@ -717,6 +722,21 @@ public void setData(SavableData data, T value) { } } + @Override + public void resetDatas() { + super.resetDatas(); + + if (resetAccountDatas != null) { + try (Connection connection = db.getConnection(); + PreparedStatement statement = connection.prepareStatement(resetAccountDatas)) { + statement.setInt(1, index); + statement.executeUpdate(); + }catch (SQLException ex) { + BeautyQuests.logger.severe("An error occurred while resetting account " + index + " datas from database", ex); + } + } + } + } } From f1b04bfe6b92d081d0dc4f486ae2a4b47b5e8314 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Wed, 7 Sep 2022 22:27:58 +0200 Subject: [PATCH 066/111] :globe_with_meridians: Fixed Spanish, updated Chinese locales --- core/src/main/resources/locales/es_ES.yml | 2 - core/src/main/resources/locales/zh_CN.yml | 63 +++++++++++++++++++++-- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/core/src/main/resources/locales/es_ES.yml b/core/src/main/resources/locales/es_ES.yml index cffdde5d..01ee910b 100644 --- a/core/src/main/resources/locales/es_ES.yml +++ b/core/src/main/resources/locales/es_ES.yml @@ -621,8 +621,6 @@ inv: poolRedo: '§8Puede rehacer misiones completadas: §7{0}' poolTime: '§8Tiempo entre misiones: §7{0}' poolHologram: '§8Texto del Holograma: §7{0}' - poolAvoidDuplicates: "citizens.changed-implementation=Citizens implementation changed, disabling plugin.\ncitizens.commands.citizens.save.help=Use the -a flag to save async (off the main server thread).\ncitizens.commands.console-error=Please report this error: [See console]\ncitizens.commands.errors.missing-world=World not found.\ncitizens.commands.help.command-missing=Command /{0} not found.\ncitizens.commands.help.header=Help\ncitizens.commands.id-not-found=Couldn''t find any NPC with ID [[{0}]].\ncitizens.commands.invalid.class=Invalid external commands class.\ncitizens.commands.invalid-mobtype=[[{0}]] is not a valid mobtype.\ncitizens.commands.invalid-number=That is not a valid number.\ncitizens.commands.npc.ai.started=Now using Minecraft AI.\ncitizens.commands.npc.ai.stopped=No longer using Minecraft AI.\ncitizens.commands.npc.age.cannot-be-aged=The mob type {0} cannot be aged.\ncitizens.commands.npc.age.help=Can only be used on entities that can become babies. Use the [[-l]] flag to lock age over time (note: relogs may be required to see this).\ncitizens.commands.npc.age.invalid-age=Invalid age. Valid ages are adult, baby, number between -24000 and 0\ncitizens.commands.npc.age.locked=Age locked.\ncitizens.commands.npc.age.set-adult=[[{0}]] is now an adult.\ncitizens.commands.npc.age.set-baby=[[{0}]] is now a baby.\ncitizens.commands.npc.age.set-normal=[[{0}]] is now age [[{1}]].\ncitizens.commands.npc.age.set=[[{0}]] is now [[{1}]].\ncitizens.commands.npc.age.unlocked=Age unlocked.\ncitizens.commands.npc.anchor.added=Anchor added.\ncitizens.commands.npc.anchor.already-exists=The anchor [[{0}]] already exists.\ncitizens.commands.npc.anchor.invalid-name=Invalid anchor name.\ncitizens.commands.npc.anchor.missing=The anchor [[{1}]] does not exist.\ncitizens.commands.npc.anchor.removed=Anchor removed.\ncitizens.commands.npc.bee.anger-set=Anger set to [[{0}]].\ncitizens.commands.npc.bee.has-nectar=[[{0}]] has nectar.\ncitizens.commands.npc.bee.no-nectar=[[{0}]] has no nectar.\ncitizens.commands.npc.bee.not-stung=[[{0}]] has no longer stung.\ncitizens.commands.npc.bee.bee-stung=[[{0}]] has now stung.\ncitizens.commands.npc.bee.invalid-anger=Anger should be above zero.\ncitizens.commands.npc.behaviour.added=Behaviours added.\ncitizens.commands.npc.behaviour.help=The scripts argument is a comma-separated list of file names. Scripts will be loaded automatically and run every tick. Use the [[-r]] flag to remove behaviours.\ncitizens.commands.npc.behaviour.removed=Behaviours removed.\ncitizens.commands.npc.cat.invalid-type=Invalid type specified. Valid types are [[{0}]].\ncitizens.commands.npc.cat.invalid-collar-color=Invalid collar color specified. Valid types are [[{0}]].\ncitizens.commands.npc.cat.collar-color-set=Collar color set to [[{0}]].\ncitizens.commands.npc.cat.sitting-start=[[{0}]] started sitting.\ncitizens.commands.npc.cat.sitting-stop=[[{0}]] stopped sitting.\ncitizens.commands.npc.cat.lying-start=[[{0}]] started lying down.\ncitizens.commands.npc.cat.lying-stop=[[{0}]] stopped lying down.\ncitizens.commands.npc.cat.type-set=Type set to [[{0}]].\ncitizens.commands.npc.collidable.set=[[{0}]] will now collide with entities.\ncitizens.commands.npc.collidable.unset=[[{0}]] will no longer collide with entities.\ncitizens.commands.npc.command.none-added=No commands have been added.\ncitizens.commands.npc.command.cost-set=Set cost per click to [[{0}]].\ncitizens.commands.npc.command.left-hand-header=Commands to run on [[left click]]:\ncitizens.commands.npc.command.right-hand-header=Commands to run on [[right click]]:\ncitizens.commands.npc.command.command-removed=Command [[{0}]] removed.\ncitizens.commands.npc.command.command-added=Command [[{0}]] added with id [[{1}]].\ncitizens.commands.npc.command.help=
Use the [[-l]] flag to make the command run on left click, [[-r]] on right click (default).
Set the per-player cooldown before the command can be used again using [[--cooldown]] (in [[seconds]]).
Set the server-wide cooldown in seconds using [[--gcooldown]].
[[--delay]] will wait the specified amount in [[ticks]] before executing the command.
[[--permissions]] will set the command to require specific permissions (separate multiple with commas).
[[--n]] will only let the player run the command that number of times.
Use [[-o]] to temporarily execute the command as an op and [[-p]] to run the command as the clicking player instead of the server.
To give the player temporary permissions instead of op, use [[/npc command permissions]].
Set the cost of each click with [[/npc command cost]].
Commands can be executed one by one instead of all at once by using [[/npc command sequential]].\ncitizens.commands.npc.command.unknown-id=Unknown command id [[{0}]] for this NPC.\ncitizens.commands.npc.command.temporary-permissions-set=Temporary permissions set to [[{0}]].\ncitizens.commands.npc.commands.sequential-set=Commands will now execute sequentially.\ncitizens.commands.npc.commands.sequential-unset=Commands will no longer execute sequentially.\ncitizens.commands.npc.commands.random-set=Commands will now execute at random.\ncitizens.commands.npc.commands.random-unset=Commands will no longer execute at random.\ncitizens.commands.npc.controllable.not-controllable=[[{0}]] is not controllable.\ncitizens.commands.npc.controllable.removed=[[{0}]] can no longer be controlled.\ncitizens.commands.npc.controllable.set=[[{0}]] can now be controlled.\ncitizens.commands.npc.copy.copied=[[{0}]] has been copied.\ncitizens.commands.npc.create.mobtype-missing=[[{0}]] is not supported as an NPC mobtype.\ncitizens.commands.npc.create.invalid-location=Spawn location could not be parsed or was not found.\ncitizens.commands.npc.create.invalid-mobtype=[[{0}]] is not a valid mob type.\ncitizens.commands.npc.create.npc-name-too-long=NPC names cannot be longer than [[{0}]] characters. The name has been shortened.\ncitizens.commands.npc.create.no-player-for-spawn=No player could be found by that name to spawn an NPC at.\ncitizens.commands.npc.despawn.despawned=You despawned [[{0}]].\ncitizens.commands.npc.endercrystal.showing-bottom=[[{0}]] is now showing its base.\ncitizens.commands.npc.endercrystal.not-showing-bottom=[[{0}]] is no longer showing its base.\ncitizens.commands.npc.enderman.angry-set=[[{0}]] is now angry.\ncitizens.commands.npc.enderman.angry-unset=[[{0}]] is no longer angry.\ncitizens.commands.npc.flyable.set=[[{0}]] is now flyable.\ncitizens.commands.npc.flyable.unset=[[{0}]] is no longer flyable.\ncitizens.commands.npc.fox.crouching-set=[[{0}]] is now crouching.\ncitizens.commands.npc.fox.crouching-unset=[[{0}]] is no longer crouching.\ncitizens.commands.npc.fox.sitting-set=[[{0}]] is now sitting.\ncitizens.commands.npc.fox.sitting-unset=[[{0}]] is no longer sitting.\ncitizens.commands.npc.fox.sleeping-set=[[{0}]] is now sleeping.\ncitizens.commands.npc.fox.sleeping-unset=[[{0}]] is no longer sleeping.\ncitizens.commands.npc.fox.fox-type-set=Fox type set to [[{0}]].\ncitizens.commands.npc.fox.invalid-fox-type=Invalid fox type specified. Valid types are [[{0}]].\ncitizens.commands.npc.follow.set=[[{0}]] is now following [[{1}]]. \ncitizens.commands.npc.follow.unset=[[{0}]] is no longer following anyone.\ncitizens.commands.npc.gamemode.describe={0}''s gamemode is [[{1}]].\ncitizens.commands.npc.gamemode.invalid={0} is not a valid gamemode.\ncitizens.commands.npc.gamemode.set=Gamemode set to [[{0}]].\ncitizens.commands.npc.gravity.disabled=Gravity [[disabled]].\ncitizens.commands.npc.gravity.enabled=Gravity [[enabled]].\ncitizens.commands.npc.glowing.set=[[{0}]] is now glowing.\ncitizens.commands.npc.glowing.player-only=Glowing color can only be changed for player-type NPCs.\ncitizens.commands.npc.glowing.unset=[[{0}]] is no longer glowing.\ncitizens.commands.npc.glowing.color-set=[[{0}]]''s glowing color set to {1}]].\ncitizens.commands.npc.guardian.elder-unset=[[{0}]] is no longer an elder guardian.\ncitizens.commands.npc.guardian.elder-set=[[{0}]] is now an elder guardian.\ncitizens.commands.npc.hologram.text-set=Set hologram text line [[{0}]] to [[{1}]].\ncitizens.commands.npc.hologram.text-removed=Removed hologram text.\ncitizens.commands.npc.hologram.text-missing=Missing text to add.\ncitizens.commands.npc.hologram.text-describe-header=[[{0}]]''s hologram text (in bottom-up order):\ncitizens.commands.npc.hologram.invalid-text-id=Invalid line number.\ncitizens.commands.npc.hologram.line-height-set=Line height set to [[{0}]].\ncitizens.commands.npc.hologram.line-removed=Removed line [[{0}]].\ncitizens.commands.npc.hologram.direction-set=Direction set to [[{0}]].\ncitizens.commands.npc.hologram.line-add=Added a new hologram line: [[{0}]].\ncitizens.commands.npc.hologram.cleared=Hologram lines cleared.\ncitizens.commands.npc.horse.chest-set=The horse is now carrying a chest.\ncitizens.commands.npc.horse.chest-unset=The horse is no longer carrying a chest.\ncitizens.commands.npc.horse.color-set=The horse''s color was set to [[{0}]].\ncitizens.commands.npc.horse.type-set=The horse''s type was set to [[{0}]].\ncitizens.commands.npc.horse.style-set=The horse''s style was set to [[{0}]].\ncitizens.commands.npc.horse.describe=The horse''s color is [[{0}]], the type is [[{1}]] and the style is [[{2}]].\ncitizens.commands.npc.horse.invalid-color=Invalid horse color given. Valid colors are: [[{0}]].\ncitizens.commands.npc.horse.invalid-type=Invalid horse type given. Valid types are: [[{0}]].\ncitizens.commands.npc.horse.invalid-style=Invalid horse style given. Valid styles are: [[{0}]].\ncitizens.commands.npc.item.item-set=NPC item set to [[{0}]].\ncitizens.commands.npc.item.unknown-material=Unknown material given.\ncitizens.commands.npc.leashable.set=[[{0}]] is now leashable.\ncitizens.commands.npc.leashable.stopped=[[{0}]] is no longer leashable.\ncitizens.commands.npc.llama.strength-set=Llama strength set to [[{0}]].\ncitizens.commands.npc.llama.color-set=Llama color set to [[{0}]].\ncitizens.commands.npc.llama.invalid-color=Invalid llama color given. Valid colors are: [[{0}]].\ncitizens.commands.npc.lookclose.random-look-delay-set=[[{0}]] will now randomly look every [[{1}]] ticks.\ncitizens.commands.npc.lookclose.random-pitch-range-set=[[{0}]] will now randomly choose a pitch in the range [{1}].\ncitizens.commands.npc.lookclose.random-yaw-range-set=[[{0}]] will now randomly choose a yaw in the range [{1}].\ncitizens.commands.npc.lookclose.random-set=[[{0}]] will now randomly look around.\ncitizens.commands.npc.lookclose.random-stopped=[[{0}]] will no longer randomly look around.\ncitizens.commands.npc.lookclose.error-random-range=Invalid range [[{0}]]. Use the format `min,max`.\ncitizens.commands.npc.lookclose.set=[[{0}]] will now rotate when players are nearby.\ncitizens.commands.npc.lookclose.stopped=[[{0}]] will no longer rotate when players are nearby.\ncitizens.commands.npc.metadata.set=[[{0}]] set to [[{1}]].\ncitizens.commands.npc.metadata.unset=Removed [[{{0}}]] from [[{1}]].\ncitizens.commands.npc.minecart.set=[[{0}]] now has item [[{1}]]:[[{2}]] with offset [[{3}]].\ncitizens.commands.npc.mount.failed=Couldn''t mount [[{0}]].\ncitizens.commands.npc.mount.must-be-spawned=Couldn''t mount [[{0}]]. Make sure that the destination NPC ID is correct and it is spawned.\ncitizens.commands.npc.moveto.format=Format is x:y:z(:world) or x y z( world).\ncitizens.commands.npc.mount.mount-on-itself=Can''t mount NPC on itself.\ncitizens.commands.npc.moveto.teleported=[[{0}]] teleported to [[{1}]].\ncitizens.commands.npc.mushroomcow.invalid-variant=Invalid variant. Valid values are: [[{0}]].\ncitizens.commands.npc.mushroomcow.variant-set=[[{0}]]''s variant set to [[{1}]].\ncitizens.commands.npc.nameplate.toggled=Nameplate visibility toggled.\ncitizens.commands.npc.ocelot.deprecated=Ocelot types don''t work in this version of Minecraft. Use [[/npc type cat]] if you want to change the colour.\ncitizens.commands.npc.ocelot.invalid-type=Invalid ocelot type. Valid types are: [[{0}]].\ncitizens.commands.npc.owner.already-owner={0} is already the owner of [[{1}]].\ncitizens.commands.npc.owner.owner=[[{0}]]''s owner is [[{1}]].\ncitizens.commands.npc.owner.set-server=[[The server]] is now the owner of [[{0}]].\ncitizens.commands.npc.owner.set=[[{1}]] is now the owner of [[{0}]].\ncitizens.commands.npc.panda.invalid-gene=Invalid gene. Valid genes are [[{0}]].\ncitizens.commands.npc.panda.sitting=Started sitting.\ncitizens.commands.npc.panda.stopped-sitting=Stopped sitting.\ncitizens.commands.npc.panda.main-gene-set=Main gene set to [[{0}]].\ncitizens.commands.npc.panda.hidden-gene-set=Hidden gene set to [[{0}]].\ncitizens.commands.npc.passive.set=[[{0}]] will no longer damage entities.\ncitizens.commands.npc.passive.unset=[[{0}]] will now damage entities.\ncitizens.commands.npc.pathfindingrange.set=Pathfinding range set to [[{0}]].\ncitizens.commands.npc.parrot.invalid-variant=Invalid parrot variant. Valid variants are [[{0}]].\ncitizens.commands.npc.parrot.variant-set=Variant set to [[{0}]].\ncitizens.commands.npc.pathopt.avoid-water-set=[[{0}]] will now avoid water.\ncitizens.commands.npc.pathopt.avoid-water-unset=[[{0}]] will no longer avoid water.\ncitizens.commands.npc.pathopt.stationary-ticks-set=[[{0}]]''s maximum stationary ticks set to [[{1}]].\ncitizens.commands.npc.pathopt.distance-margin-set=[[{0}]]''s pathfinding distance margin set to [[{1}]].\ncitizens.commands.npc.pathopt.path-distance-margin-set=[[{0}]]''s pathfinding path distance margin set to [[{1}]].\ncitizens.commands.npc.pathopt.attack-range-set=[[{0}]]''s attack range set to [[{1}]].\ncitizens.commands.npc.pathopt.use-new-finder=[[{0}]]''s use new finder set to [[{1}]].\ncitizens.commands.npc.playerlist.added=Added [[{0}]] to the player list.\ncitizens.commands.npc.playerlist.removed=Removed [[{0}]] from the player list. \ncitizens.commands.npc.polarbear.rearing-set=[[{0}]] is now rearing.\ncitizens.commands.npc.polarbear.rearing-unset=[[{0}]] is no longer rearing.\ncitizens.commands.npc.pose.added=Pose added.\ncitizens.commands.npc.pose.already-exists=The pose [[{0}]] already exists.\ncitizens.commands.npc.pose.invalid-name=Invalid pose name.\ncitizens.commands.npc.pose.missing=The pose [[{0}]] does not exist.\ncitizens.commands.npc.pose.default-pose-set=Default pose set to [[{0}]].\ncitizens.commands.npc.pose.removed=Pose removed.\ncitizens.commands.npc.powered.set=[[{0}]] will now be powered.\ncitizens.commands.npc.powered.stopped=[[{0}]] will no longer be powered.\ncitizens.commands.npc.phantom.phantom-set=Size set to [[{0}]].\ncitizens.commands.npc.tropicalfish.invalid-color=Invalid fish color. Valid colors are: [[{0}]]\ncitizens.commands.npc.tropicalfish.invalid-pattern=Invalid fish pattern. Valid patterns are: [[{0}]]\ncitizens.commands.npc.tropicalfish.body-color-set=Body color set to [[{0}]].\ncitizens.commands.npc.tropicalfish.pattern-color-set=Pattern color set to [[{0}]].\ncitizens.commands.npc.tropicalfish.pattern-set=Pattern set to [[{0}]].\ncitizens.commands.npc.profession.invalid-profession=[[{0}]] is not a valid profession. Try one of the following: [[{1}]].\ncitizens.commands.npc.profession.set=[[{0}]] is now a [[{1}]].\ncitizens.commands.npc.pufferfish.state-set=State set to [[{0}]].\ncitizens.commands.npc.rabbittype.invalid-type=Invalid rabbit type. Try one of the following: [[{0}]].\ncitizens.commands.npc.rabbittype.type-set=[[{0}]]''s rabbit type has been set to [[{1}]]\ncitizens.commands.npc.remove.npcs-removed=NPCs removed.\ncitizens.commands.npc.remove.incorrect-syntax=Incorrect syntax. /npc remove (all)\ncitizens.commands.npc.remove.removed-all=You permanently removed all NPCs.\ncitizens.commands.npc.remove.removed=You permanently removed [[{0}]].\ncitizens.commands.npc.rename.renamed=You renamed [[{0}]] to [[{1}]].\ncitizens.commands.npc.respawn.delay-set=Respawn delay set to [[{0}]].\ncitizens.commands.npc.respawn.describe=Respawn delay is currently [[{0}]].\ncitizens.commands.npc.select.already-selected=You already have that NPC selected.\ncitizens.commands.npc.script.invalid-file=Unknown or unavailable script ''[[{0}]]''.\ncitizens.commands.npc.scoreboard.added-tags=Added these tags: [[{0}]].\ncitizens.commands.npc.scoreboard.removed-tags=Removed these tags: [[{0}]].\ncitizens.commands.npc.sheep.color-set=The sheep''s color was set to [[{0}]].\ncitizens.commands.npc.sheep.invalid-color=Invalid sheep color given. Valid colors are: [[{0}]].\ncitizens.commands.npc.script.current-scripts=[[{0}]]''s current scripts are [[{1}]].\ncitizens.commands.npc.shulker.peek-set=[[{0}]]''s peek amount set to [[{1}]].\ncitizens.commands.npc.shulker.color-set=[[{0}]]''s color set to [[{1}]].\ncitizens.commands.npc.shulker.invalid-color=Invalid shulker color given. Valid colors are: [[{0}]].\ncitizens.commands.npc.skin.error-setting-url=Error downloading skin texture from [[{0}]]. Are you sure the URL is valid?\ncitizens.commands.npc.skin.skin-url-set=Downloaded [[{0}]]''s skin from [[{1}]].\ncitizens.commands.npc.skin.set=[[{0}]]''s skin name set to [[{1}]].\ncitizens.commands.npc.skin.missing-skin=A skin name is required.\ncitizens.commands.npc.skin.cleared=[[{0}]]''s skin name was cleared.\ncitizens.commands.npc.skin.layers-set=[[{0}]]''s skin layers: cape - [[{1}]], hat - [[{2}]], jacket - [[{3}]], sleeves - [[{4}]], pants - [[{5}]].\ncitizens.commands.npc.size.description=[[{0}]]''s size is [[{1}]].\ncitizens.commands.npc.size.set=[[{0}]]''s size set to [[{1}]].\ncitizens.commands.npc.sound.invalid-sound=Invalid sound.\ncitizens.commands.npc.sound.set=[[{0}]]''s sounds are now: ambient - [[{1}]] hurt - [[{2}]] and death - [[{3}]].\ncitizens.commands.npc.sound.info=[[{0}]]''s sounds are: ambient - [[{1}]] hurt - [[{2}]] and death - [[{3}]].

Valid sounds are {4}.\ncitizens.commands.npc.skeletontype.set={0}''s skeleton type set to [[{1}]].\ncitizens.commands.npc.skeletontype.invalid-type=Invalid skeleton type. Try one of the following: [[{0}]].\ncitizens.commands.npc.snowman.derp-set=[[{0}]] is now in derp mode.\ncitizens.commands.npc.snowman.derp-stopped=[[{0}]] is no longer in derp mode.\ncitizens.commands.npc.spawn.already-spawned=[[{0}]] is already spawned at another location. Use ''/npc tphere'' to teleport the NPC to your location.\ncitizens.commands.npc.spawn.missing-npc-id=No NPC with the ID {0} exists.\ncitizens.commands.npc.spawn.no-location=No stored location available - command must be used ingame.\ncitizens.commands.npc.spawn.numeric-id-only=Only numeric IDs can be used for /npc spawn.\ncitizens.commands.npc.spawn.spawned=You spawned [[{0}]].\ncitizens.commands.npc.speed.modifier-above-limit=Speed is above the limit.\ncitizens.commands.npc.speed.set=NPC speed modifier set to [[{0}]].\ncitizens.commands.npc.swim.set=[[{0}]] will now swim in water.\ncitizens.commands.npc.swim.unset=[[{0}]] will no longer swim in water.\ncitizens.commands.npc.targetable.set=[[{0}]] can now be targeted by mobs.\ncitizens.commands.npc.targetable.unset=[[{0}]] can no longer be targeted by mobs.\ncitizens.commands.npc.tp.teleported=You teleported to [[{0}]].\ncitizens.commands.npc.tp.location-not-found=Couldn''t find the target NPC''s location.\ncitizens.commands.npc.tpto.success=Teleported successfully.\ncitizens.commands.npc.tpto.to-not-found=Destination entity not found.\ncitizens.commands.npc.tpto.from-not-found=Source entity not found.\ncitizens.commands.npc.tphere.missing-cursor-block=Please look at a block to teleport to.\ncitizens.commands.npc.tphere.teleported=[[{0}]] was teleported to {1}.\ncitizens.commands.npc.type.set=[[{0}]]''s type set to [[{1}]].\ncitizens.commands.npc.type.invalid=[[{0}]] is not a valid type.\ncitizens.commands.npc.vulnerable.set=[[{0}]] is now vulnerable.\ncitizens.commands.npc.vulnerable.stopped=[[{0}]] is no longer vulnerable.\ncitizens.commands.npc.wolf.unknown-collar-color=[[{0}]] is not an RGB-formatted collar color or the name of a DyeColor.\ncitizens.commands.npc.wolf.collar-color-unsupported=[[{0}]] is not a RGB color code that can be used on a wolf''s collar.\ncitizens.commands.npc.villager.level-set=Level set to [[{0}]].\ncitizens.commands.npc.villager.invalid-type=Invalid villager type. Valid types are: [[{0}]].\ncitizens.commands.npc.villager.type-set=Type set to [[{0}]].\ncitizens.commands.npc.zombiemod.villager-set=[[{0}]] is now a villager.\ncitizens.commands.npc.zombiemod.villager-unset=[[{0}]] is no longer a villager.\ncitizens.commands.npc.zombiemod.baby-set=[[{0}]] is now a baby.\ncitizens.commands.npc.zombiemod.baby-unset=[[{0}]] is no longer a baby.\ncitizens.commands.npc.zombiemod.villager-profession-set=[[{0}]]''s profession set to [[{1}]].\ncitizens.commands.page-missing=The page [[{0}]] does not exist.\ncitizens.commands.requirements.disallowed-mobtype=The NPC cannot be the mob type [[{0}]] for that command.\ncitizens.commands.requirements.living-entity=The NPC must be a living entity.\ncitizens.commands.requirements.missing-permission=You don''t have permission to execute that command.\ncitizens.commands.requirements.missing-required-trait=Missing required trait [[{0}]].\ncitizens.commands.requirements.must-be-ingame=You must be ingame to use that command.\ncitizens.commands.requirements.must-be-owner=You must be the owner of this NPC to execute that command.\ncitizens.commands.requirements.must-have-selected=You must have an NPC selected to execute that command.\ncitizens.commands.requirements.too-few-arguments=Too few arguments.\ncitizens.commands.requirements.too-many-arguments=Too many arguments.\ncitizens.commands.script.compiled=Script compiled.\ncitizens.commands.script.compiling=Script compiling...\ncitizens.commands.script.file-missing=The file [[{0}]] doesn''t exist!\ncitizens.commands.template.applied=Template applied to [[{0}]] NPCs.\ncitizens.commands.template.conflict=A template by that name already exists.\ncitizens.commands.template.created=Template created.\ncitizens.commands.template.missing=Template not found.\ncitizens.commands.template.delete.deleted=Deleted template [[{0}]].\ncitizens.commands.template.list.header=]]Available templates[[:]]\ncitizens.commands.trait.added=Added {0} successfully.\ncitizens.commands.trait.failed-to-add=<7>Couldn''t add {0}.\ncitizens.commands.trait.failed-to-change=<7>Couldn''t change {0}.\ncitizens.commands.trait.failed-to-remove=<7>Couldn''t remove {0}.\ncitizens.commands.trait.removed=Removed {0} successfully.\ncitizens.commands.traitc.missing=Trait not found.\ncitizens.commands.traitc.not-configurable=That trait is not configurable.\ncitizens.commands.traitc.not-on-npc=The NPC doesn''t have that trait.\ncitizens.commands.unknown-command=Unknown command. Did you mean:\ncitizens.commands.waypoints.add.waypoint-added=Added waypoint at [[{0}]] (index [[{1}]]).\ncitizens.commands.waypoints.disableteleporting.disabled=[[{0}]] will no longer teleport when stuck pathfinding.\ncitizens.commands.waypoints.opendoors.enabled=[[{0}]] will now open doors while pathfinding.\ncitizens.commands.waypoints.opendoors.disabled=[[{0}]] will no longer doors while pathfinding.\ncitizens.commands.wolf.traits-updated=[[{0}]]''s Traits were updated. Angry:[[{1}]], Sitting:[[{2}]], Tamed:[[{3}]], Collar Color:[[{4}]] \ncitizens.conversations.selection.invalid-choice=[[{0}]] is not a valid option.\ncitizens.economy.error-loading=Vault not found -> no economy handling.\ncitizens.economy.minimum-cost-required=Need at least [[{0}]].\ncitizens.economy.money-withdrawn=Withdrew [[{0}]] for your NPC.\ncitizens.editors.already-in-editor=You''re already in an editor!\ncitizens.editors.copier.begin=Entered the NPC copier!
Click anywhere to copy the currently selected NPC.\ncitizens.editors.copier.end=Exited the NPC copier.\ncitizens.editors.equipment.all-items-removed=[[{0}]] had all of its items removed.\ncitizens.editors.equipment.begin=Entered the equipment editor!
[[Right click]] to equip the NPC or [[crouch right click]] to change the item in hand!
Type [[offhand]], [[chestplate]], [[helmet]], etc. in chat to equip specific slots with the item you''re holding!\ncitizens.editors.equipment.end=Exited the equipment editor.\ncitizens.editors.equipment.invalid-block=Invalid block!\ncitizens.editors.equipment.saddled-set=[[{0}]] is now saddled.\ncitizens.editors.equipment.saddled-stopped=[[{0}]] is no longer saddled.\ncitizens.editors.equipment.sheared-set=[[{0}]] is now sheared.\ncitizens.editors.equipment.sheared-stopped=[[{0}]] is no longer sheared.\ncitizens.editors.equipment.sheep-coloured=[[{0}]] is now coloured [[{1}]].\ncitizens.editors.selection.start-prompt=There were multiple NPCs with the supplied name.
Please enter an id or number from the list below to select that NPC.\ncitizens.editors.text.add-prompt=Enter text to add to the NPC.\ncitizens.editors.text.added-entry=[[Added]] the entry [[{0}]].\ncitizens.editors.text.begin=Entered the text editor! Type ''exit'' to leave the editor.\ncitizens.editors.text.change-page-prompt=Enter a page number to view more text entries.\ncitizens.editors.text.close-talker-set=[[Close talker]] set to [[{0}]].\ncitizens.editors.text.edit-begin-prompt=Enter the index of the entry you wish to edit or [[page]] to view more pages.\ncitizens.editors.text.edit-prompt=Enter text to edit the entry.\ncitizens.editors.text.edited-text=Changed entry at index [[{0}]] to [[{1}]].\ncitizens.editors.text.end=Exited the text editor.\ncitizens.editors.text.missing-item-set-pattern=Missing item in hand pattern.\ncitizens.editors.text.invalid-edit-type=Invalid edit type.\ncitizens.editors.text.invalid-index=[[{0}]] is not a valid index!\ncitizens.editors.text.invalid-input=Invalid input.\ncitizens.editors.text.invalid-page=Invalid page number.\ncitizens.editors.text.invalid-range=Invalid range.\ncitizens.editors.text.invalid-delay=Invalid delay.\ncitizens.editors.text.random-talker-set=[[Random talking]] set to [[{0}]].\ncitizens.editors.text.range-set=[[Range]] set to [[{0}]].\ncitizens.editors.text.delay-set=[[Delay]] set to [[{0}]] seconds.\ncitizens.editors.text.realistic-looking-set=[[Realistic looking]] set to [[{0}]].\ncitizens.editors.text.remove-prompt=Enter the index of the entry you wish to remove or [[page]] to view more pages.\ncitizens.editors.text.removed-entry=[[Removed]] entry at index [[{0}]].\ncitizens.editors.text.start-prompt=Type [[add]] to add an entry, [[edit]] to edit entries, [[remove]] to remove entries, [[close]] to toggle the NPC to send messages when players get close, [[item]] to set the talk item in hand pattern (set to [[default]] to clear), [[range]] to set the talking range, [[delay]] to set the talking delay in seconds and [[random]] to toggle the NPC as a random talker. Type [[help]] to show this again.\ncitizens.editors.text.talk-item-set=[[Talk item pattern]] set to [[{0}]].\ncitizens.editors.waypoints.wander.editing-regions-stop=Exited the region editor.\ncitizens.editors.waypoints.wander.worldguard-region-not-found=WorldGuard region not found.\ncitizens.editors.waypoints.wander.worldguard-region-set=WorldGuard region set to [[{0}]].\ncitizens.editors.waypoints.wander.range-set=Wander range set to xrange [[{0}]] and yrange [[{1}]].\ncitizens.editors.waypoints.wander.begin=Entered the wander waypoint editor.
Type [[xrange ]] or [[yrange ]] to modify the random wander range. Type [[regions]] to enter the region editor.
Type [[delay ]] to delay the NPC between wanders. Type [[worldguardregion ]] to restrict movement to a WorldGuard region.\ncitizens.editors.waypoints.wander.end=Exited the wander waypoint editor.\ncitizens.editors.waypoints.wander.delay-set=Delay between wanders set to [[{0}]] ticks.\ncitizens.editors.waypoints.wander.invalid-delay=Invalid delay specified.\ncitizens.editors.waypoints.wander.added-region=[[Added]] wanderable region at ({0}) ([[{1}]]).\ncitizens.editors.waypoints.wander.removed-region=[[Removed]] wanderable region at ({0}) ([[{1}]] remaining).\ncitizens.editors.waypoints.wander.editing-regions=Now editing regions!
[[Left click]] to add a new wanderable region using the xrange/yrange box centred at that block.
[[Right click]] an existing marker to remove that region.
Type [[regions]] to stop or simply exit the editor. Regions should be overlapping with each other.\ncitizens.editors.waypoints.guided.end=Exited the guided waypoint editor.\ncitizens.editors.waypoints.guided.begin=Entered the guided waypoint editor!
[[Left click]] to add a waypoint guide, [[right click]] an existing waypoint to remove.
[[Sneak]] while left clicking to add a destination waypoint.
Type [[toggle path]] to toggle showing entities at waypoints.\ncitizens.editors.waypoints.guided.added-guide=Added a [[guide]] waypoint. This will guide NPCs to their destination.\ncitizens.editors.waypoints.guided.added-available=Added a [[destination]] waypoint. This will be available for NPCs to path to.\ncitizens.editors.waypoints.guided.already-taken=There is already a waypoint here.\ncitizens.editors.waypoints.linear.added-waypoint=[[Added]] a waypoint at ({0}) ([[{1}]] total).\ncitizens.editors.waypoints.linear.begin==== Linear Waypoint Editor ===
[[Left click]] to add a waypoint, [[right click]] to remove it.
You can right click while sneaking to select and remove specific points.
Type [[markers]] to hide waypoints,
[[triggers]] to enter the trigger editor,
[[clear]] to clear all waypoints,
[[cycle]] to make NPCs cycle through waypoints instead of looping.\ncitizens.editors.waypoints.linear.selected-waypoint=Selected waypoint at {0}. Sneak + right click again to remove this waypoint.\ncitizens.editors.waypoints.linear.end=Exited the linear waypoint editor.\ncitizens.editors.waypoints.linear.not-showing-markers=[[Stopped]] showing waypoint markers.\ncitizens.editors.waypoints.linear.range-exceeded=Previous waypoint is {0} blocks away but the distance limit is {1}.\ncitizens.editors.waypoints.linear.removed-waypoint=[[Removed]] a waypoint ([[{0}]] remaining)\ncitizens.editors.waypoints.linear.showing-markers=[[Showing]] waypoint markers.\ncitizens.editors.waypoints.linear.waypoints-cleared=Waypoints cleared.\ncitizens.editors.waypoints.linear.cycle-set=Now [[cycling]] through waypoints.\ncitizens.editors.waypoints.linear.cycle-unset=Now [[looping]] through waypoints.\ncitizens.editors.waypoints.triggers.add.added=Added waypoint trigger successfully ({0}).\ncitizens.editors.waypoints.triggers.add.invalid-trigger=Couldn''t create a trigger by the name [[{0}]].\ncitizens.editors.waypoints.triggers.add.prompt=Enter in a trigger name to add or type [[back]] to return to the edit prompt. Valid trigger names are {0}.\ncitizens.editors.waypoints.triggers.animation.added=Animation [[{0}]] added.\ncitizens.editors.waypoints.triggers.list=Current triggers are:{0}\ncitizens.editors.waypoints.triggers.animation.prompt=Enter in animations to perform - valid animations are {0}.
Type in [[finish]] to finish the animation trigger or [[back]] to return to the previous prompt.\ncitizens.editors.waypoints.triggers.animation.invalid-animation=Invalid animation [[{0}]]. Valid animations are {1}.\ncitizens.editors.waypoints.triggers.chat.invalid-radius=The radius must be a number.\ncitizens.editors.waypoints.triggers.chat.missing-radius=No radius supplied.\ncitizens.editors.waypoints.triggers.chat.message-added=Message added: [[{0}]].\ncitizens.editors.waypoints.triggers.chat.radius-set=Radius set to [[{0}]] blocks.\ncitizens.editors.waypoints.triggers.chat.prompt=Enter in chat lines to say.
Type in [[radius (radius)]] to set the block radius to broadcast the messages.
Type [[finish]] to finish the chat trigger or [[back]] to return to the previous prompt.\ncitizens.editors.waypoints.triggers.delay.prompt=Enter the delay in [[server ticks]] to use. (20 ticks = 1 second)\ncitizens.editors.waypoints.triggers.main.missing-waypoint=Not editing a waypoint.\ncitizens.editors.waypoints.triggers.main.exit=Exited the waypoint trigger editor.\ncitizens.editors.waypoints.triggers.main.prompt=- Waypoint Trigger Editor -
Type [[add]] or [[remove]] to edit triggers.
Type [[triggers]] or [[exit]] to exit this editor.
Current triggers are:{0}\ncitizens.editors.waypoints.triggers.remove.index-out-of-range=Index must be in the range [[1-{0}]].\ncitizens.editors.waypoints.triggers.remove.not-a-number=Index must be a number.\ncitizens.editors.waypoints.triggers.remove.prompt=Enter in the index of the trigger to delete or [[back]] to return to the edit prompt. Current triggers are:{0}\ncitizens.editors.waypoints.triggers.remove.removed=Successfully removed trigger [[{0}]].\ncitizens.editors.waypoints.triggers.speed.prompt=Enter the speed modifier as a [[percentage]] of its base speed.\ncitizens.editors.waypoints.triggers.teleport.invalid-format=Invalid location given. Format is [[world]]:[[x]]:[[y]]:[[z]].\ncitizens.editors.waypoints.triggers.teleport.prompt=Enter the destination in the format world:x:y:z. Type [[here]] to use your current location. Type [[back]] to return to the edit prompt.\ncitizens.limits.over-npc-limit=Over the NPC limit of {0}.\ncitizens.load-task-error=NPC load task couldn''t be scheduled, disabling...\ncitizens.nms-errors.clearing-goals=Could not clear goals: {0}.\ncitizens.nms-errors.restoring-goals=Could not restore goals: {0}.\ncitizens.nms-errors.error-setting-persistent=Could not set NPC as persistent: {0}. NPC entity may despawn.\ncitizens.nms-errors.getting-field=Could not fetch NMS field {0}: [[{1}.\ncitizens.nms-errors.getting-method=Could not fetch NMS method {0}: [[{1}.\ncitizens.nms-errors.getting-id-mapping=Could not fetch entity id mapping fields: {0}.\ncitizens.nms-errors.spawning-custom-entity=Could not spawn custom entity: {0}.\ncitizens.nms-errors.stopping-network-threads=Could not stop network threads: {0}.\ncitizens.nms-errors.updating-land-modifier=Could not update land speed modifier: {0}.\ncitizens.nms-errors.updating-navigation-world=Could not update navigation world: {0}.\ncitizens.nms-errors.updating-pathfinding-range=Could not update pathfinding range: {0}.\ncitizens.notifications.database-connection-failed=Unable to connect to database, falling back to YAML\ncitizens.notifications.error-reloading=Error occured while reloading, see console.\ncitizens.notifications.exception-updating-npc=Exception while updating {0}: {1}.\ncitizens.notifications.incompatible-version=v{0} is not compatible with Minecraft v{1} - try upgrading or downgrading Citizens. Disabling.\ncitizens.notifications.locale=Using locale {0}.\ncitizens.notifications.metrics-load-error=Unable to start metrics: {0}.\ncitizens.notifications.missing-translations=Missing translations file for locale {0}. Defaulting to en locale.\ncitizens.notifications.npc-name-not-found=Could not find a name for ID {0}.\ncitizens.notifications.npc-not-found=No NPC could be found.\ncitizens.notifications.npcs-loaded=Loaded {0} NPCs.\ncitizens.notifications.reloaded=Citizens reloaded.\ncitizens.notifications.reloading=Reloading Citizens...\ncitizens.notifications.saved=Citizens saved.\ncitizens.notifications.saving=Saving Citizens...\ncitizens.notifications.skipping-broken-trait=Skipped broken or missing trait {0} while loading ID {1}. Has the name changed?\ncitizens.notifications.skipping-invalid-pose=Skipping pose {0} - invalid yaw/pitch ({1}).\ncitizens.notifications.trait-load-failed=The trait {0} failed to load for NPC ID: {1}.\ncitizens.notifications.trait-onspawn-failed=An exception occurred while the trait {0} was spawning for NPC ID {1}.\ncitizens.notifications.unknown-npc-type=NPC type {0} was not recognized. Did you spell it correctly?\ncitizens.saves.load-failed=Unable to load saves, disabling...\ncitizens.settings.writing-default=Writing default setting: {0}\ncitizens.sub-plugins.error-on-load={0} initializing {1}\ncitizens.sub-plugins.load=Loading {0}\ncitizens.traits.age-description={0}''s age is [[{1}]]. Locked is [[{2}]].\ncitizens.waypoints.available-providers-header=List of available providers\ncitizens.waypoints.current-provider=The current waypoint provider is [[{0}]].\ncitizens.waypoints.set-provider=Set the waypoint provider to [[{0}]]." - poolQuestsList: "citizens.changed-implementation=Citizens implementation changed, disabling plugin.\ncitizens.commands.citizens.save.help=Use the -a flag to save async (off the main server thread).\ncitizens.commands.console-error=Please report this error: [See console]\ncitizens.commands.errors.missing-world=World not found.\ncitizens.commands.help.command-missing=Command /{0} not found.\ncitizens.commands.help.header=Help\ncitizens.commands.id-not-found=Couldn''t find any NPC with ID [[{0}]].\ncitizens.commands.invalid.class=Invalid external commands class.\ncitizens.commands.invalid-mobtype=[[{0}]] is not a valid mobtype.\ncitizens.commands.invalid-number=That is not a valid number.\ncitizens.commands.npc.ai.started=Now using Minecraft AI.\ncitizens.commands.npc.ai.stopped=No longer using Minecraft AI.\ncitizens.commands.npc.age.cannot-be-aged=The mob type {0} cannot be aged.\ncitizens.commands.npc.age.help=Can only be used on entities that can become babies. Use the [[-l]] flag to lock age over time (note: relogs may be required to see this).\ncitizens.commands.npc.age.invalid-age=Invalid age. Valid ages are adult, baby, number between -24000 and 0\ncitizens.commands.npc.age.locked=Age locked.\ncitizens.commands.npc.age.set-adult=[[{0}]] is now an adult.\ncitizens.commands.npc.age.set-baby=[[{0}]] is now a baby.\ncitizens.commands.npc.age.set-normal=[[{0}]] is now age [[{1}]].\ncitizens.commands.npc.age.set=[[{0}]] is now [[{1}]].\ncitizens.commands.npc.age.unlocked=Age unlocked.\ncitizens.commands.npc.anchor.added=Anchor added.\ncitizens.commands.npc.anchor.already-exists=The anchor [[{0}]] already exists.\ncitizens.commands.npc.anchor.invalid-name=Invalid anchor name.\ncitizens.commands.npc.anchor.missing=The anchor [[{1}]] does not exist.\ncitizens.commands.npc.anchor.removed=Anchor removed.\ncitizens.commands.npc.bee.anger-set=Anger set to [[{0}]].\ncitizens.commands.npc.bee.has-nectar=[[{0}]] has nectar.\ncitizens.commands.npc.bee.no-nectar=[[{0}]] has no nectar.\ncitizens.commands.npc.bee.not-stung=[[{0}]] has no longer stung.\ncitizens.commands.npc.bee.bee-stung=[[{0}]] has now stung.\ncitizens.commands.npc.bee.invalid-anger=Anger should be above zero.\ncitizens.commands.npc.behaviour.added=Behaviours added.\ncitizens.commands.npc.behaviour.help=The scripts argument is a comma-separated list of file names. Scripts will be loaded automatically and run every tick. Use the [[-r]] flag to remove behaviours.\ncitizens.commands.npc.behaviour.removed=Behaviours removed.\ncitizens.commands.npc.cat.invalid-type=Invalid type specified. Valid types are [[{0}]].\ncitizens.commands.npc.cat.invalid-collar-color=Invalid collar color specified. Valid types are [[{0}]].\ncitizens.commands.npc.cat.collar-color-set=Collar color set to [[{0}]].\ncitizens.commands.npc.cat.sitting-start=[[{0}]] started sitting.\ncitizens.commands.npc.cat.sitting-stop=[[{0}]] stopped sitting.\ncitizens.commands.npc.cat.lying-start=[[{0}]] started lying down.\ncitizens.commands.npc.cat.lying-stop=[[{0}]] stopped lying down.\ncitizens.commands.npc.cat.type-set=Type set to [[{0}]].\ncitizens.commands.npc.collidable.set=[[{0}]] will now collide with entities.\ncitizens.commands.npc.collidable.unset=[[{0}]] will no longer collide with entities.\ncitizens.commands.npc.command.none-added=No commands have been added.\ncitizens.commands.npc.command.cost-set=Set cost per click to [[{0}]].\ncitizens.commands.npc.command.left-hand-header=Commands to run on [[left click]]:\ncitizens.commands.npc.command.right-hand-header=Commands to run on [[right click]]:\ncitizens.commands.npc.command.command-removed=Command [[{0}]] removed.\ncitizens.commands.npc.command.command-added=Command [[{0}]] added with id [[{1}]].\ncitizens.commands.npc.command.help=
Use the [[-l]] flag to make the command run on left click, [[-r]] on right click (default).
Set the per-player cooldown before the command can be used again using [[--cooldown]] (in [[seconds]]).
Set the server-wide cooldown in seconds using [[--gcooldown]].
[[--delay]] will wait the specified amount in [[ticks]] before executing the command.
[[--permissions]] will set the command to require specific permissions (separate multiple with commas).
[[--n]] will only let the player run the command that number of times.
Use [[-o]] to temporarily execute the command as an op and [[-p]] to run the command as the clicking player instead of the server.
To give the player temporary permissions instead of op, use [[/npc command permissions]].
Set the cost of each click with [[/npc command cost]].
Commands can be executed one by one instead of all at once by using [[/npc command sequential]].\ncitizens.commands.npc.command.unknown-id=Unknown command id [[{0}]] for this NPC.\ncitizens.commands.npc.command.temporary-permissions-set=Temporary permissions set to [[{0}]].\ncitizens.commands.npc.commands.sequential-set=Commands will now execute sequentially.\ncitizens.commands.npc.commands.sequential-unset=Commands will no longer execute sequentially.\ncitizens.commands.npc.commands.random-set=Commands will now execute at random.\ncitizens.commands.npc.commands.random-unset=Commands will no longer execute at random.\ncitizens.commands.npc.controllable.not-controllable=[[{0}]] is not controllable.\ncitizens.commands.npc.controllable.removed=[[{0}]] can no longer be controlled.\ncitizens.commands.npc.controllable.set=[[{0}]] can now be controlled.\ncitizens.commands.npc.copy.copied=[[{0}]] has been copied.\ncitizens.commands.npc.create.mobtype-missing=[[{0}]] is not supported as an NPC mobtype.\ncitizens.commands.npc.create.invalid-location=Spawn location could not be parsed or was not found.\ncitizens.commands.npc.create.invalid-mobtype=[[{0}]] is not a valid mob type.\ncitizens.commands.npc.create.npc-name-too-long=NPC names cannot be longer than [[{0}]] characters. The name has been shortened.\ncitizens.commands.npc.create.no-player-for-spawn=No player could be found by that name to spawn an NPC at.\ncitizens.commands.npc.despawn.despawned=You despawned [[{0}]].\ncitizens.commands.npc.endercrystal.showing-bottom=[[{0}]] is now showing its base.\ncitizens.commands.npc.endercrystal.not-showing-bottom=[[{0}]] is no longer showing its base.\ncitizens.commands.npc.enderman.angry-set=[[{0}]] is now angry.\ncitizens.commands.npc.enderman.angry-unset=[[{0}]] is no longer angry.\ncitizens.commands.npc.flyable.set=[[{0}]] is now flyable.\ncitizens.commands.npc.flyable.unset=[[{0}]] is no longer flyable.\ncitizens.commands.npc.fox.crouching-set=[[{0}]] is now crouching.\ncitizens.commands.npc.fox.crouching-unset=[[{0}]] is no longer crouching.\ncitizens.commands.npc.fox.sitting-set=[[{0}]] is now sitting.\ncitizens.commands.npc.fox.sitting-unset=[[{0}]] is no longer sitting.\ncitizens.commands.npc.fox.sleeping-set=[[{0}]] is now sleeping.\ncitizens.commands.npc.fox.sleeping-unset=[[{0}]] is no longer sleeping.\ncitizens.commands.npc.fox.fox-type-set=Fox type set to [[{0}]].\ncitizens.commands.npc.fox.invalid-fox-type=Invalid fox type specified. Valid types are [[{0}]].\ncitizens.commands.npc.follow.set=[[{0}]] is now following [[{1}]]. \ncitizens.commands.npc.follow.unset=[[{0}]] is no longer following anyone.\ncitizens.commands.npc.gamemode.describe={0}''s gamemode is [[{1}]].\ncitizens.commands.npc.gamemode.invalid={0} is not a valid gamemode.\ncitizens.commands.npc.gamemode.set=Gamemode set to [[{0}]].\ncitizens.commands.npc.gravity.disabled=Gravity [[disabled]].\ncitizens.commands.npc.gravity.enabled=Gravity [[enabled]].\ncitizens.commands.npc.glowing.set=[[{0}]] is now glowing.\ncitizens.commands.npc.glowing.player-only=Glowing color can only be changed for player-type NPCs.\ncitizens.commands.npc.glowing.unset=[[{0}]] is no longer glowing.\ncitizens.commands.npc.glowing.color-set=[[{0}]]''s glowing color set to {1}]].\ncitizens.commands.npc.guardian.elder-unset=[[{0}]] is no longer an elder guardian.\ncitizens.commands.npc.guardian.elder-set=[[{0}]] is now an elder guardian.\ncitizens.commands.npc.hologram.text-set=Set hologram text line [[{0}]] to [[{1}]].\ncitizens.commands.npc.hologram.text-removed=Removed hologram text.\ncitizens.commands.npc.hologram.text-missing=Missing text to add.\ncitizens.commands.npc.hologram.text-describe-header=[[{0}]]''s hologram text (in bottom-up order):\ncitizens.commands.npc.hologram.invalid-text-id=Invalid line number.\ncitizens.commands.npc.hologram.line-height-set=Line height set to [[{0}]].\ncitizens.commands.npc.hologram.line-removed=Removed line [[{0}]].\ncitizens.commands.npc.hologram.direction-set=Direction set to [[{0}]].\ncitizens.commands.npc.hologram.line-add=Added a new hologram line: [[{0}]].\ncitizens.commands.npc.hologram.cleared=Hologram lines cleared.\ncitizens.commands.npc.horse.chest-set=The horse is now carrying a chest.\ncitizens.commands.npc.horse.chest-unset=The horse is no longer carrying a chest.\ncitizens.commands.npc.horse.color-set=The horse''s color was set to [[{0}]].\ncitizens.commands.npc.horse.type-set=The horse''s type was set to [[{0}]].\ncitizens.commands.npc.horse.style-set=The horse''s style was set to [[{0}]].\ncitizens.commands.npc.horse.describe=The horse''s color is [[{0}]], the type is [[{1}]] and the style is [[{2}]].\ncitizens.commands.npc.horse.invalid-color=Invalid horse color given. Valid colors are: [[{0}]].\ncitizens.commands.npc.horse.invalid-type=Invalid horse type given. Valid types are: [[{0}]].\ncitizens.commands.npc.horse.invalid-style=Invalid horse style given. Valid styles are: [[{0}]].\ncitizens.commands.npc.item.item-set=NPC item set to [[{0}]].\ncitizens.commands.npc.item.unknown-material=Unknown material given.\ncitizens.commands.npc.leashable.set=[[{0}]] is now leashable.\ncitizens.commands.npc.leashable.stopped=[[{0}]] is no longer leashable.\ncitizens.commands.npc.llama.strength-set=Llama strength set to [[{0}]].\ncitizens.commands.npc.llama.color-set=Llama color set to [[{0}]].\ncitizens.commands.npc.llama.invalid-color=Invalid llama color given. Valid colors are: [[{0}]].\ncitizens.commands.npc.lookclose.random-look-delay-set=[[{0}]] will now randomly look every [[{1}]] ticks.\ncitizens.commands.npc.lookclose.random-pitch-range-set=[[{0}]] will now randomly choose a pitch in the range [{1}].\ncitizens.commands.npc.lookclose.random-yaw-range-set=[[{0}]] will now randomly choose a yaw in the range [{1}].\ncitizens.commands.npc.lookclose.random-set=[[{0}]] will now randomly look around.\ncitizens.commands.npc.lookclose.random-stopped=[[{0}]] will no longer randomly look around.\ncitizens.commands.npc.lookclose.error-random-range=Invalid range [[{0}]]. Use the format `min,max`.\ncitizens.commands.npc.lookclose.set=[[{0}]] will now rotate when players are nearby.\ncitizens.commands.npc.lookclose.stopped=[[{0}]] will no longer rotate when players are nearby.\ncitizens.commands.npc.metadata.set=[[{0}]] set to [[{1}]].\ncitizens.commands.npc.metadata.unset=Removed [[{{0}}]] from [[{1}]].\ncitizens.commands.npc.minecart.set=[[{0}]] now has item [[{1}]]:[[{2}]] with offset [[{3}]].\ncitizens.commands.npc.mount.failed=Couldn''t mount [[{0}]].\ncitizens.commands.npc.mount.must-be-spawned=Couldn''t mount [[{0}]]. Make sure that the destination NPC ID is correct and it is spawned.\ncitizens.commands.npc.moveto.format=Format is x:y:z(:world) or x y z( world).\ncitizens.commands.npc.mount.mount-on-itself=Can''t mount NPC on itself.\ncitizens.commands.npc.moveto.teleported=[[{0}]] teleported to [[{1}]].\ncitizens.commands.npc.mushroomcow.invalid-variant=Invalid variant. Valid values are: [[{0}]].\ncitizens.commands.npc.mushroomcow.variant-set=[[{0}]]''s variant set to [[{1}]].\ncitizens.commands.npc.nameplate.toggled=Nameplate visibility toggled.\ncitizens.commands.npc.ocelot.deprecated=Ocelot types don''t work in this version of Minecraft. Use [[/npc type cat]] if you want to change the colour.\ncitizens.commands.npc.ocelot.invalid-type=Invalid ocelot type. Valid types are: [[{0}]].\ncitizens.commands.npc.owner.already-owner={0} is already the owner of [[{1}]].\ncitizens.commands.npc.owner.owner=[[{0}]]''s owner is [[{1}]].\ncitizens.commands.npc.owner.set-server=[[The server]] is now the owner of [[{0}]].\ncitizens.commands.npc.owner.set=[[{1}]] is now the owner of [[{0}]].\ncitizens.commands.npc.panda.invalid-gene=Invalid gene. Valid genes are [[{0}]].\ncitizens.commands.npc.panda.sitting=Started sitting.\ncitizens.commands.npc.panda.stopped-sitting=Stopped sitting.\ncitizens.commands.npc.panda.main-gene-set=Main gene set to [[{0}]].\ncitizens.commands.npc.panda.hidden-gene-set=Hidden gene set to [[{0}]].\ncitizens.commands.npc.passive.set=[[{0}]] will no longer damage entities.\ncitizens.commands.npc.passive.unset=[[{0}]] will now damage entities.\ncitizens.commands.npc.pathfindingrange.set=Pathfinding range set to [[{0}]].\ncitizens.commands.npc.parrot.invalid-variant=Invalid parrot variant. Valid variants are [[{0}]].\ncitizens.commands.npc.parrot.variant-set=Variant set to [[{0}]].\ncitizens.commands.npc.pathopt.avoid-water-set=[[{0}]] will now avoid water.\ncitizens.commands.npc.pathopt.avoid-water-unset=[[{0}]] will no longer avoid water.\ncitizens.commands.npc.pathopt.stationary-ticks-set=[[{0}]]''s maximum stationary ticks set to [[{1}]].\ncitizens.commands.npc.pathopt.distance-margin-set=[[{0}]]''s pathfinding distance margin set to [[{1}]].\ncitizens.commands.npc.pathopt.path-distance-margin-set=[[{0}]]''s pathfinding path distance margin set to [[{1}]].\ncitizens.commands.npc.pathopt.attack-range-set=[[{0}]]''s attack range set to [[{1}]].\ncitizens.commands.npc.pathopt.use-new-finder=[[{0}]]''s use new finder set to [[{1}]].\ncitizens.commands.npc.playerlist.added=Added [[{0}]] to the player list.\ncitizens.commands.npc.playerlist.removed=Removed [[{0}]] from the player list. \ncitizens.commands.npc.polarbear.rearing-set=[[{0}]] is now rearing.\ncitizens.commands.npc.polarbear.rearing-unset=[[{0}]] is no longer rearing.\ncitizens.commands.npc.pose.added=Pose added.\ncitizens.commands.npc.pose.already-exists=The pose [[{0}]] already exists.\ncitizens.commands.npc.pose.invalid-name=Invalid pose name.\ncitizens.commands.npc.pose.missing=The pose [[{0}]] does not exist.\ncitizens.commands.npc.pose.default-pose-set=Default pose set to [[{0}]].\ncitizens.commands.npc.pose.removed=Pose removed.\ncitizens.commands.npc.powered.set=[[{0}]] will now be powered.\ncitizens.commands.npc.powered.stopped=[[{0}]] will no longer be powered.\ncitizens.commands.npc.phantom.phantom-set=Size set to [[{0}]].\ncitizens.commands.npc.tropicalfish.invalid-color=Invalid fish color. Valid colors are: [[{0}]]\ncitizens.commands.npc.tropicalfish.invalid-pattern=Invalid fish pattern. Valid patterns are: [[{0}]]\ncitizens.commands.npc.tropicalfish.body-color-set=Body color set to [[{0}]].\ncitizens.commands.npc.tropicalfish.pattern-color-set=Pattern color set to [[{0}]].\ncitizens.commands.npc.tropicalfish.pattern-set=Pattern set to [[{0}]].\ncitizens.commands.npc.profession.invalid-profession=[[{0}]] is not a valid profession. Try one of the following: [[{1}]].\ncitizens.commands.npc.profession.set=[[{0}]] is now a [[{1}]].\ncitizens.commands.npc.pufferfish.state-set=State set to [[{0}]].\ncitizens.commands.npc.rabbittype.invalid-type=Invalid rabbit type. Try one of the following: [[{0}]].\ncitizens.commands.npc.rabbittype.type-set=[[{0}]]''s rabbit type has been set to [[{1}]]\ncitizens.commands.npc.remove.npcs-removed=NPCs removed.\ncitizens.commands.npc.remove.incorrect-syntax=Incorrect syntax. /npc remove (all)\ncitizens.commands.npc.remove.removed-all=You permanently removed all NPCs.\ncitizens.commands.npc.remove.removed=You permanently removed [[{0}]].\ncitizens.commands.npc.rename.renamed=You renamed [[{0}]] to [[{1}]].\ncitizens.commands.npc.respawn.delay-set=Respawn delay set to [[{0}]].\ncitizens.commands.npc.respawn.describe=Respawn delay is currently [[{0}]].\ncitizens.commands.npc.select.already-selected=You already have that NPC selected.\ncitizens.commands.npc.script.invalid-file=Unknown or unavailable script ''[[{0}]]''.\ncitizens.commands.npc.scoreboard.added-tags=Added these tags: [[{0}]].\ncitizens.commands.npc.scoreboard.removed-tags=Removed these tags: [[{0}]].\ncitizens.commands.npc.sheep.color-set=The sheep''s color was set to [[{0}]].\ncitizens.commands.npc.sheep.invalid-color=Invalid sheep color given. Valid colors are: [[{0}]].\ncitizens.commands.npc.script.current-scripts=[[{0}]]''s current scripts are [[{1}]].\ncitizens.commands.npc.shulker.peek-set=[[{0}]]''s peek amount set to [[{1}]].\ncitizens.commands.npc.shulker.color-set=[[{0}]]''s color set to [[{1}]].\ncitizens.commands.npc.shulker.invalid-color=Invalid shulker color given. Valid colors are: [[{0}]].\ncitizens.commands.npc.skin.error-setting-url=Error downloading skin texture from [[{0}]]. Are you sure the URL is valid?\ncitizens.commands.npc.skin.skin-url-set=Downloaded [[{0}]]''s skin from [[{1}]].\ncitizens.commands.npc.skin.set=[[{0}]]''s skin name set to [[{1}]].\ncitizens.commands.npc.skin.missing-skin=A skin name is required.\ncitizens.commands.npc.skin.cleared=[[{0}]]''s skin name was cleared.\ncitizens.commands.npc.skin.layers-set=[[{0}]]''s skin layers: cape - [[{1}]], hat - [[{2}]], jacket - [[{3}]], sleeves - [[{4}]], pants - [[{5}]].\ncitizens.commands.npc.size.description=[[{0}]]''s size is [[{1}]].\ncitizens.commands.npc.size.set=[[{0}]]''s size set to [[{1}]].\ncitizens.commands.npc.sound.invalid-sound=Invalid sound.\ncitizens.commands.npc.sound.set=[[{0}]]''s sounds are now: ambient - [[{1}]] hurt - [[{2}]] and death - [[{3}]].\ncitizens.commands.npc.sound.info=[[{0}]]''s sounds are: ambient - [[{1}]] hurt - [[{2}]] and death - [[{3}]].

Valid sounds are {4}.\ncitizens.commands.npc.skeletontype.set={0}''s skeleton type set to [[{1}]].\ncitizens.commands.npc.skeletontype.invalid-type=Invalid skeleton type. Try one of the following: [[{0}]].\ncitizens.commands.npc.snowman.derp-set=[[{0}]] is now in derp mode.\ncitizens.commands.npc.snowman.derp-stopped=[[{0}]] is no longer in derp mode.\ncitizens.commands.npc.spawn.already-spawned=[[{0}]] is already spawned at another location. Use ''/npc tphere'' to teleport the NPC to your location.\ncitizens.commands.npc.spawn.missing-npc-id=No NPC with the ID {0} exists.\ncitizens.commands.npc.spawn.no-location=No stored location available - command must be used ingame.\ncitizens.commands.npc.spawn.numeric-id-only=Only numeric IDs can be used for /npc spawn.\ncitizens.commands.npc.spawn.spawned=You spawned [[{0}]].\ncitizens.commands.npc.speed.modifier-above-limit=Speed is above the limit.\ncitizens.commands.npc.speed.set=NPC speed modifier set to [[{0}]].\ncitizens.commands.npc.swim.set=[[{0}]] will now swim in water.\ncitizens.commands.npc.swim.unset=[[{0}]] will no longer swim in water.\ncitizens.commands.npc.targetable.set=[[{0}]] can now be targeted by mobs.\ncitizens.commands.npc.targetable.unset=[[{0}]] can no longer be targeted by mobs.\ncitizens.commands.npc.tp.teleported=You teleported to [[{0}]].\ncitizens.commands.npc.tp.location-not-found=Couldn''t find the target NPC''s location.\ncitizens.commands.npc.tpto.success=Teleported successfully.\ncitizens.commands.npc.tpto.to-not-found=Destination entity not found.\ncitizens.commands.npc.tpto.from-not-found=Source entity not found.\ncitizens.commands.npc.tphere.missing-cursor-block=Please look at a block to teleport to.\ncitizens.commands.npc.tphere.teleported=[[{0}]] was teleported to {1}.\ncitizens.commands.npc.type.set=[[{0}]]''s type set to [[{1}]].\ncitizens.commands.npc.type.invalid=[[{0}]] is not a valid type.\ncitizens.commands.npc.vulnerable.set=[[{0}]] is now vulnerable.\ncitizens.commands.npc.vulnerable.stopped=[[{0}]] is no longer vulnerable.\ncitizens.commands.npc.wolf.unknown-collar-color=[[{0}]] is not an RGB-formatted collar color or the name of a DyeColor.\ncitizens.commands.npc.wolf.collar-color-unsupported=[[{0}]] is not a RGB color code that can be used on a wolf''s collar.\ncitizens.commands.npc.villager.level-set=Level set to [[{0}]].\ncitizens.commands.npc.villager.invalid-type=Invalid villager type. Valid types are: [[{0}]].\ncitizens.commands.npc.villager.type-set=Type set to [[{0}]].\ncitizens.commands.npc.zombiemod.villager-set=[[{0}]] is now a villager.\ncitizens.commands.npc.zombiemod.villager-unset=[[{0}]] is no longer a villager.\ncitizens.commands.npc.zombiemod.baby-set=[[{0}]] is now a baby.\ncitizens.commands.npc.zombiemod.baby-unset=[[{0}]] is no longer a baby.\ncitizens.commands.npc.zombiemod.villager-profession-set=[[{0}]]''s profession set to [[{1}]].\ncitizens.commands.page-missing=The page [[{0}]] does not exist.\ncitizens.commands.requirements.disallowed-mobtype=The NPC cannot be the mob type [[{0}]] for that command.\ncitizens.commands.requirements.living-entity=The NPC must be a living entity.\ncitizens.commands.requirements.missing-permission=You don''t have permission to execute that command.\ncitizens.commands.requirements.missing-required-trait=Missing required trait [[{0}]].\ncitizens.commands.requirements.must-be-ingame=You must be ingame to use that command.\ncitizens.commands.requirements.must-be-owner=You must be the owner of this NPC to execute that command.\ncitizens.commands.requirements.must-have-selected=You must have an NPC selected to execute that command.\ncitizens.commands.requirements.too-few-arguments=Too few arguments.\ncitizens.commands.requirements.too-many-arguments=Too many arguments.\ncitizens.commands.script.compiled=Script compiled.\ncitizens.commands.script.compiling=Script compiling...\ncitizens.commands.script.file-missing=The file [[{0}]] doesn''t exist!\ncitizens.commands.template.applied=Template applied to [[{0}]] NPCs.\ncitizens.commands.template.conflict=A template by that name already exists.\ncitizens.commands.template.created=Template created.\ncitizens.commands.template.missing=Template not found.\ncitizens.commands.template.delete.deleted=Deleted template [[{0}]].\ncitizens.commands.template.list.header=]]Available templates[[:]]\ncitizens.commands.trait.added=Added {0} successfully.\ncitizens.commands.trait.failed-to-add=<7>Couldn''t add {0}.\ncitizens.commands.trait.failed-to-change=<7>Couldn''t change {0}.\ncitizens.commands.trait.failed-to-remove=<7>Couldn''t remove {0}.\ncitizens.commands.trait.removed=Removed {0} successfully.\ncitizens.commands.traitc.missing=Trait not found.\ncitizens.commands.traitc.not-configurable=That trait is not configurable.\ncitizens.commands.traitc.not-on-npc=The NPC doesn''t have that trait.\ncitizens.commands.unknown-command=Unknown command. Did you mean:\ncitizens.commands.waypoints.add.waypoint-added=Added waypoint at [[{0}]] (index [[{1}]]).\ncitizens.commands.waypoints.disableteleporting.disabled=[[{0}]] will no longer teleport when stuck pathfinding.\ncitizens.commands.waypoints.opendoors.enabled=[[{0}]] will now open doors while pathfinding.\ncitizens.commands.waypoints.opendoors.disabled=[[{0}]] will no longer doors while pathfinding.\ncitizens.commands.wolf.traits-updated=[[{0}]]''s Traits were updated. Angry:[[{1}]], Sitting:[[{2}]], Tamed:[[{3}]], Collar Color:[[{4}]] \ncitizens.conversations.selection.invalid-choice=[[{0}]] is not a valid option.\ncitizens.economy.error-loading=Vault not found -> no economy handling.\ncitizens.economy.minimum-cost-required=Need at least [[{0}]].\ncitizens.economy.money-withdrawn=Withdrew [[{0}]] for your NPC.\ncitizens.editors.already-in-editor=You''re already in an editor!\ncitizens.editors.copier.begin=Entered the NPC copier!
Click anywhere to copy the currently selected NPC.\ncitizens.editors.copier.end=Exited the NPC copier.\ncitizens.editors.equipment.all-items-removed=[[{0}]] had all of its items removed.\ncitizens.editors.equipment.begin=Entered the equipment editor!
[[Right click]] to equip the NPC or [[crouch right click]] to change the item in hand!
Type [[offhand]], [[chestplate]], [[helmet]], etc. in chat to equip specific slots with the item you''re holding!\ncitizens.editors.equipment.end=Exited the equipment editor.\ncitizens.editors.equipment.invalid-block=Invalid block!\ncitizens.editors.equipment.saddled-set=[[{0}]] is now saddled.\ncitizens.editors.equipment.saddled-stopped=[[{0}]] is no longer saddled.\ncitizens.editors.equipment.sheared-set=[[{0}]] is now sheared.\ncitizens.editors.equipment.sheared-stopped=[[{0}]] is no longer sheared.\ncitizens.editors.equipment.sheep-coloured=[[{0}]] is now coloured [[{1}]].\ncitizens.editors.selection.start-prompt=There were multiple NPCs with the supplied name.
Please enter an id or number from the list below to select that NPC.\ncitizens.editors.text.add-prompt=Enter text to add to the NPC.\ncitizens.editors.text.added-entry=[[Added]] the entry [[{0}]].\ncitizens.editors.text.begin=Entered the text editor! Type ''exit'' to leave the editor.\ncitizens.editors.text.change-page-prompt=Enter a page number to view more text entries.\ncitizens.editors.text.close-talker-set=[[Close talker]] set to [[{0}]].\ncitizens.editors.text.edit-begin-prompt=Enter the index of the entry you wish to edit or [[page]] to view more pages.\ncitizens.editors.text.edit-prompt=Enter text to edit the entry.\ncitizens.editors.text.edited-text=Changed entry at index [[{0}]] to [[{1}]].\ncitizens.editors.text.end=Exited the text editor.\ncitizens.editors.text.missing-item-set-pattern=Missing item in hand pattern.\ncitizens.editors.text.invalid-edit-type=Invalid edit type.\ncitizens.editors.text.invalid-index=[[{0}]] is not a valid index!\ncitizens.editors.text.invalid-input=Invalid input.\ncitizens.editors.text.invalid-page=Invalid page number.\ncitizens.editors.text.invalid-range=Invalid range.\ncitizens.editors.text.invalid-delay=Invalid delay.\ncitizens.editors.text.random-talker-set=[[Random talking]] set to [[{0}]].\ncitizens.editors.text.range-set=[[Range]] set to [[{0}]].\ncitizens.editors.text.delay-set=[[Delay]] set to [[{0}]] seconds.\ncitizens.editors.text.realistic-looking-set=[[Realistic looking]] set to [[{0}]].\ncitizens.editors.text.remove-prompt=Enter the index of the entry you wish to remove or [[page]] to view more pages.\ncitizens.editors.text.removed-entry=[[Removed]] entry at index [[{0}]].\ncitizens.editors.text.start-prompt=Type [[add]] to add an entry, [[edit]] to edit entries, [[remove]] to remove entries, [[close]] to toggle the NPC to send messages when players get close, [[item]] to set the talk item in hand pattern (set to [[default]] to clear), [[range]] to set the talking range, [[delay]] to set the talking delay in seconds and [[random]] to toggle the NPC as a random talker. Type [[help]] to show this again.\ncitizens.editors.text.talk-item-set=[[Talk item pattern]] set to [[{0}]].\ncitizens.editors.waypoints.wander.editing-regions-stop=Exited the region editor.\ncitizens.editors.waypoints.wander.worldguard-region-not-found=WorldGuard region not found.\ncitizens.editors.waypoints.wander.worldguard-region-set=WorldGuard region set to [[{0}]].\ncitizens.editors.waypoints.wander.range-set=Wander range set to xrange [[{0}]] and yrange [[{1}]].\ncitizens.editors.waypoints.wander.begin=Entered the wander waypoint editor.
Type [[xrange ]] or [[yrange ]] to modify the random wander range. Type [[regions]] to enter the region editor.
Type [[delay ]] to delay the NPC between wanders. Type [[worldguardregion ]] to restrict movement to a WorldGuard region.\ncitizens.editors.waypoints.wander.end=Exited the wander waypoint editor.\ncitizens.editors.waypoints.wander.delay-set=Delay between wanders set to [[{0}]] ticks.\ncitizens.editors.waypoints.wander.invalid-delay=Invalid delay specified.\ncitizens.editors.waypoints.wander.added-region=[[Added]] wanderable region at ({0}) ([[{1}]]).\ncitizens.editors.waypoints.wander.removed-region=[[Removed]] wanderable region at ({0}) ([[{1}]] remaining).\ncitizens.editors.waypoints.wander.editing-regions=Now editing regions!
[[Left click]] to add a new wanderable region using the xrange/yrange box centred at that block.
[[Right click]] an existing marker to remove that region.
Type [[regions]] to stop or simply exit the editor. Regions should be overlapping with each other.\ncitizens.editors.waypoints.guided.end=Exited the guided waypoint editor.\ncitizens.editors.waypoints.guided.begin=Entered the guided waypoint editor!
[[Left click]] to add a waypoint guide, [[right click]] an existing waypoint to remove.
[[Sneak]] while left clicking to add a destination waypoint.
Type [[toggle path]] to toggle showing entities at waypoints.\ncitizens.editors.waypoints.guided.added-guide=Added a [[guide]] waypoint. This will guide NPCs to their destination.\ncitizens.editors.waypoints.guided.added-available=Added a [[destination]] waypoint. This will be available for NPCs to path to.\ncitizens.editors.waypoints.guided.already-taken=There is already a waypoint here.\ncitizens.editors.waypoints.linear.added-waypoint=[[Added]] a waypoint at ({0}) ([[{1}]] total).\ncitizens.editors.waypoints.linear.begin==== Linear Waypoint Editor ===
[[Left click]] to add a waypoint, [[right click]] to remove it.
You can right click while sneaking to select and remove specific points.
Type [[markers]] to hide waypoints,
[[triggers]] to enter the trigger editor,
[[clear]] to clear all waypoints,
[[cycle]] to make NPCs cycle through waypoints instead of looping.\ncitizens.editors.waypoints.linear.selected-waypoint=Selected waypoint at {0}. Sneak + right click again to remove this waypoint.\ncitizens.editors.waypoints.linear.end=Exited the linear waypoint editor.\ncitizens.editors.waypoints.linear.not-showing-markers=[[Stopped]] showing waypoint markers.\ncitizens.editors.waypoints.linear.range-exceeded=Previous waypoint is {0} blocks away but the distance limit is {1}.\ncitizens.editors.waypoints.linear.removed-waypoint=[[Removed]] a waypoint ([[{0}]] remaining)\ncitizens.editors.waypoints.linear.showing-markers=[[Showing]] waypoint markers.\ncitizens.editors.waypoints.linear.waypoints-cleared=Waypoints cleared.\ncitizens.editors.waypoints.linear.cycle-set=Now [[cycling]] through waypoints.\ncitizens.editors.waypoints.linear.cycle-unset=Now [[looping]] through waypoints.\ncitizens.editors.waypoints.triggers.add.added=Added waypoint trigger successfully ({0}).\ncitizens.editors.waypoints.triggers.add.invalid-trigger=Couldn''t create a trigger by the name [[{0}]].\ncitizens.editors.waypoints.triggers.add.prompt=Enter in a trigger name to add or type [[back]] to return to the edit prompt. Valid trigger names are {0}.\ncitizens.editors.waypoints.triggers.animation.added=Animation [[{0}]] added.\ncitizens.editors.waypoints.triggers.list=Current triggers are:{0}\ncitizens.editors.waypoints.triggers.animation.prompt=Enter in animations to perform - valid animations are {0}.
Type in [[finish]] to finish the animation trigger or [[back]] to return to the previous prompt.\ncitizens.editors.waypoints.triggers.animation.invalid-animation=Invalid animation [[{0}]]. Valid animations are {1}.\ncitizens.editors.waypoints.triggers.chat.invalid-radius=The radius must be a number.\ncitizens.editors.waypoints.triggers.chat.missing-radius=No radius supplied.\ncitizens.editors.waypoints.triggers.chat.message-added=Message added: [[{0}]].\ncitizens.editors.waypoints.triggers.chat.radius-set=Radius set to [[{0}]] blocks.\ncitizens.editors.waypoints.triggers.chat.prompt=Enter in chat lines to say.
Type in [[radius (radius)]] to set the block radius to broadcast the messages.
Type [[finish]] to finish the chat trigger or [[back]] to return to the previous prompt.\ncitizens.editors.waypoints.triggers.delay.prompt=Enter the delay in [[server ticks]] to use. (20 ticks = 1 second)\ncitizens.editors.waypoints.triggers.main.missing-waypoint=Not editing a waypoint.\ncitizens.editors.waypoints.triggers.main.exit=Exited the waypoint trigger editor.\ncitizens.editors.waypoints.triggers.main.prompt=- Waypoint Trigger Editor -
Type [[add]] or [[remove]] to edit triggers.
Type [[triggers]] or [[exit]] to exit this editor.
Current triggers are:{0}\ncitizens.editors.waypoints.triggers.remove.index-out-of-range=Index must be in the range [[1-{0}]].\ncitizens.editors.waypoints.triggers.remove.not-a-number=Index must be a number.\ncitizens.editors.waypoints.triggers.remove.prompt=Enter in the index of the trigger to delete or [[back]] to return to the edit prompt. Current triggers are:{0}\ncitizens.editors.waypoints.triggers.remove.removed=Successfully removed trigger [[{0}]].\ncitizens.editors.waypoints.triggers.speed.prompt=Enter the speed modifier as a [[percentage]] of its base speed.\ncitizens.editors.waypoints.triggers.teleport.invalid-format=Invalid location given. Format is [[world]]:[[x]]:[[y]]:[[z]].\ncitizens.editors.waypoints.triggers.teleport.prompt=Enter the destination in the format world:x:y:z. Type [[here]] to use your current location. Type [[back]] to return to the edit prompt.\ncitizens.limits.over-npc-limit=Over the NPC limit of {0}.\ncitizens.load-task-error=NPC load task couldn''t be scheduled, disabling...\ncitizens.nms-errors.clearing-goals=Could not clear goals: {0}.\ncitizens.nms-errors.restoring-goals=Could not restore goals: {0}.\ncitizens.nms-errors.error-setting-persistent=Could not set NPC as persistent: {0}. NPC entity may despawn.\ncitizens.nms-errors.getting-field=Could not fetch NMS field {0}: [[{1}.\ncitizens.nms-errors.getting-method=Could not fetch NMS method {0}: [[{1}.\ncitizens.nms-errors.getting-id-mapping=Could not fetch entity id mapping fields: {0}.\ncitizens.nms-errors.spawning-custom-entity=Could not spawn custom entity: {0}.\ncitizens.nms-errors.stopping-network-threads=Could not stop network threads: {0}.\ncitizens.nms-errors.updating-land-modifier=Could not update land speed modifier: {0}.\ncitizens.nms-errors.updating-navigation-world=Could not update navigation world: {0}.\ncitizens.nms-errors.updating-pathfinding-range=Could not update pathfinding range: {0}.\ncitizens.notifications.database-connection-failed=Unable to connect to database, falling back to YAML\ncitizens.notifications.error-reloading=Error occured while reloading, see console.\ncitizens.notifications.exception-updating-npc=Exception while updating {0}: {1}.\ncitizens.notifications.incompatible-version=v{0} is not compatible with Minecraft v{1} - try upgrading or downgrading Citizens. Disabling.\ncitizens.notifications.locale=Using locale {0}.\ncitizens.notifications.metrics-load-error=Unable to start metrics: {0}.\ncitizens.notifications.missing-translations=Missing translations file for locale {0}. Defaulting to en locale.\ncitizens.notifications.npc-name-not-found=Could not find a name for ID {0}.\ncitizens.notifications.npc-not-found=No NPC could be found.\ncitizens.notifications.npcs-loaded=Loaded {0} NPCs.\ncitizens.notifications.reloaded=Citizens reloaded.\ncitizens.notifications.reloading=Reloading Citizens...\ncitizens.notifications.saved=Citizens saved.\ncitizens.notifications.saving=Saving Citizens...\ncitizens.notifications.skipping-broken-trait=Skipped broken or missing trait {0} while loading ID {1}. Has the name changed?\ncitizens.notifications.skipping-invalid-pose=Skipping pose {0} - invalid yaw/pitch ({1}).\ncitizens.notifications.trait-load-failed=The trait {0} failed to load for NPC ID: {1}.\ncitizens.notifications.trait-onspawn-failed=An exception occurred while the trait {0} was spawning for NPC ID {1}.\ncitizens.notifications.unknown-npc-type=NPC type {0} was not recognized. Did you spell it correctly?\ncitizens.saves.load-failed=Unable to load saves, disabling...\ncitizens.settings.writing-default=Writing default setting: {0}\ncitizens.sub-plugins.error-on-load={0} initializing {1}\ncitizens.sub-plugins.load=Loading {0}\ncitizens.traits.age-description={0}''s age is [[{1}]]. Locked is [[{2}]].\ncitizens.waypoints.available-providers-header=List of available providers\ncitizens.waypoints.current-provider=The current waypoint provider is [[{0}]].\ncitizens.waypoints.set-provider=Set the waypoint provider to [[{0}]]." create: '§aCrear conjunto de misiones' edit: '§e> §6§oEditar el conjunto... §e<' choose: '§e> §6§oElige este grupo §e<' diff --git a/core/src/main/resources/locales/zh_CN.yml b/core/src/main/resources/locales/zh_CN.yml index b873c8bf..e426b451 100644 --- a/core/src/main/resources/locales/zh_CN.yml +++ b/core/src/main/resources/locales/zh_CN.yml @@ -74,6 +74,7 @@ msg: negative: '§c你必须输入正数!' zero: '§c你必须输入大于0的数!' invalid: '§c"{0}" 不是有效的数字.' + notInBounds: '§c你的数字必须介于{0}和{1}之间。' errorOccurred: '§c插件发生错误,请联系管理员!错误代码为: {0}' commandsDisabled: '§c你不能在这时输入指令.' indexOutOfBounds: '§c数字 ({0}) 超过了区间 [{1}, {2}].' @@ -93,7 +94,7 @@ msg: downloadTranslations: syntax: '§c你必须指定要下载的语言。例如:"/quests downloadTranslations en_US"。' notFound: '§c找不到{1}版本的{0}语言。' - exists: '§c文件{0}已存在。质量加上"true"覆盖文件。(/quests downloads translations true)' + exists: '§c文件 {0} 已存在。在你的指令后加上 "-overwrite" 覆盖文件。 (/quests downloadTranslations -overwrite)' downloaded: '§a已下载语言{0}!§7你现在必须打开文件"/plugins/BeautyQuests/config.yml",更改§ominecraftTranslationsFile§7的值为{0},然后重启服务器。' checkpoint: noCheckpoint: '§c未找到任务{0}的记录点。' @@ -128,7 +129,7 @@ msg: remover: '§6{1} 的 {0} 条任务信息已被删除.' resetQuest: '§6已移除{0}的任务数据。' startQuest: '§6你接受了任务 {0} (UUID: {1}).' - startQuestNoRequirements: '§c玩家不符合任务{0}需求... 在指令末尾加上“true”绕过任务需求检测。' + startQuestNoRequirements: '§c玩家不满足任务{0}的需求……在指令结尾加上 "-overrideRequirements" 无视需求检测。' cancelQuest: '§6你放弃了任务 {0}.' cancelQuestUnavailable: '§c无法取消任务 {0} .' backupCreated: '§6你成功地创建了任务和玩家数据的备份文件.' @@ -177,6 +178,7 @@ msg: blockData: '§a请输入方块数据 (可用的方块数据: {0}):' blockTag: '§a请输入方块标签 (可用标签:§7{0}§a):' typeBucketAmount: '§a请输入要装满桶的数量:' + typeDamageAmount: '输入玩家需要造成的伤害:' goToLocation: '§a前往该任务阶段指定地点。' typeLocationRadius: '§a请输入距离指定位置所需的距离:' typeGameTicks: '§a请输入所需的刻数:' @@ -264,7 +266,7 @@ msg: list: '§a所有的MythocMobs:' isntMythicMob: '§c这个MythicMob不存在.' disabled: '§cMythicMob已关闭.' - epicBossDoesntExist: '§c这个EpicBoss 不存在.' + advancedSpawnersMob: '输入所需击杀的自定义刷怪笼怪物名称:' textList: syntax: '§c正确用法: ' added: '§a已添加文本 "§7{0}§a"。' @@ -275,6 +277,7 @@ msg: remove: '§6remove [id]: §e删除一条文本.' list: '§6list: §e查看所有已添加的文本.' close: '6close: §e确认添加文本并退出编辑器.' + availableElements: '可用元素:§e{0}' noSuchElement: '§c没有这样的元素。可用元素:§e{0}' invalidPattern: '§c无效的正则表达式§4{0}§c。' comparisonTypeDefault: '§a请选择你想要在{0}中使用的比较类型。默认比较类型为:§e§l{1}§r§a。输入§onull§r§a使用。' @@ -290,6 +293,9 @@ msg: fadeIn: '输入淡入持续时间,单位为刻(20刻=1秒)。' stay: '输入停留持续时间,单位为刻(20刻=1秒)。' fadeOut: '输入淡出持续时间,单位为刻(20刻=1秒)。' + colorNamed: '输入颜色名称。' + color: '可以输入十六位格式(#XXXXX)或RGB格式(RED GRE BLU)的颜色。' + invalidColor: '你输入的颜色无效,必须是十六位或RGB颜色。' firework: invalid: '这件物品不是有效的烟花。' invalidHand: '你必须在主手手持烟花。' @@ -332,6 +338,9 @@ inv: playTime: '§e游戏时间' breedAnimals: '§a饲养动物' tameAnimals: '§a驯服动物' + death: '§c死亡' + dealDamage: '§c对怪物造成伤害' + eatDrink: '§a食用食物或饮用药水' NPCText: '§e编辑NPC的文本对话' dialogLines: '{0}行' NPCSelect: '§e选择或创建NPC' @@ -370,6 +379,15 @@ inv: location: worldPattern: '§a设置世界名称模式 §d(高级选项)' worldPatternLore: '如果你想要匹配特定模式的任意世界都能完成任务阶段,请在此输入正则表达式。' + death: + causes: '§a设置死因 §d(高级)' + anyCause: 任何死因 + setCauses: '{0} 死因' + dealDamage: + damage: '§e要造成的伤害' + targetMobs: '§c要造成伤害的怪物对象' + eatDrink: + items: '§e编辑要食用或饮用的物品' stages: name: '§8创建任务阶段' nextPage: '§e下一页' @@ -401,8 +419,6 @@ inv: startableFromGUILore: 允许玩家在任务菜单中开始任务。 scoreboardItem: 启用计分版 scoreboardItemLore: 如果禁用,则该任务目标不会显示在计分板上。 - hideItem: 隐藏任务 (菜单和卫星地图) - hideItemLore: 如果启用,任务将不会在dynmap或任务菜单中显示。 hideNoRequirementsItem: 未满足需求时隐藏 hideNoRequirementsItemLore: 如果启用,在未满足需求时任务将不会显示在任务菜单中。 bypassLimit: 忽略任务上限 @@ -459,6 +475,8 @@ inv: firework: '§d任务结束烟花' fireworkLore: 玩家完成任务时发射的烟花 fireworkLoreDrop: 把你的自定义烟花放在这里 + visibility: '§b任务可见度' + visibilityLore: 选择在菜单栏何处显示任务,如果安装了卫星地图插件,则在地图上显示任务。 keepDatas: 保留玩家数据 keepDatasLore: |- 强制插件保存玩家数据,即使是已编辑阶段也会保存。 @@ -649,6 +667,26 @@ inv: fadeIn: '§a淡入持续时间' stay: '§b停留持续时间' fadeOut: '§a淡出持续时间' + particleEffect: + name: 创建粒子特效 + shape: '§d粒子形状' + type: '§e粒子类型' + color: '§b粒子颜色' + particleList: + name: 粒子列表 + colored: 彩色粒子 + damageCause: + name: 造成伤害 + damageCausesList: + name: 造成伤害列表 + visibility: + name: 任务可见度 + notStarted: '“未开始”菜单栏' + inProgress: '“进行中”菜单栏' + finished: '“已完成”菜单栏' + maps: '地图(比如Dynmap或BlueMap)' + equipmentSlots: + name: 装备栏位 scoreboard: name: '§6§l任务' noLaunched: '§c你没有进行中的任务.' @@ -675,6 +713,11 @@ scoreboard: playTimeFormatted: '§e游玩 §6{0}' breed: '§e饲养§6{0}' tame: '§e驯服§6{0}' + die: '§c死亡' + dealDamage: + any: '§c造成{0}点伤害' + mobs: '§c对{1}造成{0}点伤害' + eatDrink: '§e消耗§6{0}' indication: startQuest: '§7您是否想要接受任务 {0}?' closeInventory: '§7你确定要关闭该页面吗?' @@ -722,6 +765,9 @@ misc: playTime: 游戏时间 breedAnimals: 饲养动物 tameAnimals: 驯服动物 + die: 死亡 + dealDamage: 造成伤害 + eatDrink: 食用或饮用 comparison: equals: 等于{0} different: 不同于{0} @@ -744,6 +790,7 @@ misc: quest: '§a所需前置任务' mcMMOSkillLevel: '§d所需技能等级' money: '§d所需金额' + equipment: '§e所需装备' bucket: water: 水桶 lava: 岩浆桶 @@ -755,6 +802,12 @@ misc: shift-right: Shift+右击 shift-left: Shift+左击 middle: 中键点击 + amounts: + items: '{0}件物品' + comparisons: '{0}个对比' + dialogLines: '{0}行' + permissions: '{0}个权限' + mobs: '{0}个怪物' ticks: '{0}刻' questItemLore: '§e§o任务物品' hologramText: '§8§l任务NPC' From a474acbcdc71af6e8efe36a84d89a29c216017bc Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 11 Sep 2022 14:20:39 +0200 Subject: [PATCH 067/111] :card_file_box: Added a "force mysql" config option --- .../stages/types/AbstractCountableStage.java | 8 +- .../fr/skytasul/quests/utils/Database.java | 76 ++++++++------- core/src/main/resources/config.yml | 1 + v1_19_R1/dependency-reduced-pom.xml | 93 +++++++++++++++++++ v1_19_R1/pom.xml | 34 +++++++ 5 files changed, 179 insertions(+), 33 deletions(-) create mode 100644 v1_19_R1/dependency-reduced-pom.xml diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableStage.java index 58ebec10..74227545 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableStage.java @@ -180,7 +180,13 @@ public void leaves(PlayerAccount acc, Player p) { } protected void createBar(Player p, int amount) { - if (barsEnabled) bars.put(p, new BossBar(p, amount)); + if (barsEnabled) { + if (bars.containsKey(p)) { + BeautyQuests.logger.warning("Trying to create an already existing bossbar for player " + p.getName()); + return; + } + bars.put(p, new BossBar(p, amount)); + } } protected void removeBar(Player p) { diff --git a/core/src/main/java/fr/skytasul/quests/utils/Database.java b/core/src/main/java/fr/skytasul/quests/utils/Database.java index 29eb781b..01ffd1ed 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Database.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Database.java @@ -25,42 +25,54 @@ public Database(ConfigurationSection config) throws SQLException { this.config = config; this.databaseName = config.getString("database"); - try { - Class.forName("org.mariadb.jdbc.MariaDbPoolDataSource"); - MariaDbPoolDataSource msource = new MariaDbPoolDataSource(); - msource.setServerName(config.getString("host")); - msource.setPortNumber(config.getInt("port")); - msource.setDatabaseName(databaseName); - msource.setUser(config.getString("username")); - msource.setPassword(config.getString("password")); - - msource.setPoolName("beautyquests"); - msource.setMaxIdleTime(60); - msource.setLoginTimeout(20); - - source = msource; - }catch (ClassNotFoundException e) { - MysqlDataSource msource = new MysqlDataSource(); - msource.setServerName(config.getString("host")); - msource.setPortNumber(config.getInt("port")); - msource.setDatabaseName(databaseName); - msource.setUser(config.getString("username")); - msource.setPassword(config.getString("password")); - - msource.setConnectTimeout(20); - boolean ssl = config.getBoolean("ssl"); - msource.setVerifyServerCertificate(ssl); - msource.setUseSSL(ssl); - - source = msource; + if (config.getBoolean("forceMysql")) { + source = newMysqlSource(config); + }else { + try { + Class.forName("org.mariadb.jdbc.MariaDbPoolDataSource"); + source = newMariadbSource(config); + }catch (ClassNotFoundException e) { + source = newMysqlSource(config); + } } DebugUtils.logMessage("Created SQL data source: " + source.getClass().getName()); - // Yes, I know there is literally the same code twice. - // Unfortunately, there is no common interface - // between MariaDB and MySQL pool data source - // which provides the configuration methods. + + } + + private DataSource newMariadbSource(ConfigurationSection config) throws SQLException { + MariaDbPoolDataSource msource = new MariaDbPoolDataSource(); + msource.setServerName(config.getString("host")); + msource.setPortNumber(config.getInt("port")); + msource.setDatabaseName(databaseName); + msource.setUser(config.getString("username")); + msource.setPassword(config.getString("password")); + + msource.setPoolName("beautyquests"); + msource.setMaxIdleTime(60); + msource.setLoginTimeout(20); + return msource; } + private DataSource newMysqlSource(ConfigurationSection config) throws SQLException { + MysqlDataSource msource = new MysqlDataSource(); + msource.setServerName(config.getString("host")); + msource.setPortNumber(config.getInt("port")); + msource.setDatabaseName(databaseName); + msource.setUser(config.getString("username")); + msource.setPassword(config.getString("password")); + + msource.setConnectTimeout(20); + boolean ssl = config.getBoolean("ssl"); + msource.setVerifyServerCertificate(ssl); + msource.setUseSSL(ssl); + return msource; + } + + // Yes, I know there is literally the same code twice. + // Unfortunately, there is no common interface + // between MariaDB and MySQL pool data source + // which provides the configuration methods. + public void testConnection() throws SQLException { DebugUtils.logMessage("Trying to connect to " + config.getString("host")); try (Connection connection = source.getConnection()) { diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index 035291bb..1380dded 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -24,6 +24,7 @@ database: username: "unknown" password: "unknown" ssl: false + forceMysql: false tables: playerAccounts: "player_accounts" playerQuests: "player_quests" diff --git a/v1_19_R1/dependency-reduced-pom.xml b/v1_19_R1/dependency-reduced-pom.xml new file mode 100644 index 00000000..a972d4a8 --- /dev/null +++ b/v1_19_R1/dependency-reduced-pom.xml @@ -0,0 +1,93 @@ + + + + beautyquests-parent + fr.skytasul + 0.20.0-SNAPSHOT + + 4.0.0 + beautyquests-v1_19_R1 + + + + net.md-5 + specialsource-maven-plugin + 1.2.4 + + + remap-obf + package + + remap + + + org.spigotmc:minecraft-server:1.19.2-R0.1-SNAPSHOT:txt:maps-mojang + true + org.spigotmc:spigot:1.19.2-R0.1-SNAPSHOT:jar:remapped-mojang + true + remapped-obf + + + + remap-spigot + package + + remap + + + ${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar + org.spigotmc:minecraft-server:1.19.2-R0.1-SNAPSHOT:csrg:maps-spigot + org.spigotmc:spigot:1.19.2-R0.1-SNAPSHOT:jar:remapped-obf + + + + + + maven-shade-plugin + 3.3.0 + + + package + + shade + + + + + + + revxrsal.commands + fr.skytasul.quests.commands.revxrsal + + + + + + + + + jitpack.io + https://jitpack.io + + + + + fr.skytasul + beautyquests-core + 0.20.0-SNAPSHOT + provided + + + org.spigotmc + spigot + 1.19.2-R0.1-SNAPSHOT + remapped-mojang + provided + + + + 17 + 17 + true + + diff --git a/v1_19_R1/pom.xml b/v1_19_R1/pom.xml index 0b4ba97c..69c985bc 100644 --- a/v1_19_R1/pom.xml +++ b/v1_19_R1/pom.xml @@ -17,11 +17,19 @@ 17 + + + jitpack.io + https://jitpack.io + + + fr.skytasul beautyquests-core ${project.version} + provided org.spigotmc @@ -30,6 +38,11 @@ remapped-mojang provided + + com.github.Revxrsal.Lamp + paper-internal-dont-use + d72483065c + @@ -67,6 +80,27 @@ + + org.apache.maven.plugins + maven-shade-plugin + 3.3.0 + + + + revxrsal.commands + fr.skytasul.quests.commands.revxrsal + + + + + + package + + shade + + + + From af2048061db7b25aa09004c80ccb073451059dc0 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 11 Sep 2022 19:11:54 +0200 Subject: [PATCH 068/111] :sparkles: Sounds of messages are now cutted when messages get skipped --- .../quests/utils/types/DialogRunner.java | 16 ++++++--- .../skytasul/quests/utils/types/Message.java | 36 +++++++++++++------ 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/utils/types/DialogRunner.java b/core/src/main/java/fr/skytasul/quests/utils/types/DialogRunner.java index e7ede0d8..c411705a 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/types/DialogRunner.java +++ b/core/src/main/java/fr/skytasul/quests/utils/types/DialogRunner.java @@ -154,10 +154,12 @@ private boolean send(Player p, PlayerStatus status) { if (dialog.messages.isEmpty()) return true; int id = ++status.lastId; - if (id == dialog.messages.size()) { - // dialog ended correctly - return true; - } + boolean endOfDialog = id == dialog.messages.size(); + + if (status.runningMsg != null) status.runningMsg.finished(p, endOfDialog); + if (status.runningMsgTask != null) status.runningMsgTask.cancel(); + + if (endOfDialog) return true; Message msg = dialog.messages.get(id); if (msg == null) { @@ -165,9 +167,11 @@ private boolean send(Player p, PlayerStatus status) { return true; } + status.runningMsg = msg; DialogSendMessageEvent event = new DialogSendMessageEvent(dialog, msg, npc, p); Bukkit.getPluginManager().callEvent(event); - if (!event.isCancelled()) msg.sendMessage(p, dialog.getNPCName(npc), id, dialog.messages.size()); + if (!event.isCancelled()) + status.runningMsgTask = msg.sendMessage(p, dialog.getNPCName(npc), id, dialog.messages.size()); return false; } @@ -221,6 +225,8 @@ public void unload() { class PlayerStatus { int lastId = -1; BukkitTask task = null; + BukkitTask runningMsgTask = null; + Message runningMsg = null; void cancel() { if (task != null) { diff --git a/core/src/main/java/fr/skytasul/quests/utils/types/Message.java b/core/src/main/java/fr/skytasul/quests/utils/types/Message.java index 200196fb..6788f98b 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/types/Message.java +++ b/core/src/main/java/fr/skytasul/quests/utils/types/Message.java @@ -6,6 +6,7 @@ import org.apache.commons.lang.StringUtils; import org.bukkit.entity.Player; import org.bukkit.scheduler.BukkitRunnable; +import org.bukkit.scheduler.BukkitTask; import fr.skytasul.quests.BeautyQuests; import fr.skytasul.quests.QuestsConfiguration; @@ -31,13 +32,15 @@ public int getWaitTime() { return wait == -1 ? QuestsConfiguration.getDialogsConfig().getDefaultTime() : wait; } - public void sendMessage(Player p, String npc, int id, int size) { + public BukkitTask sendMessage(Player p, String npc, int id, int size) { + BukkitTask task = null; + String sent = formatMessage(p, npc, id, size); if (QuestsConfiguration.getDialogsConfig().sendInActionBar()) { BaseComponent[] components = TextComponent.fromLegacyText(sent.replace("{nl}", " ")); p.spigot().sendMessage(ChatMessageType.ACTION_BAR, components); if (getWaitTime() > 60) { - new BukkitRunnable() { + task = new BukkitRunnable() { int time = 40; @Override @@ -56,18 +59,25 @@ public void run() { }else p.sendMessage(StringUtils.splitByWholeSeparator(sent, "{nl}")); if (!"none".equals(sound)) { - String sentSound = sound; - if (sentSound == null) { - if (sender == Sender.PLAYER) { - sentSound = QuestsConfiguration.getDialogsConfig().getDefaultPlayerSound(); - }else if (sender == Sender.NPC) { - sentSound = QuestsConfiguration.getDialogsConfig().getDefaultNPCSound(); - } - } + String sentSound = getSound(); if (sentSound != null) p.playSound(p.getLocation(), sentSound, 1, 1); } + + return task; } + private String getSound() { + String sentSound = sound; + if (sentSound == null) { + if (sender == Sender.PLAYER) { + sentSound = QuestsConfiguration.getDialogsConfig().getDefaultPlayerSound(); + }else if (sender == Sender.NPC) { + sentSound = QuestsConfiguration.getDialogsConfig().getDefaultNPCSound(); + } + } + return sentSound; + } + public String formatMessage(Player p, String npc, int id, int size) { String sent = null; switch (sender) { @@ -84,6 +94,12 @@ public String formatMessage(Player p, String npc, int id, int size) { return sent; } + public void finished(Player p, boolean endOfDialog) { + if (endOfDialog) return; + String sentSound = getSound(); + if (sentSound != null) p.stopSound(sentSound); + } + @Override public Message clone() { Message clone = new Message(text, sender); From 2a298a228963a9c248c753fd0f2bc79b723868fa Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Tue, 13 Sep 2022 18:34:44 +0200 Subject: [PATCH 069/111] :alien: Updated BlueMap integration --- core/pom.xml | 2 +- .../utils/compatibility/maps/BQBlueMap.java | 73 +++++++------------ 2 files changed, 29 insertions(+), 46 deletions(-) diff --git a/core/pom.xml b/core/pom.xml index ec9f84e5..411f022e 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -253,7 +253,7 @@ com.github.BlueMap-Minecraft BlueMapAPI - v1.7.0 + v2.1.0 provided diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/maps/BQBlueMap.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/maps/BQBlueMap.java index ad48bcab..2d942109 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/compatibility/maps/BQBlueMap.java +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/maps/BQBlueMap.java @@ -1,6 +1,5 @@ package fr.skytasul.quests.utils.compatibility.maps; -import java.io.IOException; import java.util.function.Consumer; import org.bukkit.Location; @@ -15,30 +14,29 @@ import de.bluecolored.bluemap.api.BlueMapAPI; import de.bluecolored.bluemap.api.BlueMapMap; import de.bluecolored.bluemap.api.BlueMapWorld; -import de.bluecolored.bluemap.api.marker.MarkerAPI; -import de.bluecolored.bluemap.api.marker.MarkerSet; -import de.bluecolored.bluemap.api.marker.POIMarker; +import de.bluecolored.bluemap.api.markers.MarkerSet; +import de.bluecolored.bluemap.api.markers.POIMarker; public class BQBlueMap extends AbstractMapIntegration { private static final String MARKERSET_ID = "beautyquests.markerset"; private Consumer enableConsumer; + private MarkerSet set; @Override protected void initializeMarkers(Runnable initializeQuests) { BlueMapAPI.onEnable(enableConsumer = api -> { try { - MarkerAPI markerAPI = api.getMarkerAPI(); - MarkerSet set = markerAPI.createMarkerSet(MARKERSET_ID); - set.setLabel(QuestsConfiguration.dynmapSetName()); - set.setToggleable(true); - set.setDefaultHidden(false); - markerAPI.save(); + set = MarkerSet.builder() + .label(QuestsConfiguration.dynmapSetName()) + .defaultHidden(false) + .toggleable(true) + .build(); DebugUtils.logMessage("Enabled BlueMap integration."); initializeQuests.run(); - }catch (IOException e) { + }catch (Exception e) { BeautyQuests.logger.severe("An error occurred while loading BlueMap integration.", e); QuestsAPI.unregisterQuestsHandler(this); } @@ -49,52 +47,37 @@ protected void initializeMarkers(Runnable initializeQuests) { public void unload() { BlueMapAPI.unregisterListener(enableConsumer); BlueMapAPI.getInstance().ifPresent(api -> { - try { - api.getMarkerAPI().removeMarkerSet(MARKERSET_ID); - }catch (IOException e) { - e.printStackTrace(); - } + api.getMaps().forEach(map -> map.getMarkerSets().remove(MARKERSET_ID)); }); } @Override protected void addMarker(Quest quest, Location lc) { BlueMapAPI.getInstance().ifPresent(api -> { - try { - MarkerAPI markerAPI = api.getMarkerAPI(); - markerAPI.getMarkerSet(MARKERSET_ID).ifPresent(set -> { - api.getWorld(lc.getWorld().getUID()).map(BlueMapWorld::getMaps).ifPresent(maps -> { - int i = 0; - for (BlueMapMap map : maps) { - POIMarker marker = set.createPOIMarker("qu_" + quest.getID() + "_" + i++, map, lc.getX(), lc.getY(), lc.getZ()); - marker.setLabel(quest.getName()); - marker.setIcon(QuestsConfiguration.dynmapMarkerIcon(), 0, 0); - } - DebugUtils.logMessage("Added " + i + " BlueMap markers for quest " + quest.getID()); - }); - }); - markerAPI.save(); - }catch (IOException e) { - e.printStackTrace(); - } + api.getWorld(lc.getWorld()).map(BlueMapWorld::getMaps).ifPresent(maps -> { + int i = 0; + for (BlueMapMap map : maps) { + POIMarker marker = POIMarker.toBuilder() + .label(quest.getName()) + .icon(QuestsConfiguration.dynmapMarkerIcon(), 0, 0) + .build(); + set.getMarkers().put("qu_" + quest.getID() + "_" + i++, marker); + map.getMarkerSets().putIfAbsent(MARKERSET_ID, set); + } + DebugUtils.logMessage("Added " + i + " BlueMap markers for quest " + quest.getID()); + }); }); } @Override public void removeMarker(Quest quest) { BlueMapAPI.getInstance().ifPresent(api -> { - try { - api.getMarkerAPI().getMarkerSet(MARKERSET_ID).ifPresent(set -> { - Location lc = quest.getOptionValueOrDef(OptionStarterNPC.class).getLocation(); - api.getWorld(lc.getWorld().getUID()).map(BlueMapWorld::getMaps).ifPresent(maps -> { - for (int i = 0; i < maps.size(); i++) { - set.removeMarker("qu_" + quest.getID() + "_" + i++); - } - }); - }); - }catch (IOException e) { - e.printStackTrace(); - } + Location lc = quest.getOptionValueOrDef(OptionStarterNPC.class).getLocation(); + api.getWorld(lc.getWorld()).map(BlueMapWorld::getMaps).ifPresent(maps -> { + for (int i = 0; i < maps.size(); i++) { + set.getMarkers().remove("qu_" + quest.getID() + "_" + i); + } + }); }); } From 608ec2b8f600e74eb01100dc5a59ac68b9f37c7d Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Tue, 13 Sep 2022 18:36:05 +0200 Subject: [PATCH 070/111] :bug: Fixed timer not being tested in "/quests start" --- .../fr/skytasul/quests/commands/CommandsPlayerManagement.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/fr/skytasul/quests/commands/CommandsPlayerManagement.java b/core/src/main/java/fr/skytasul/quests/commands/CommandsPlayerManagement.java index 982a6d66..464566d9 100644 --- a/core/src/main/java/fr/skytasul/quests/commands/CommandsPlayerManagement.java +++ b/core/src/main/java/fr/skytasul/quests/commands/CommandsPlayerManagement.java @@ -268,7 +268,7 @@ public void start(BukkitCommandActor actor, ExecutableCommand command, EntitySel } private void start(CommandSender sender, Player player, PlayerAccount acc, Quest quest, boolean overrideRequirements) { - if (!overrideRequirements && !quest.isLauncheable(player, acc, true)) { + if (!overrideRequirements && !(quest.isLauncheable(player, acc, true) && quest.testTimer(acc, true))) { Lang.START_QUEST_NO_REQUIREMENT.send(sender, quest.getName()); return; } From 32c4963f493620a90489e0a9ca7dc0fd8e2a3bae Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 25 Sep 2022 17:46:11 +0200 Subject: [PATCH 071/111] :loud_sound: Logged illegal account retrieval + fixed README maven config --- README.md | 4 ++-- .../main/java/fr/skytasul/quests/players/PlayersManager.java | 1 + .../main/java/fr/skytasul/quests/structure/QuestBranch.java | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3476daf3..e5e5404e 100644 --- a/README.md +++ b/README.md @@ -51,12 +51,12 @@ In *pom.xml*, add this to the `repositories` section: https://repo.codemc.org/repository/maven-public ``` -And add this to the `dependencies` section: (replace VERSION by whatever version you want, i.e. `0.19.2`, `0.19.3-SNAPSHOT`...) +And add this to the `dependencies` section: (replace VERSION by whatever version you want, i.e. `0.19.7`, `0.20-SNAPSHOT`...) ```xml fr.skytasul beautyquests-core VERSION - compile + provided ``` diff --git a/core/src/main/java/fr/skytasul/quests/players/PlayersManager.java b/core/src/main/java/fr/skytasul/quests/players/PlayersManager.java index d4c7abed..d1a5ad57 100644 --- a/core/src/main/java/fr/skytasul/quests/players/PlayersManager.java +++ b/core/src/main/java/fr/skytasul/quests/players/PlayersManager.java @@ -164,6 +164,7 @@ public static synchronized void unloadPlayer(Player p) { public static PlayerAccount getPlayerAccount(Player p) { if (QuestsAPI.getNPCsManager().isNPC(p)) return null; + if (!p.isOnline()) BeautyQuests.logger.severe("Trying to fetch the account of an offline player (" + p.getName() + ")"); return cachedAccounts.get(p); } diff --git a/core/src/main/java/fr/skytasul/quests/structure/QuestBranch.java b/core/src/main/java/fr/skytasul/quests/structure/QuestBranch.java index e0f88753..c84d6541 100644 --- a/core/src/main/java/fr/skytasul/quests/structure/QuestBranch.java +++ b/core/src/main/java/fr/skytasul/quests/structure/QuestBranch.java @@ -157,7 +157,7 @@ public void start(PlayerAccount acc){ } public void finishStage(Player p, AbstractStage stage){ - DebugUtils.logMessage("Next stage for player " + p.getName() + ", via " + DebugUtils.stackTraces(2, 4)); + DebugUtils.logMessage("Next stage for player " + p.getName() + " (coming from " + stage.toString() + ") via " + DebugUtils.stackTraces(1, 3)); PlayerAccount acc = PlayersManager.getPlayerAccount(p); PlayerQuestDatas datas = acc.getQuestDatas(getQuest()); if (datas.getBranch() != getID() || (datas.isInEndingStages() && isRegularStage(stage)) || (!datas.isInEndingStages() && datas.getStage() != stage.getID())) { From 1ad61a7c8fc3af92f95880c4b3483290b673f4f0 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Wed, 28 Sep 2022 20:53:32 +0200 Subject: [PATCH 072/111] :sparkles: "Craft items" stage now works for furnance and smithing table --- .../fr/skytasul/quests/stages/StageCraft.java | 34 ++++++++++++++++--- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageCraft.java b/core/src/main/java/fr/skytasul/quests/stages/StageCraft.java index f22e0f06..cebd3486 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageCraft.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageCraft.java @@ -9,6 +9,9 @@ import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.inventory.CraftItemEvent; +import org.bukkit.event.inventory.FurnaceExtractEvent; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.SmithItemEvent; import org.bukkit.inventory.ComplexRecipe; import org.bukkit.inventory.CraftingInventory; import org.bukkit.inventory.Inventory; @@ -49,10 +52,8 @@ public ItemStack getItem(){ return result; } - @EventHandler (priority = EventPriority.MONITOR) + @EventHandler (priority = EventPriority.MONITOR, ignoreCancelled = true) public void onCraft(CraftItemEvent e){ - Player p = (Player) e.getView().getPlayer(); - PlayerAccount acc = PlayersManager.getPlayerAccount(p); ItemStack item = e.getRecipe().getResult(); if (item.getType() == Material.AIR && e.getRecipe() instanceof ComplexRecipe) { String key = ((ComplexRecipe) e.getRecipe()).getKey().toString(); @@ -61,6 +62,32 @@ public void onCraft(CraftItemEvent e){ } } + onCraftClick(e, item, getMaxCraftAmount(e.getInventory())); + } + + @EventHandler (priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onSmith(SmithItemEvent event) { + onCraftClick(event, event.getCurrentItem(), event.getCurrentItem().getAmount()); + } + + @EventHandler (priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onFurnaceExtract(FurnaceExtractEvent event) { + Player p = event.getPlayer(); + PlayerAccount acc = PlayersManager.getPlayerAccount(p); + + if (comparisons.isSimilar(result, new ItemStack(event.getItemType())) && branch.hasStageLaunched(acc, this) && canUpdate(p, true)) { + int amount = getPlayerAmount(acc) - event.getItemAmount(); + if (amount <= 0) { + finishStage(p); + }else { + updateObjective(acc, p, "amount", amount); + } + } + } + + public void onCraftClick(InventoryClickEvent e, ItemStack item, int maxCraftable) { + Player p = (Player) e.getView().getPlayer(); + PlayerAccount acc = PlayersManager.getPlayerAccount(p); if (branch.hasStageLaunched(acc, this) && canUpdate(p)) { if (comparisons.isSimilar(result, item)) { @@ -83,7 +110,6 @@ public void onCraft(CraftItemEvent e){ case SHIFT_LEFT: if (recipeAmount == 0) break; - int maxCraftable = getMaxCraftAmount(e.getInventory()); int capacity = fits(item, e.getView().getBottomInventory()); // If we can't fit everything, increase "space" to include the items dropped by crafting From 959f0e0eaac0f4914ed68f057aa6ae3051824bda Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Wed, 28 Sep 2022 20:55:17 +0200 Subject: [PATCH 073/111] :bug: Fixed bugs on quests launched automatically on first join --- .../main/java/fr/skytasul/quests/api/stages/AbstractStage.java | 1 + core/src/main/java/fr/skytasul/quests/stages/StagePlayTime.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java index 05c376cd..8757bcd7 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/AbstractStage.java @@ -283,6 +283,7 @@ public void load() { @EventHandler public void onJoin(PlayerAccountJoinEvent e) { + if (e.isFirstJoin()) return; if (branch.hasStageLaunched(e.getPlayerAccount(), this)) { joins(e.getPlayerAccount(), e.getPlayer()); } diff --git a/core/src/main/java/fr/skytasul/quests/stages/StagePlayTime.java b/core/src/main/java/fr/skytasul/quests/stages/StagePlayTime.java index d8a39ace..df518d9f 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StagePlayTime.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StagePlayTime.java @@ -57,7 +57,7 @@ private long getRemaining(PlayerAccount acc) { } private void launchTask(PlayerAccount acc, Player p, long remaining) { - tasks.put(acc, Bukkit.getScheduler().runTaskLater(BeautyQuests.getInstance(), () -> finishStage(p), remaining < 0 ? 0 : remaining)); + tasks.put(acc, Bukkit.getScheduler().runTaskLater(BeautyQuests.getInstance(), () -> branch.finishStage(p, this), remaining < 0 ? 0 : remaining)); } @Override From 9dc9b243dcb9e4f6e5b1b6eea800d91f733313b7 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Wed, 28 Sep 2022 20:57:54 +0200 Subject: [PATCH 074/111] :card_file_box: Made database data migration asynchronous --- .../quests/players/PlayersManagerDB.java | 97 ++++++++++++------- 1 file changed, 60 insertions(+), 37 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java b/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java index 245a3616..c3f75ba3 100644 --- a/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java +++ b/core/src/main/java/fr/skytasul/quests/players/PlayersManagerDB.java @@ -33,6 +33,7 @@ import fr.skytasul.quests.utils.Database; import fr.skytasul.quests.utils.DebugUtils; import fr.skytasul.quests.utils.ThrowingConsumer; +import fr.skytasul.quests.utils.Utils; public class PlayersManagerDB extends PlayersManager { @@ -292,7 +293,7 @@ public void load() { updatePoolLastGive = "UPDATE " + POOLS_DATAS_TABLE + " SET `last_give` = ? WHERE `account_id` = ? AND `pool_id` = ?"; updatePoolCompletedQuests = "UPDATE " + POOLS_DATAS_TABLE + " SET `completed_quests` = ? WHERE `account_id` = ? AND `pool_id` = ?"; }catch (SQLException e) { - e.printStackTrace(); + throw new RuntimeException(e); } } @@ -343,44 +344,15 @@ private void createTables() throws SQLException { BeautyQuests.logger.info("Updated database with quest_flow column."); } - if (!columns.contains("additional_datas")) { // 0.20 - statement.execute("ALTER TABLE " + QUESTS_DATAS_TABLE - + " ADD COLUMN `additional_datas` longtext DEFAULT NULL AFTER `current_stage`"); - BeautyQuests.logger.info("Updated table " + QUESTS_DATAS_TABLE + " with additional_datas column."); - - BeautyQuests.logger.info("Migrating old datas..."); - PreparedStatement migration = connection.prepareStatement("UPDATE " + QUESTS_DATAS_TABLE + " SET `additional_datas` = ? WHERE `id` = ?"); - ResultSet result = statement.executeQuery("SELECT `id`, `stage_0_datas`, `stage_1_datas`, `stage_2_datas`, `stage_3_datas`, `stage_4_datas` FROM " + QUESTS_DATAS_TABLE); - while (result.next()) { - Map datas = new HashMap<>(); - for (int i = 0; i < 5; i++) { - String stageDatas = result.getString("stage_" + i + "_datas"); - if (stageDatas != null && !"{}".equals(stageDatas)) datas.put("stage" + i, CustomizedObjectTypeAdapter.deserializeNullable(stageDatas, Map.class)); - } - - if (datas.isEmpty()) continue; - migration.setString(1, CustomizedObjectTypeAdapter.serializeNullable(datas)); - migration.setInt(2, result.getInt("id")); - migration.addBatch(); + if (!columns.contains("additional_datas") || columns.contains("stage_0_datas")) { // 0.20 + // tests for stage_0_datas: it's in the case the server crashed/stopped during the migration process. + if (!columns.contains("additional_datas")) { + statement.execute("ALTER TABLE " + QUESTS_DATAS_TABLE + + " ADD COLUMN `additional_datas` longtext DEFAULT NULL AFTER `current_stage`"); + BeautyQuests.logger.info("Updated table " + QUESTS_DATAS_TABLE + " with additional_datas column."); } - int migrated = migration.executeBatch().length; - BeautyQuests.logger.info("Migrated " + migrated + " quest datas."); - - statement.execute("ALTER TABLE " + QUESTS_DATAS_TABLE - + " DROP COLUMN `stage_0_datas`," - + " DROP COLUMN `stage_1_datas`," - + " DROP COLUMN `stage_2_datas`," - + " DROP COLUMN `stage_3_datas`," - + " DROP COLUMN `stage_4_datas`;"); - BeautyQuests.logger.info("Updated database by deleting old stage_[0::4]_datas columns."); - int deletedDuplicates = - statement.executeUpdate("DELETE R1 FROM " + QUESTS_DATAS_TABLE + " R1" - + " JOIN " + QUESTS_DATAS_TABLE + " R2" - + " ON R1.account_id = R2.account_id" - + " AND R1.quest_id = R2.quest_id" - + " AND R1.id < R2.id;"); - if (deletedDuplicates > 0) BeautyQuests.logger.info("Deleted " + deletedDuplicates + " duplicated rows in the " + QUESTS_DATAS_TABLE + " table."); + Utils.runAsync(this::migrateOldQuestDatas); } }); @@ -410,6 +382,57 @@ private void upgradeTable(Connection connection, String tableName, ThrowingConsu } } + private void migrateOldQuestDatas() { + BeautyQuests.logger.info("---- CAUTION ----\n" + + "BeautyQuests will now migrate old quest datas in database to the newest format.\n" + + "This may take a LONG time. Players should NOT enter the server during this time, " + + "or serious data loss can occur."); + + try (Connection connection = db.getConnection(); Statement statement = connection.createStatement()) { + + int deletedDuplicates = + statement.executeUpdate("DELETE R1 FROM " + QUESTS_DATAS_TABLE + " R1" + + " JOIN " + QUESTS_DATAS_TABLE + " R2" + + " ON R1.account_id = R2.account_id" + + " AND R1.quest_id = R2.quest_id" + + " AND R1.id < R2.id;"); + if (deletedDuplicates > 0) BeautyQuests.logger.info("Deleted " + deletedDuplicates + " duplicated rows in the " + QUESTS_DATAS_TABLE + " table."); + + int batchCount = 0; + PreparedStatement migration = connection.prepareStatement("UPDATE " + QUESTS_DATAS_TABLE + " SET `additional_datas` = ? WHERE `id` = ?"); + ResultSet result = statement.executeQuery("SELECT `id`, `stage_0_datas`, `stage_1_datas`, `stage_2_datas`, `stage_3_datas`, `stage_4_datas` FROM " + QUESTS_DATAS_TABLE); + while (result.next()) { + Map datas = new HashMap<>(); + for (int i = 0; i < 5; i++) { + String stageDatas = result.getString("stage_" + i + "_datas"); + if (stageDatas != null && !"{}".equals(stageDatas)) datas.put("stage" + i, CustomizedObjectTypeAdapter.deserializeNullable(stageDatas, Map.class)); + } + + if (datas.isEmpty()) continue; + migration.setString(1, CustomizedObjectTypeAdapter.serializeNullable(datas)); + migration.setInt(2, result.getInt("id")); + migration.addBatch(); + batchCount++; + } + BeautyQuests.logger.info("Migrating " + batchCount + "quest datas..."); + int migrated = migration.executeBatch().length; + BeautyQuests.logger.info("Migrated " + migrated + " quest datas."); + + statement.execute("ALTER TABLE " + QUESTS_DATAS_TABLE + + " DROP COLUMN `stage_0_datas`," + + " DROP COLUMN `stage_1_datas`," + + " DROP COLUMN `stage_2_datas`," + + " DROP COLUMN `stage_3_datas`," + + " DROP COLUMN `stage_4_datas`;"); + BeautyQuests.logger.info("Updated database by deleting old stage_[0::4]_datas columns."); + BeautyQuests.logger.info("---- CAUTION ----\n" + + "The data migration succeeded. Players can now safely connect."); + }catch (SQLException ex) { + BeautyQuests.logger.severe("---- CAUTION ----\n" + + "The plugin failed to migrate old quest datas in database.", ex); + } + } + public static synchronized String migrate(Database db, PlayersManagerYAML yaml) throws SQLException { try (Connection connection = db.getConnection()) { ResultSet result = connection.getMetaData().getTables(null, null, "%", null); From c4c173f2fd7ff82bb047301bb76dc9a17faf8821 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Fri, 30 Sep 2022 21:26:40 +0200 Subject: [PATCH 075/111] :loud_sound: Made commands logged --- .../quests/commands/CommandsManager.java | 12 ++++++---- .../skytasul/quests/editors/DialogEditor.java | 24 +++++++------------ 2 files changed, 17 insertions(+), 19 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/commands/CommandsManager.java b/core/src/main/java/fr/skytasul/quests/commands/CommandsManager.java index 27b8c204..fb70b22e 100644 --- a/core/src/main/java/fr/skytasul/quests/commands/CommandsManager.java +++ b/core/src/main/java/fr/skytasul/quests/commands/CommandsManager.java @@ -22,7 +22,7 @@ import revxrsal.commands.autocomplete.SuggestionProvider; import revxrsal.commands.bukkit.BukkitCommandActor; -import revxrsal.commands.bukkit.core.BukkitHandler; +import revxrsal.commands.bukkit.BukkitCommandHandler; import revxrsal.commands.command.CommandActor; import revxrsal.commands.command.ExecutableCommand; import revxrsal.commands.exception.CommandErrorException; @@ -33,11 +33,11 @@ public class CommandsManager { private static String[] COMMAND_ALIASES = { "quests", "quest", "bq", "beautyquests", "bquests" }; - private BukkitHandler handler; + private BukkitCommandHandler handler; private boolean locked = false; public CommandsManager() { - handler = new BukkitHandler(BeautyQuests.getInstance()); + handler = BukkitCommandHandler.create(BeautyQuests.getInstance()); handler.setMessagePrefix(QuestsConfiguration.getPrefix()); handler.failOnTooManyArguments(); @@ -102,9 +102,13 @@ public CommandsManager() { handler.registerContextResolver(Scoreboard.class, context -> { return BeautyQuests.getInstance().getScoreboardManager().getPlayerScoreboard(context.getResolvedArgument(Player.class)); }); + + handler.registerCondition((actor, command, arguments) -> { + DebugUtils.logMessage(actor.getName() + " executed command: " + command.getPath().toRealString() + " " + String.join(" ", arguments)); + }); } - public BukkitHandler getHandler() { + public BukkitCommandHandler getHandler() { return handler; } diff --git a/core/src/main/java/fr/skytasul/quests/editors/DialogEditor.java b/core/src/main/java/fr/skytasul/quests/editors/DialogEditor.java index d35a1119..b1a6427b 100644 --- a/core/src/main/java/fr/skytasul/quests/editors/DialogEditor.java +++ b/core/src/main/java/fr/skytasul/quests/editors/DialogEditor.java @@ -95,15 +95,13 @@ public boolean chat(String coloredMessage, String strippedMessage){ } try{ Message message = d.messages.get(Integer.parseInt(args[1])); - if (message == null) { - Lang.OUT_OF_BOUNDS.send(p, args[1], 0, d.messages.size()); - }else { - msg = Utils.buildFromArray(argsColored, 2, " "); - message.text = msg; - Lang.DIALOG_MSG_EDITED.send(p, msg); - } + msg = Utils.buildFromArray(argsColored, 2, " "); + message.text = msg; + Lang.DIALOG_MSG_EDITED.send(p, msg); }catch (IllegalArgumentException ex){ Utils.sendMessage(p, Lang.NUMBER_INVALID.toString()); + }catch (IndexOutOfBoundsException ex) { + Lang.OBJECT_DOESNT_EXIST.send(p, args[1]); } break; @@ -114,13 +112,11 @@ public boolean chat(String coloredMessage, String strippedMessage){ } try{ Message imsg = d.messages.get(Integer.parseInt(args[1])); - if (imsg == null){ - Lang.OBJECT_DOESNT_EXIST.send(p, args[1]); - break; - } Lang.DIALOG_SOUND_ADDED.send(p, imsg.sound = args[2], args[1]); }catch (IllegalArgumentException ex){ Utils.sendMessage(p, Lang.NUMBER_INVALID.toString()); + }catch (IndexOutOfBoundsException ex) { + Lang.OBJECT_DOESNT_EXIST.send(p, args[1]); } break; @@ -131,10 +127,6 @@ public boolean chat(String coloredMessage, String strippedMessage){ } try { Message imsg = d.messages.get(Integer.parseInt(args[1])); - if (imsg == null) { - Lang.OBJECT_DOESNT_EXIST.send(p, args[1]); - break; - } int time = Integer.parseInt(args[2]); if (time < 0) { imsg.wait = -1; @@ -145,6 +137,8 @@ public boolean chat(String coloredMessage, String strippedMessage){ } }catch (IllegalArgumentException ex) { Utils.sendMessage(p, Lang.NUMBER_INVALID.toString()); + }catch (IndexOutOfBoundsException ex) { + Lang.OBJECT_DOESNT_EXIST.send(p, args[1]); } break; From e380f528c8692b791f352c4ab0fd9f4d1d763d4e Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 2 Oct 2022 11:28:15 +0200 Subject: [PATCH 076/111] :card_file_box: Now using HikariCP for database connection --- core/pom.xml | 18 ++--- .../java/fr/skytasul/quests/BeautyQuests.java | 8 +- .../fr/skytasul/quests/utils/Database.java | 80 +++++-------------- core/src/main/resources/config.yml | 1 - core/src/main/resources/hikari.properties | 10 +++ core/src/main/resources/plugin.yml | 3 - 6 files changed, 43 insertions(+), 77 deletions(-) create mode 100644 core/src/main/resources/hikari.properties diff --git a/core/pom.xml b/core/pom.xml index 411f022e..531802ff 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -20,6 +20,7 @@ *.yml locales/*.yml + *.properties @@ -142,12 +143,6 @@ - - mysql - mysql-connector-java - 8.0.29 - provided - io.papermc.paper paper-api @@ -268,12 +263,6 @@ 18.37.1 provided - - org.mariadb.jdbc - mariadb-java-client - 2.7.5 - provided - com.github.Revxrsal.Lamp bukkit @@ -284,6 +273,11 @@ common d72483065c + + com.zaxxer + HikariCP + 4.0.3 + diff --git a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java index 3ae650bf..6905eca8 100644 --- a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java +++ b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java @@ -348,11 +348,12 @@ private void loadConfigParameters(boolean init) throws LoadingException { ConfigurationSection dbConfig = config.getConfig().getConfigurationSection("database"); if (dbConfig.getBoolean("enabled")) { db = null; - try (Database newDB = new Database(dbConfig)) { - newDB.testConnection(); + try { + db = new Database(dbConfig); + db.testConnection(); logger.info("Connection to database etablished."); - db = newDB; }catch (Exception ex) { + db = null; throw new LoadingException("Connection to database has failed.", ex); } } @@ -548,6 +549,7 @@ public boolean createDataBackup(Path backup) { if (Files.exists(target)) { logger.warning("File " + target.toString() + " already exist. This should not happen."); }else { + Files.createDirectories(backup); logger.info("Datas backup created in " + Files.copy(dataFile.toPath(), target).getParent().getFileName()); } return true; diff --git a/core/src/main/java/fr/skytasul/quests/utils/Database.java b/core/src/main/java/fr/skytasul/quests/utils/Database.java index 01ffd1ed..aa29c1d3 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/Database.java +++ b/core/src/main/java/fr/skytasul/quests/utils/Database.java @@ -8,73 +8,38 @@ import javax.sql.DataSource; import org.bukkit.configuration.ConfigurationSection; -import org.mariadb.jdbc.MariaDbPoolDataSource; -import com.mysql.cj.jdbc.MysqlDataSource; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; import fr.skytasul.quests.BeautyQuests; public class Database implements Closeable { - private ConfigurationSection config; - private String databaseName; - - private DataSource source; + private final ConfigurationSection config; + private final String databaseName; + + private final DataSource source; - public Database(ConfigurationSection config) throws SQLException { + public Database(ConfigurationSection config) { this.config = config; this.databaseName = config.getString("database"); - if (config.getBoolean("forceMysql")) { - source = newMysqlSource(config); - }else { - try { - Class.forName("org.mariadb.jdbc.MariaDbPoolDataSource"); - source = newMariadbSource(config); - }catch (ClassNotFoundException e) { - source = newMysqlSource(config); - } - } - DebugUtils.logMessage("Created SQL data source: " + source.getClass().getName()); - - } - - private DataSource newMariadbSource(ConfigurationSection config) throws SQLException { - MariaDbPoolDataSource msource = new MariaDbPoolDataSource(); - msource.setServerName(config.getString("host")); - msource.setPortNumber(config.getInt("port")); - msource.setDatabaseName(databaseName); - msource.setUser(config.getString("username")); - msource.setPassword(config.getString("password")); + HikariConfig hikariConfig = new HikariConfig("/hikari.properties"); + hikariConfig.setJdbcUrl("jdbc:mysql://" + config.getString("host") + ":" + config.getInt("port") + "/" + databaseName); + hikariConfig.setUsername(config.getString("username")); + hikariConfig.setPassword(config.getString("password")); + hikariConfig.setPoolName("BeautyQuests-SQL-pool"); + hikariConfig.setConnectionTimeout(20_000); - msource.setPoolName("beautyquests"); - msource.setMaxIdleTime(60); - msource.setLoginTimeout(20); - return msource; - } - - private DataSource newMysqlSource(ConfigurationSection config) throws SQLException { - MysqlDataSource msource = new MysqlDataSource(); - msource.setServerName(config.getString("host")); - msource.setPortNumber(config.getInt("port")); - msource.setDatabaseName(databaseName); - msource.setUser(config.getString("username")); - msource.setPassword(config.getString("password")); - - msource.setConnectTimeout(20); boolean ssl = config.getBoolean("ssl"); - msource.setVerifyServerCertificate(ssl); - msource.setUseSSL(ssl); - return msource; + hikariConfig.addDataSourceProperty("verifyServerCertificate", ssl); + hikariConfig.addDataSourceProperty("useSSL", ssl); + + source = new HikariDataSource(hikariConfig); } - // Yes, I know there is literally the same code twice. - // Unfortunately, there is no common interface - // between MariaDB and MySQL pool data source - // which provides the configuration methods. - public void testConnection() throws SQLException { - DebugUtils.logMessage("Trying to connect to " + config.getString("host")); try (Connection connection = source.getConnection()) { if (!connection.isValid(0)) throw new SQLException("Could not establish database connection."); @@ -91,12 +56,11 @@ public ConfigurationSection getConfig() { @Override public void close() { - if (source instanceof Closeable) { - try { - ((Closeable) source).close(); - }catch (IOException e) { - e.printStackTrace(); - } + BeautyQuests.logger.info("Closing database pool..."); + try { + ((Closeable) source).close(); + }catch (IOException ex) { + BeautyQuests.logger.severe("An error occurred while closing database pool.", ex); } } diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index 1380dded..035291bb 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -24,7 +24,6 @@ database: username: "unknown" password: "unknown" ssl: false - forceMysql: false tables: playerAccounts: "player_accounts" playerQuests: "player_quests" diff --git a/core/src/main/resources/hikari.properties b/core/src/main/resources/hikari.properties new file mode 100644 index 00000000..01d762db --- /dev/null +++ b/core/src/main/resources/hikari.properties @@ -0,0 +1,10 @@ +dataSource.cachePrepStmts=true +dataSource.prepStmtCacheSize=250 +dataSource.prepStmtCacheSqlLimit=2048 +dataSource.useServerPrepStmts=true +dataSource.useLocalSessionState=true +dataSource.rewriteBatchedStatements=true +dataSource.cacheResultSetMetadata=true +dataSource.cacheServerConfiguration=true +dataSource.elideSetAutoCommits=true +dataSource.maintainTimeStats=false \ No newline at end of file diff --git a/core/src/main/resources/plugin.yml b/core/src/main/resources/plugin.yml index ef5f40bd..b996a509 100644 --- a/core/src/main/resources/plugin.yml +++ b/core/src/main/resources/plugin.yml @@ -7,9 +7,6 @@ website: https://www.spigotmc.org/resources/beautyquests.39255/ api-version: 1.13 main: fr.skytasul.quests.BeautyQuests -libraries: -- org.mariadb.jdbc:mariadb-java-client:2.7.5 - softdepend: - WorldGuard - MythicMobs From 491704b2b71644bbe71fda80095fd6edd4f0c0dd Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 2 Oct 2022 11:29:05 +0200 Subject: [PATCH 077/111] :bug: Fixed dependencies being unloaded while not even initialized --- .../quests/utils/compatibility/DependenciesManager.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/DependenciesManager.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/DependenciesManager.java index 524152bf..73500f9e 100644 --- a/core/src/main/java/fr/skytasul/quests/utils/compatibility/DependenciesManager.java +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/DependenciesManager.java @@ -186,6 +186,7 @@ public static class BQDependency { private final Predicate isValid; private boolean enabled = false; private boolean forceDisable = false; + private boolean initialized = false; public BQDependency(String pluginName) { this(pluginName, null); @@ -226,6 +227,7 @@ boolean testCompatibility(boolean after) { void initialize() { try { if (initialize != null) initialize.run(); + initialized = true; }catch (Throwable ex) { BeautyQuests.logger.severe("An error occurred while initializing " + pluginNames.toString() + " integration", ex); enabled = false; @@ -236,7 +238,8 @@ public void disable() { forceDisable = true; if (enabled) { enabled = false; - if (disable != null) disable.run(); + if (disable != null && initialized) disable.run(); + initialized = false; } } From b3f342d5395fe8bd04fb15411a5e3d170980b3a9 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 2 Oct 2022 11:29:19 +0200 Subject: [PATCH 078/111] :bug: Fixed a NPE on Interact stages --- .../java/fr/skytasul/quests/stages/StageInteract.java | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java b/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java index 2d6b0234..87bea601 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageInteract.java @@ -16,6 +16,7 @@ import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; +import fr.skytasul.quests.BeautyQuests; import fr.skytasul.quests.api.options.QuestOption; import fr.skytasul.quests.api.stages.AbstractStage; import fr.skytasul.quests.api.stages.StageCreation; @@ -91,12 +92,19 @@ public Spliterator getNearbyLocated(NearbyFetcher fetcher) { public void onInteract(PlayerInteractEvent e){ if (e.getClickedBlock() == null) return; if (NMS.getMCVersion() >= 9 && e.getHand() != EquipmentSlot.HAND) return; + if (left){ if (e.getAction() != Action.LEFT_CLICK_BLOCK) return; }else if (e.getAction() != Action.RIGHT_CLICK_BLOCK) return; + if (lc != null) { if (!lc.equals(e.getClickedBlock().getLocation())) return; - }else if (!block.applies(e.getClickedBlock())) return; + }else if (block != null) { + if (!block.applies(e.getClickedBlock())) return; + }else { + BeautyQuests.logger.warning("No block nor location set for " + toString()); + return; + } Player p = e.getPlayer(); if (hasStarted(p) && canUpdate(p)) { From 9eeb9f8bce726574fc71ab832c853abf2dfc1153 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 2 Oct 2022 11:58:18 +0200 Subject: [PATCH 079/111] :ambulance: Fixed plugin not enabling on pre-1.16 servers due to the Smithing event not existing --- .../java/fr/skytasul/quests/BeautyQuests.java | 3 + .../fr/skytasul/quests/QuestsListener.java | 27 +++++++++ .../quests/api/events/BQCraftEvent.java | 45 ++++++++++++++ .../fr/skytasul/quests/stages/StageCraft.java | 60 ++++--------------- .../quests/utils/compatibility/Post1_16.java | 18 ++++++ 5 files changed, 106 insertions(+), 47 deletions(-) create mode 100644 core/src/main/java/fr/skytasul/quests/api/events/BQCraftEvent.java create mode 100644 core/src/main/java/fr/skytasul/quests/utils/compatibility/Post1_16.java diff --git a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java index 6905eca8..fd9894ef 100644 --- a/core/src/main/java/fr/skytasul/quests/BeautyQuests.java +++ b/core/src/main/java/fr/skytasul/quests/BeautyQuests.java @@ -53,6 +53,7 @@ import fr.skytasul.quests.utils.DebugUtils; import fr.skytasul.quests.utils.Lang; import fr.skytasul.quests.utils.compatibility.DependenciesManager; +import fr.skytasul.quests.utils.compatibility.Post1_16; import fr.skytasul.quests.utils.compatibility.mobs.BukkitEntityFactory; import fr.skytasul.quests.utils.logger.ILoggerHandler; import fr.skytasul.quests.utils.logger.LoggerExpanded; @@ -156,6 +157,8 @@ public void run() { + (((double) System.currentTimeMillis() - lastMillis) / 1000D) + "s)!"); getServer().getPluginManager().registerEvents(new QuestsListener(), BeautyQuests.this); + if (NMS.getMCVersion() >= 16) + getServer().getPluginManager().registerEvents(new Post1_16(), BeautyQuests.this); launchSaveCycle(); diff --git a/core/src/main/java/fr/skytasul/quests/QuestsListener.java b/core/src/main/java/fr/skytasul/quests/QuestsListener.java index c2828cf0..5ecb5576 100644 --- a/core/src/main/java/fr/skytasul/quests/QuestsListener.java +++ b/core/src/main/java/fr/skytasul/quests/QuestsListener.java @@ -7,6 +7,7 @@ import java.util.stream.Collectors; import org.bukkit.Bukkit; +import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -23,10 +24,12 @@ import org.bukkit.event.player.PlayerItemConsumeEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ComplexRecipe; import org.bukkit.inventory.ItemStack; import fr.skytasul.quests.api.QuestsAPI; import fr.skytasul.quests.api.events.BQBlockBreakEvent; +import fr.skytasul.quests.api.events.BQCraftEvent; import fr.skytasul.quests.api.events.BQNPCClickEvent; import fr.skytasul.quests.api.events.accounts.PlayerAccountJoinEvent; import fr.skytasul.quests.api.events.accounts.PlayerAccountLeaveEvent; @@ -211,5 +214,29 @@ public void onBreak(BlockBreakEvent e) { if (e.getPlayer() == null) return; Bukkit.getPluginManager().callEvent(new BQBlockBreakEvent(e.getPlayer(), Arrays.asList(e.getBlock()))); } + + @EventHandler (priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onCraftMonitor(CraftItemEvent e){ + if (e.getInventory().getResult() == null) return; + + int resultCount = e.getInventory().getResult().getAmount(); + int materialCount = Integer.MAX_VALUE; + + for (ItemStack is : e.getInventory().getMatrix()) + if (is != null && is.getAmount() < materialCount) + materialCount = is.getAmount(); + + int maxCraftAmount = resultCount * materialCount; + + ItemStack item = e.getRecipe().getResult(); + if (item.getType() == Material.AIR && e.getRecipe() instanceof ComplexRecipe) { + String key = ((ComplexRecipe) e.getRecipe()).getKey().toString(); + if (key.equals("minecraft:suspicious_stew")) { + item = XMaterial.SUSPICIOUS_STEW.parseItem(); + } + } + + Bukkit.getPluginManager().callEvent(new BQCraftEvent(e, item, maxCraftAmount)); + } } diff --git a/core/src/main/java/fr/skytasul/quests/api/events/BQCraftEvent.java b/core/src/main/java/fr/skytasul/quests/api/events/BQCraftEvent.java new file mode 100644 index 00000000..da6d2eed --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/api/events/BQCraftEvent.java @@ -0,0 +1,45 @@ +package fr.skytasul.quests.api.events; + +import org.bukkit.entity.Player; +import org.bukkit.event.HandlerList; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.player.PlayerEvent; +import org.bukkit.inventory.ItemStack; + +public class BQCraftEvent extends PlayerEvent { + + private final InventoryClickEvent clickEvent; + private final ItemStack result; + private final int maxCraftable; + + public BQCraftEvent(InventoryClickEvent clickEvent, ItemStack result, int maxCraftable) { + super((Player) clickEvent.getView().getPlayer()); + this.clickEvent = clickEvent; + this.result = result; + this.maxCraftable = maxCraftable; + } + + public InventoryClickEvent getClickEvent() { + return clickEvent; + } + + public ItemStack getResult() { + return result; + } + + public int getMaxCraftable() { + return maxCraftable; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + private static final HandlerList handlers = new HandlerList(); + +} diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageCraft.java b/core/src/main/java/fr/skytasul/quests/stages/StageCraft.java index cebd3486..60f72069 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageCraft.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageCraft.java @@ -8,17 +8,13 @@ import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; -import org.bukkit.event.inventory.CraftItemEvent; import org.bukkit.event.inventory.FurnaceExtractEvent; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.SmithItemEvent; -import org.bukkit.inventory.ComplexRecipe; -import org.bukkit.inventory.CraftingInventory; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import fr.skytasul.quests.QuestsConfiguration; import fr.skytasul.quests.api.comparison.ItemComparisonMap; +import fr.skytasul.quests.api.events.BQCraftEvent; import fr.skytasul.quests.api.stages.AbstractStage; import fr.skytasul.quests.api.stages.StageCreation; import fr.skytasul.quests.gui.ItemUtils; @@ -51,24 +47,6 @@ public StageCraft(QuestBranch branch, ItemStack result, ItemComparisonMap compar public ItemStack getItem(){ return result; } - - @EventHandler (priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onCraft(CraftItemEvent e){ - ItemStack item = e.getRecipe().getResult(); - if (item.getType() == Material.AIR && e.getRecipe() instanceof ComplexRecipe) { - String key = ((ComplexRecipe) e.getRecipe()).getKey().toString(); - if (key.equals("minecraft:suspicious_stew")) { - item = XMaterial.SUSPICIOUS_STEW.parseItem(); - } - } - - onCraftClick(e, item, getMaxCraftAmount(e.getInventory())); - } - - @EventHandler (priority = EventPriority.MONITOR, ignoreCancelled = true) - public void onSmith(SmithItemEvent event) { - onCraftClick(event, event.getCurrentItem(), event.getCurrentItem().getAmount()); - } @EventHandler (priority = EventPriority.MONITOR, ignoreCancelled = true) public void onFurnaceExtract(FurnaceExtractEvent event) { @@ -85,24 +63,26 @@ public void onFurnaceExtract(FurnaceExtractEvent event) { } } - public void onCraftClick(InventoryClickEvent e, ItemStack item, int maxCraftable) { - Player p = (Player) e.getView().getPlayer(); + @EventHandler + public void onCraft(BQCraftEvent event) { + Player p = event.getPlayer(); PlayerAccount acc = PlayersManager.getPlayerAccount(p); if (branch.hasStageLaunched(acc, this) && canUpdate(p)) { + ItemStack item = event.getResult(); if (comparisons.isSimilar(result, item)) { int recipeAmount = item.getAmount(); - switch (e.getClick()) { + switch (event.getClickEvent().getClick()) { case NUMBER_KEY: // If hotbar slot selected is full, crafting fails (vanilla behavior, even when items match) - if (e.getWhoClicked().getInventory().getItem(e.getHotbarButton()) != null) recipeAmount = 0; + if (p.getInventory().getItem(event.getClickEvent().getHotbarButton()) != null) recipeAmount = 0; break; case DROP: case CONTROL_DROP: // If we are holding items, craft-via-drop fails (vanilla behavior) - ItemStack cursor = e.getCursor(); + ItemStack cursor = event.getClickEvent().getCursor(); if (cursor != null && cursor.getType() != Material.AIR) recipeAmount = 0; break; @@ -110,18 +90,17 @@ public void onCraftClick(InventoryClickEvent e, ItemStack item, int maxCraftable case SHIFT_LEFT: if (recipeAmount == 0) break; - int capacity = fits(item, e.getView().getBottomInventory()); + int capacity = fits(item, p.getInventory()); // If we can't fit everything, increase "space" to include the items dropped by crafting // (Think: Uncrafting 8 iron blocks into 1 slot) - if (capacity < maxCraftable) - maxCraftable = ((capacity + recipeAmount - 1) / recipeAmount) * recipeAmount; - - recipeAmount = maxCraftable; + if (capacity < event.getMaxCraftable()) { + recipeAmount = ((capacity + recipeAmount - 1) / recipeAmount) * recipeAmount; + }else recipeAmount = event.getMaxCraftable(); break; default: - cursor = e.getCursor(); + cursor = event.getClickEvent().getCursor(); if (cursor != null && cursor.getType() != Material.AIR) { if (cursor.isSimilar(item)) { if (cursor.getAmount() + item.getAmount() > cursor.getMaxStackSize()) recipeAmount = 0; @@ -176,19 +155,6 @@ protected void serialize(ConfigurationSection section) { public static StageCraft deserialize(ConfigurationSection section, QuestBranch branch) { return new StageCraft(branch, ItemStack.deserialize(section.getConfigurationSection("result").getValues(false)), section.contains("itemComparisons") ? new ItemComparisonMap(section.getConfigurationSection("itemComparisons")) : new ItemComparisonMap()); } - - public static int getMaxCraftAmount(CraftingInventory inv) { - if (inv.getResult() == null) return 0; - - int resultCount = inv.getResult().getAmount(); - int materialCount = Integer.MAX_VALUE; - - for (ItemStack is : inv.getMatrix()) - if (is != null && is.getAmount() < materialCount) - materialCount = is.getAmount(); - - return resultCount * materialCount; - } public static int fits(ItemStack stack, Inventory inv) { int result = 0; diff --git a/core/src/main/java/fr/skytasul/quests/utils/compatibility/Post1_16.java b/core/src/main/java/fr/skytasul/quests/utils/compatibility/Post1_16.java new file mode 100644 index 00000000..341541cb --- /dev/null +++ b/core/src/main/java/fr/skytasul/quests/utils/compatibility/Post1_16.java @@ -0,0 +1,18 @@ +package fr.skytasul.quests.utils.compatibility; + +import org.bukkit.Bukkit; +import org.bukkit.event.EventHandler; +import org.bukkit.event.EventPriority; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.SmithItemEvent; + +import fr.skytasul.quests.api.events.BQCraftEvent; + +public class Post1_16 implements Listener { + + @EventHandler (priority = EventPriority.MONITOR, ignoreCancelled = true) + public void onSmith(SmithItemEvent event) { + Bukkit.getPluginManager().callEvent(new BQCraftEvent(event, event.getCurrentItem(), event.getCurrentItem().getAmount())); + } + +} From 0fd1519c5b5c0ff262a6a8f02482896853c8e0c8 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 2 Oct 2022 14:19:16 +0200 Subject: [PATCH 080/111] :loud_sound: When players have missing datas for "mine blocks" stage type, the plugin will no longer throw an NPE --- .../stages/types/AbstractCountableStage.java | 28 ++++++++----------- .../quests/players/PlayerAccount.java | 5 ++++ .../fr/skytasul/quests/stages/StageMine.java | 3 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableStage.java b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableStage.java index 74227545..3a6b6395 100644 --- a/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableStage.java +++ b/core/src/main/java/fr/skytasul/quests/api/stages/types/AbstractCountableStage.java @@ -50,8 +50,11 @@ public Map> cloneObjects() { return map; } - public Map getPlayerRemainings(PlayerAccount acc) { - return getData(acc, "remaining"); + public Map getPlayerRemainings(PlayerAccount acc, boolean warnNull) { + Map remaining = getData(acc, "remaining"); + if (warnNull && remaining == null) + BeautyQuests.logger.severe("Cannot retrieve stage datas for " + acc.getNameAndID() + " on " + super.toString()); + return remaining; } protected void calculateSize() { @@ -73,11 +76,8 @@ protected Supplier[] descriptionFormat(PlayerAccount acc, Source source) } private String[] buildRemainingArray(PlayerAccount acc, Source source) { - Map playerAmounts = getPlayerRemainings(acc); - if (playerAmounts == null) { - BeautyQuests.logger.severe("The plugin has been unable to retrieve stage datas for account " + acc.debugName() + " on " + super.toString()); - return new String[] { "§4§lerror" }; - } + Map playerAmounts = getPlayerRemainings(acc, true); + if (playerAmounts == null) return new String[] { "§4§lerror" }; String[] elements = new String[playerAmounts.size()]; int i = 0; for (Entry obj : playerAmounts.entrySet()) { @@ -114,11 +114,8 @@ public boolean event(PlayerAccount acc, Player p, Object object, int amount) { for (Entry> entry : objects.entrySet()) { int id = entry.getKey(); if (objectApplies(entry.getValue().getKey(), object)) { - Map playerAmounts = getPlayerRemainings(acc); - if (playerAmounts == null) { - BeautyQuests.logger.warning(p.getName() + " does not have object datas for stage " + toString() + ". This is a bug!"); - return true; - } + Map playerAmounts = getPlayerRemainings(acc, true); + if (playerAmounts == null) return true; if (playerAmounts.containsKey(id)) { int playerAmount = playerAmounts.get(id); if (playerAmount <= amount) { @@ -165,11 +162,8 @@ public void unload() { @Override public void joins(PlayerAccount acc, Player p) { super.joins(acc, p); - Map remainings = getPlayerRemainings(acc); - if (remainings == null) { - BeautyQuests.logger.severe(p.getName() + " does not have remaining datas for stage " + toString() + ". This is a bug!"); - return; - } + Map remainings = getPlayerRemainings(acc, true); + if (remainings == null) return; createBar(p, remainings.values().stream().mapToInt(Integer::intValue).sum()); } diff --git a/core/src/main/java/fr/skytasul/quests/players/PlayerAccount.java b/core/src/main/java/fr/skytasul/quests/players/PlayerAccount.java index a88e3665..630f42a5 100644 --- a/core/src/main/java/fr/skytasul/quests/players/PlayerAccount.java +++ b/core/src/main/java/fr/skytasul/quests/players/PlayerAccount.java @@ -154,6 +154,11 @@ public String getName() { return p == null ? debugName() : p.getName(); } + public String getNameAndID() { + Player p = getPlayer(); + return p == null ? debugName() : p.getName() + " (# " + index + ")"; + } + public String debugName() { return abstractAcc.getIdentifier() + " (#" + index + ")"; } diff --git a/core/src/main/java/fr/skytasul/quests/stages/StageMine.java b/core/src/main/java/fr/skytasul/quests/stages/StageMine.java index f071f7c5..3f1ff2bf 100644 --- a/core/src/main/java/fr/skytasul/quests/stages/StageMine.java +++ b/core/src/main/java/fr/skytasul/quests/stages/StageMine.java @@ -73,7 +73,8 @@ public void onPlace(BlockPlaceEvent e){ Player p = e.getPlayer(); PlayerAccount acc = PlayersManager.getPlayerAccount(p); if (!branch.hasStageLaunched(acc, this)) return; - Map playerBlocks = getPlayerRemainings(acc); + Map playerBlocks = getPlayerRemainings(acc, true); + if (playerBlocks == null) return; for (Integer id : playerBlocks.keySet()) { if (objectApplies(super.objects.get(id).getKey(), e.getBlock())) { e.getBlock().setMetadata("playerInStage", new FixedMetadataValue(BeautyQuests.getInstance(), p.getName())); From cf074f806de8a6b225965021188e1881269bc0a0 Mon Sep 17 00:00:00 2001 From: SkytAsul Date: Sun, 2 Oct 2022 14:46:16 +0200 Subject: [PATCH 081/111] :globe_with_meridians: Updated Thai and Hungarian, added Ukrainian translations --- core/src/main/resources/locales/hu_HU.yml | 33 +- core/src/main/resources/locales/th_TH.yml | 13 +- core/src/main/resources/locales/uk_UA.yml | 831 ++++++++++++++++++++++ 3 files changed, 860 insertions(+), 17 deletions(-) create mode 100644 core/src/main/resources/locales/uk_UA.yml diff --git a/core/src/main/resources/locales/hu_HU.yml b/core/src/main/resources/locales/hu_HU.yml index 5eba44df..9a589549 100644 --- a/core/src/main/resources/locales/hu_HU.yml +++ b/core/src/main/resources/locales/hu_HU.yml @@ -17,6 +17,10 @@ msg: updated: '§7A következő küldetés §e{0}§7 frissítve.' checkpoint: '§7Küldetés ellenőrző pont elérve!' failed: '§cNem sikerült a küldetés{0}' + pools: + noTime: '§cVárnod kell még {0} mielőtt elkezdenél egy újabb küldetést.' + allCompleted: '§7Az összes küldetést teljesítetted!' + noAvailable: '§7Nincsen elérhető küldetések.' questItem: drop: '§cKüldetés itemet nem dobhatsz el!' craft: '§cNem barkácsolhatsz küldetés itemmel!' @@ -28,7 +32,6 @@ msg: writeXPGain: '§Írd ide az xp mennyiséget amit a játékos kapni fog:(Utolsó érték:{0})' writeMobAmount: '§aÍrd ide a mennyiséget,hogy mennyi szörnyet kell megölni:' writeChatMessage: '§aÍrd a következő üzenetet: (Add {SLASH}" az elkezdéshez.)' - writeEndMessage: '§aÍrd ide az üzenetet amit a játékos fog kapni mikor befejezi a küldetést:' writeDescriptionText: '§aÍrd ide a szöveget ami leírja a lépéseket:' writeStageText: '§aÍrd ide a szöveget amit a játékos fog kapni mikor elkezdi a lépést:' moveToTeleportPoint: '§aMenj a kért teleportálás helyszínére.' @@ -46,7 +49,6 @@ msg: level: '§cA szintednek a következőnek kell lennie{0}!' combatLevel: '§aA harc szinted a következőnek kell lennie{0}!' money: '§cEnnyi kell lennied nállad {0}!' - wait: '§cVárnod kell {0} percet mielött újra indítanád!' experience: edited: '§aMegkell változtatnod az xp-t {0} ről ennyire {1}.' selectNPCToKill: '§aVálazd ki az NPC akit megkell ölni.' @@ -67,6 +69,11 @@ msg: inventoryFull: '§cTele van a táskád, a tárgy el lett dobva a földre.' playerNeverConnected: '§cNem lehet információkat találni a játékosról {0}.' playerNotOnline: '§cA következő játékos nem elérhető {0}.' + versionRequired: 'Szükséges verzió: §l{0}' + restartServer: '§7Indítsd újra a szervert, hogy lásd a módosításokat.' + dialogs: + skipped: '§8§oPárbeszéd átugorva.' + tooFar: '§7§oTúl messze vagy ettől: {0}...' command: checkpoint: noCheckpoint: '§cNem találtunk ellenörző pontot a küldetéshez {0}' @@ -77,6 +84,9 @@ msg: next: '§aA szint kihagyva.' nextUnavailable: '§c A "kihagyás" opció nem elérhető mikor a játékos az ág végén van.' set: '§aSzint {0} elindítva.' + startDialog: + noDialog: '§cA megadott játékosnak nincsen folyamatban lévő párbeszéde.' + alreadyIn: '§cA játékos már párbeszédben van.' playerNeeded: '§cJátékos kell lenned, hogy lefuttathasd ezt a parancsot!' incorrectSyntax: '§cHelytelen szintax.' noPermission: '§cNincs elég jogod, hogy futtathasd a parancsot! (Szükséges: {0})' @@ -90,12 +100,12 @@ msg: leaveAll: '§aKényszerítve lettél, hogy befejezd a következő küldetést {0}. Hibák:{1}' resetPlayer: player: '§6Az összes információ a következő küldetésedből {0} törölve lett általa {1}.' - remover: '§6{0} küldetés információk törölve lettek {1}.' resetPlayerQuest: player: '§6Az összes infó a küldetésről {0} törölve lett általa {1}.' remover: '§6Küldetés infó törölve {0} ennyiből {1}.' resetQuest: '§6Küldetés adat törölve neki {0}.' startQuest: '§6Kényszerítve lettél, hogy elkezd a következő küldetést {0} (Játékos UUID: {1}).' + startQuestNoRequirements: '§cA megadott játékosnak nincsen meg a szükséges követelményei a(z) {0} küldetéshez... Mellékeld a a "-overrideRequirements" szót a parancsod végére, hogy átugord a követelményeket.' cancelQuest: '§6Elutasítottad a következő küldetést {0}.' cancelQuestUnavailable: '§cA küldetés nem elutasítható {0}.' backupCreated: '§6Sikeresen csináltál mentést.' @@ -111,6 +121,9 @@ msg: resetAll: '§6Sikeres újrarakás a következő játékosnak {0}.' hidden: '§6A lista a következő játékosnak {0} láthatatlan.' shown: '§6A lista következő játékosnak látható {0}.' + own: + hidden: '§6A scoreboard mostantól elvan rejtve.' + shown: '§6A scoreboard mostantól látható.' help: header: '§6§lBeautyQuests - Segítség' create: '§6/{0} létrehoz: §eLétrehoz egy küldetést.' @@ -146,11 +159,8 @@ msg: list: '§aAz összes Mythic Mob lista:' isntMythicMob: '§cEz a Mythic Mob nem létezik.' disabled: '§cMythicMob kikapcsolva.' - epicBossDoesntExist: '§cEz az Epic Boss nem létezik.' textList: syntax: '§cHelyes szintax: ' - added: '§aSzöveg "{0}" hozzáadva.' - removed: '§aSzöveg "{0}" törölve.' help: header: '§6§lBeautyQuests - Lista szerkesztő segítség' add: '§6add : §eHozzáad egy szöveget.' @@ -158,7 +168,6 @@ msg: list: '§6list: §eÖsszes szöveg megnézése.' close: '§6close: §eHozzáadott szöveg megerősítése.' noSuchElement: '§cNincs ilyen elem. Engedélyezett elemek: §e{0}' - comparisonType: '§aVálaszd ki a kívánt összehasonlítás tipust {0}. Alap összehasonlítás ez: §e§ljobb vagy egyenlő §r§a. Tipus §onull§r§a, hogy használd.' scoreboardObjectiveNotFound: '§cIsmeretlen lista objektum.' writeCommandDelay: '§aÍrd a kívánt parancs késleltetést, tickben.' advancement: @@ -186,6 +195,8 @@ inv: craft: '§aKészíts tárgyakat' bucket: '§aTölts meg vödröket' location: '§aMenj el a helyre' + stages: + laterPage: Előző oldal details: endMessage: '§eBefejező üzenet szerkesztése' startDialog: '§eKezdő üzenet szerkesztése' @@ -221,9 +232,6 @@ inv: mobs: name: Szörnyek kiválasztása none: '§aKattints a szörny hozzáadásához.' - click: |- - §7Bal-klikk a szerkesztéshez. - Jobb-klikk a törléshez. mobSelect: name: Szörny tipus kiválasztása bukkitEntityType: '§eEntitás tipus kiválasztása' @@ -252,13 +260,14 @@ inv: notStarted: Nem elindított küldetések finished: Befejezett küldetések inProgress: Folyamatban lévő küldetések - loreCancel: '§c§oKattints a küldetés elutasításához.' - timeToWait: '§3§o {0} perc mielött újra elindíthatnád a küldetést.' canRedo: '§3§oÚjratudod kezdeni ezt a küldetést!' itemCreator: name: Tárgy készítő itemType: '§bTárgy tipus' itemFlags: Tárgy funkciók bekapcsolása +scoreboard: + stage: + npc: Beszélj az {0} npc-vel indication: cancelQuest: '§7Biztos vagy benne, hogy megakarod szakítani ezt a küldetést {0}?' removeQuest: '§7Biztos vagy benne, hogy elakarod távolítani ezt a küldetést {0}?' diff --git a/core/src/main/resources/locales/th_TH.yml b/core/src/main/resources/locales/th_TH.yml index d5652395..dbdfc8a3 100644 --- a/core/src/main/resources/locales/th_TH.yml +++ b/core/src/main/resources/locales/th_TH.yml @@ -87,11 +87,11 @@ msg: restartServer: '§7รีสตาร์ทเซิร์ฟเวอร์ของคุณเพื่อดูการแก้ไข' dialogs: skipped: '§8§o Dialog skipped.' + tooFar: '§7§oคุณได้อยู่ห่างไกลจาก {0}...' command: downloadTranslations: syntax: '§cคุณต้องระบุภาษาที่จะดาวน์โหลด ตัวอย่าง: "/quests downloadTranslations en_US"' notFound: "§cLanguage {0} ไม่พบสำหรับรุ่น {1}\n" - exists: '§cไฟล์ {0} มีอยู่แล้ว ผนวก "จริง" ต่อท้ายคำสั่งของคุณเพื่อเขียนทับ (/quests downloadแปล จริง)' downloaded: 'ดาวน์โหลด §aLanguage {0} แล้ว! §7ตอนนี้คุณต้องแก้ไขไฟล์ "/plugins/BeautyQuests/config.yml" เพื่อเปลี่ยนค่าของ §ominecraftTranslationsFile§7 ด้วย {0} จากนั้นรีสตาร์ทเซิร์ฟเวอร์' checkpoint: noCheckpoint: '§cไม่พบ checkpoint สำหรับภารกิจ {0}' @@ -126,7 +126,6 @@ msg: remover: '§6 {0} ข้อมูลภารกิจของ {1} ถูกลบไปแล้ว' resetQuest: '§6ลบข้อมูลของภารกิจสำหรับ {0} players.' startQuest: '§6คุณบังคับให้เริ่มภารกิจ {0} (UUID ของผู้เล่น: {1})' - startQuestNoRequirements: '§cผู้เล่นไม่ตรงตามข้อกำหนดสำหรับภารกิจ {0}... ต่อท้าย "จริง" ที่ท้ายคำสั่งของคุณเพื่อข้ามการตรวจสอบข้อกำหนด' cancelQuest: '§6คุณได้ยกเลิกภารกิจ {0}.' cancelQuestUnavailable: '§ภารกิจ {0} ไม่สามารถยกเลิกได้' backupCreated: '§6คุณได้สร้างการสำรองข้อมูลของ ภารกิจ และข้อมูลผู้เล่นทั้งหมดเรียบร้อยแล้ว' @@ -215,6 +214,8 @@ msg: dialog: syntax: '§cCorrect syntax: {0}{1} ' syntaxRemove: '§cCorrect sytax: remove ' + messageRemoved: '§aข้อความ "§7{0}§a" ได้ถูกลบออกเรียบร้อย' + edited: '§aข้อความ "§7{0}§a" ได้ถูกแก้ไขเรียบร้อย' cleared: '§a {0} ข้อความที่ถูกลบ' help: header: '§6§lBeautyQuests — Dialog editor help' @@ -237,9 +238,10 @@ msg: list: '§aรายการทั้งหมดของ Mythic Mobs:' isntMythicMob: '§cMythic Mob นี้ไม่มีอยู่จริง' disabled: '§cMythicMob ถูกปิดใช้งาน' - epicBossDoesntExist: '§c Epic Boss ตัวนี้ไม่มีอยู่จริง' textList: syntax: '§cCorrect syntax: ' + added: '§aข้อความ "§7{0}§a" ได้ถูกเพิ่มเรียบร้อย' + removed: '§aข้อความ "§7{0}§a" ได้ถูกลบออกเรียบร้อย' help: header: '§6§lBeautyQuests — List editor help' add: '§6add : §eเพิ่มข้อความ' @@ -251,6 +253,7 @@ msg: pool: hologramText: 'เขียนข้อความโฮโลแกรมที่กำหนดเองสำหรับพูลนี้ (หรือ "null" ถ้าคุณต้องการใช้ค่าเริ่มต้น)' maxQuests: 'เขียนจำนวนสูงสุดของ ภารกิจ ที่สามารถเรียกใช้งานได้จาก pool นี้' + colorNamed: 'โปรดใส่รหัสสี' writeCommandDelay: '§aเขียนการหน่วงเวลาคำสั่งที่ต้องการ ใน ticks.' advancement: finished: เสร็จแล้ว @@ -267,6 +270,8 @@ inv: create: stageCreate: '§aสร้าง step ใหม่' stageRemove: '§cลบ step' + stageUp: เลื่อนขึ้น + stageDown: เลื่อนลง findNPC: '§aค้นหา NPC' bringBack: '§aนำ ไอเท่ม กลับมา' findRegion: '§aค้นหา region' @@ -340,8 +345,6 @@ inv: startableFromGUILore: อนุญาตให้ผู้เล่นเริ่มภารกิจจากเมนูเควส scoreboardItem: เปิดใช้งาน สกอร์บอร์ด scoreboardItemLore: หากปิดใช้งานภารกิจจะไม่ถูกติดตามผ่าน สกอร์บอร์ด - hideItem: ซ่อนเมนูและ dynmap - hideItemLore: หากเปิดใช้งานเควสจะไม่แสดงบน dynmap หรือในเมนูเควส hideNoRequirementsItem: ซ่อนเมื่อไม่เป็นไปตามข้อกำหนด hideNoRequirementsItemLore: หากเปิดใช้งาน ภารจะไม่แสดงในเมนูภารกิจ เมื่อไม่ตรงตามข้อกำหนด bypassLimit: อย่านับขีดจำกัด ของภารกิจ diff --git a/core/src/main/resources/locales/uk_UA.yml b/core/src/main/resources/locales/uk_UA.yml new file mode 100644 index 00000000..ca9d959e --- /dev/null +++ b/core/src/main/resources/locales/uk_UA.yml @@ -0,0 +1,831 @@ +--- +msg: + quest: + finished: + base: '§aПоздоровляємо! Ви закінчили квест §e{0}§a!' + obtain: '§aВи отримали {0}!' + started: '§aВи почали квест §r§e{0}§o§6!' + created: '§aПоздоровляємо! Ви створили квест §a{0}§a що містить {1} гілку(-ок)!' + edited: '§aПоздоровляємо! Ви відредагували квест §a{0}§a що містить {1} гілку(-ок)!' + createCancelled: '§cСтворення або редагування було скасовано іншим плагіном!' + cancelling: '§cПроцес створення квесту скасовано.' + editCancelling: '§cПроцес редагування квесту скасовано.' + invalidID: '§cКвест з ідентифікатором {0} не існує.' + invalidPoolID: '§cПул {0} не існує.' + alreadyStarted: '§cВи вже почали квест!' + quests: + maxLaunched: '§cВи не можете виконувати більше {0} квестів одночасно...' + nopStep: '§cЦей квест не має жодного завдання.' + updated: '§7Квест §e{0}§7 оновлено.' + checkpoint: '§7Досягнута контрольна точка квесту!' + failed: '§cВи провалили квест {0}...' + pools: + noTime: '§cВи повинні зачекати {0} перед виконанням іншого квесту.' + allCompleted: '§7Ви завершили всі квести!' + noAvailable: '§7Більше немає доступних квестів...' + maxQuests: '§cВи не можете мати більше {0} квестів одночасно...' + questItem: + drop: '§cВи не можете викинути квестовий предмет!' + craft: '§cВи не можете використати квестовий предмет, для крафту!' + eat: '§cВи не можете з''їсти квестовий предмет!' + stageMobs: + noMobs: '§cЦе завдання не потребує вбивств мобів.' + listMobs: '§aВи повинні вбити {0}.' + writeNPCText: '§aНапишіть повідомлення, яке NPC буде казати гравцям: (Напишіть "help", щоб отримати допомогу.)' + writeRegionName: '§aНапишіть назву регіону потрібну для кроку:' + writeXPGain: '§aНапишіть кількість досвіду, який отримає гравець: (Останнє значення: {0})' + writeMobAmount: '§aНапишіть кількість мобів, яких потрібно вбити:' + writeMobName: '§aНапишіть кастомне ім''я моба, якого потрібно вбити:' + writeChatMessage: '§aНапишіть необхідне повідомлення: (Додайте "{SLASH}" на початку, якщо хочете вказати команду.)' + writeMessage: '§aНапишіть повідомлення, яке буде відправлено гравцю' + writeStartMessage: '§aНапишіть повідомлення, яке буде відправлено на початку квесту, "null" якщо ви хочете залишити стандартне, або "none" якщо не хочете вказувати:' + writeEndMsg: '§aНапишіть повідомлення, яке буде відправлено в кінці завдання, "null" якщо ви хочете, щоб відправилось стандартне повідомлення, або "none", якщо ви не хочете відправляти жодного повідомлення. Ви можете використати "{0}", який буде замінено на нагороду.' + writeEndSound: '§aНапишіть назву звуку, який буде відтворюватись гравцю на прикінці квесту, "null", якщо ви хочете залишити стандартний, або "none" якщо не хочете вказувати:' + writeDescriptionText: '§aНапишіть текст, що описує ціль стадії:' + writeStageText: '§aНапишіть текст, який буде відправлено гравцю на початку стадії:' + moveToTeleportPoint: '§aЙдіть до бажаного розташування телепорту.' + writeNpcName: '§aНапишіть ім''я NPC:' + writeNpcSkinName: '§aНапишіть нік скіна для NPC:' + writeQuestName: '§aНапишіть назву свого квесту:' + writeCommand: '§aВведіть потрібну команду: (Команда без "/" і заповнення"{PLAYER}" підтримується. Воно буде замінено ім''ям виконавця.)' + writeHologramText: '§aЗапишіть текст голограми: (Напишіть "none", якщо вам не потрібна голограма, або "null", якщо ви хочете звичайний текст.)' + writeQuestTimer: '§aНапишіть необхідний час (у хвилинах), для перепроходження квесту: (Напишіть "null", якщо ви хочете встановити таймер за замовчуванням.)' + writeConfirmMessage: '§aНапишіть повідомлення для підтвердження, яке гравець має написати, щоб запустити завдання: (Напишіть, "null", якщо ви хочете встановити повідомлення за замовчуванням.)' + writeQuestDescription: '§aНапишіть опис завдання, який буде показано гравцю під час виконання.' + writeQuestMaterial: '§aНапишіть матеріали для квестових предметів.' + requirements: + quest: '§cВи повинні закінчити квест §e{0}§c!' + level: '§cВаш рівень має бути {0}!' + job: '§cВаш рівень роботи§e{1}§c має бути {0}!' + skill: '§cВаш рівень вмінь §e{1}§c має бути {0}!' + combatLevel: '§cВаш рівень битви повинен бути {0}!' + money: '§aВи повинні мати {0}!' + waitTime: '§cВи повинні зачекати {0} перед повторним виконанням цього квесту!' + experience: + edited: '§aВи змінили приріст досвіду від {0} до {1} очок.' + selectNPCToKill: '§aВиберіть NPC, якого потрібно вбити.' + npc: + remove: '§aNPC видалено.' + talk: '§aЙдіть і поговоріть з NPC по імені §e{0}.' + regionDoesntExists: '§cЦей регіон не існує. (Ви повинні бути в тому ж світі, що й регіон.)' + npcDoesntExist: '§cNPC з ідентифікатором {0} не існує.' + objectDoesntExist: '§cВказаний предмет з ідентифікатором {0} не існує.' + number: + negative: '§cВи повинні ввести додатнє число!' + zero: '§cВи повинні ввести номер більший за 0!' + invalid: '§c{0} не є правильним числом.' + notInBounds: '§cВаш номер повинен бути від {0} до {1}.' + errorOccurred: '§cСталася помилка, зв''яжіться з адміністратором! §4§lКод помилки: {0}' + commandsDisabled: '§cЗараз ви не можете виконувати команди!' + indexOutOfBounds: '§cЧисло {0} за межами діапазону! Воно повинна бути між {1} і {2}.' + invalidBlockData: '§cБлоки {0} є недійсними або несумісними з блоком {1}.' + invalidBlockTag: '§cНедоступний тег блоку {0}.' + bringBackObjects: Поверніть мене назад {0}. + inventoryFull: '§cВаш інвентар повний, предмет був скинутий на підлогу.' + playerNeverConnected: '§cНеможливо знайти інформацію про гравця {0}.' + playerNotOnline: '§cГравець {0} офлайн.' + playerDataNotFound: '§cДані гравця {0} не знайдено.' + versionRequired: 'Потрібна версія: §l{0}' + restartServer: '§7Перезавантажте сервер, щоб застосувати зміни.' + dialogs: + skipped: '§8§o Діалог пропущено.' + tooFar: '§7§oВи занадто далеко від {0}...' + command: + downloadTranslations: + syntax: '§cви повинні вказати мову для завантаження. Приклад: "/quests downloadTranslations en_US".' + notFound: '§cМова {0} не знайдена для версії {1}.' + exists: '§cФайл {0} вже існує. Додавайте «-overwrite» до вашої команди, щоб перезаписати його. (/quests downloadTranslations -overwrite)' + downloaded: '§aМову {0} було завантажено! §7Тепер ви повинні відредагувати файл "/plugins/BeautyQuests/config.yml", щоб змінити значення §ominecraftTranslationsFile§7 з {0}, а потім перезапустити сервер.' + checkpoint: + noCheckpoint: '§cНе знайдено жодної контрольної точки для квесту {0}.' + questNotStarted: '§cВи не виконуєте цей квест.' + setStage: + branchDoesntExist: '§cГілка з ідентифікатором {0} не існує.' + doesntExist: '§cЕтап з ідентифікатором {0} не існує.' + next: '§aЕтап пропущено.' + nextUnavailable: '§cРпція "Пропустити" недоступна, коли гравець знаходиться у кінці гілки.' + set: '§aЕтап {0} запущено.' + startDialog: + impossible: '§cНеможливо зараз почати діалог.' + noDialog: '§cГравець не має діалогового вікна, що очікує на розгляд.' + alreadyIn: '§cГравець вже бачить діалог.' + success: '§aРозпочате діалогове вікно для гравця {0} в квесті {1}!' + playerNeeded: '§cВи повинні бути гравцем для виконання цієї команди!' + incorrectSyntax: '§cНевірний синтаксис.' + noPermission: '§cУ вас недостатньо прав для виконання цієї команди! (Потрібно: {0})' + invalidCommand: + quests: '§cЦя команда не існує, напишіть §e/quests help§c.' + simple: '§cЦя команда не існує, напишіть §ehelp§c.' + needItem: '§cВи повинні тримати предмет у головній руці!' + itemChanged: '§aЕлемент було відредаговано. Зміни буде застосовано після перезапуску.' + itemRemoved: '§aГолограма була видалена.' + removed: '§aКвест {0} успішно видалено.' + leaveAll: '§aВи примусово завершили {0} квест(и). Помилок: {1}' + resetPlayer: + player: '§6Всі відомості про ваші {0} квест(и) було видалено {1}.' + remover: '§6{0} інформація про квест (і {2} пули) з {1} було видалено.' + resetPlayerQuest: + player: '§6Всі відомості про квест {0} було видалено {1}.' + remover: '§6{0} інформація про квест {1} була видалена.' + resetQuest: '§6Видалені дані квесту для {0} гравців.' + startQuest: '§6Ви примусово запустили квест {0} (UUID Гравця: {1}).' + startQuestNoRequirements: '§cГравець не відповідає вимогам квесту {0}... Долучайтесь до "-overrideRequires" наприкінці вашої команди для обходу вимог.' + cancelQuest: '§6Ви відмінили квест {0}.' + cancelQuestUnavailable: '§cКвест {0} не може бути скасовано.' + backupCreated: '§6Ви успішно створили бекапи для всіх завдань та інформації про гравця.' + backupPlayersFailed: '§cСтворення резервної копії для всіх даних гравця зазнало невдачі.' + backupQuestsFailed: '§cСтворення резервної копії для всіх квестів невдале.' + adminModeEntered: '§aВи увійшли в Режим Адміністратора.' + adminModeLeft: '§aВи вийшли з Режиму Адміністратора.' + resetPlayerPool: + timer: '§aВи скинули {0} таймер пулу в {1}.' + full: '§aВи скинули {0} дані пулу з {1}.' + scoreboard: + lineSet: '§6Ви успішно відредагували рядок {0}.' + lineReset: '§6Ви успішно змінили рядок {0}.' + lineRemoved: '§6Ви успішно видалили рядок {0}.' + lineInexistant: '§cРядок {0} не існує.' + resetAll: '§6Ви успішно скинули таблицю гравця {0}.' + hidden: '§6Таблицю гравця {0} було приховано.' + shown: '§6Таблицю гравця {0} було показано.' + own: + hidden: '§6Ваша таблиця прихована.' + shown: '§6Ваша таблиця показана знову.' + help: + header: '§6§lBeautyQuests - Допомога' + create: '§6/{0} create: §eСтворити квест.' + edit: '§6/{0} edit: §eРедагувати квест.' + remove: '§6/{0} remove: §eВидалити квест з зазначеним id або натисніть на NPC якщо не визначено.' + finishAll: '§6/{0} finishAll : §eЗавершіть всі квести гравця.' + setStage: '§6/{0} setStage [нова гілка] [нова стадія]: §eПропустити поточний етап/почати етап для гілки.' + startDialog: '§6/{0} startDialog : §eЗапустити діалогове вікно для етапу NPC або початкове діалогове вікно для квесту.' + resetPlayer: '§6/{0} resetPlayer : §eВидалити всі відомості про гравця.' + resetPlayerQuest: '§6/{0} resetPlayerQuest [id]: §eВидалити інформацію про квест для гравця.' + seePlayer: '§6/{0} seePlayer : §eПереглянути інформацію про гравця.' + reload: '§6/{0} reload: §eЗберегти і перезавантажити всі конфігурації і файли. (§cзастарілий§e)' + start: '§6/{0} start [id]: §eПримусовий початок завдання.' + setItem: '§6/{0} setItem : §eЗберегти предмет голограми.' + setFirework: '§6/{0} setFirework: §eРедагувати стандартний завершальний феєрверк.' + adminMode: '§6/{0} adminMode: §eПеремикання режиму адміністратора. (Використовується для відображення невеликих повідомлень журналу.)' + version: '§6/{0} version: §eПереглянути поточну версію плагіна.' + downloadTranslations: '§6/{0} downloadTranslations : §eЗавантажує стандартний файл перекладу' + save: '§6/{0} save: §eРучне збереження змін плагіну' + list: '§6/{0} list: §eЩоб відобразити список квестів (Лише для підтримуваних версій.)' + typeCancel: '§aНапишіть "cancel", щоб повернутися до останнього тексту.' + editor: + blockAmount: '§aНапишіть кількість блоків:' + blockName: '§aНапишіть назву блоку:' + blockData: '§aНапишіть блокові дані (доступні дані блоків: §7{0}§a):' + blockTag: '§aЗапишіть тег блоку (доступні теги: §7{0}§a):' + typeBucketAmount: '§aНапишіть кількість відер для заповнення:' + typeDamageAmount: 'Напишіть кількість дамагу, який повинен нанести гравець:' + goToLocation: '§aЙдіть до бажаного місця для етапу.' + typeLocationRadius: '§aНапишіть потрібну відстань від місця розташування:' + typeGameTicks: '§aНапишіть кількість ігрових тіків:' + already: '§cВи вже в редакторі.' + stage: + location: + typeWorldPattern: '§aНапишіть регулярний вираз для назв світів:' + enter: + title: '§6~ Режим Редактора ~' + subtitle: '§6Напишіть "/quests exitEditor" щоб примусово вийти з редактора.' + list: '§c⚠ §7Ви увійшли до редактора "списків". Для додавання рядків використовуйте "add". Перейдіть до довідки "help" (без слешу). §e§lНапишіть "close" для виходу з редактора.' + chat: '§6Ви перебуваєте в режимі редактора. Напишіть "/quests exitEditor", щоб примусово вийти з редактора. (Не рекомендується, розгляньте можливість використання таких команд, як "close" або "cancel", щоб повернутися до попереднього інвентарю.)' + npc: + enter: '§aНатисніть на NPC, або напишіть "cancel".' + choseStarter: '§aОберіть NPC, який почне завдання.' + notStarter: '§cЦей NPC — не починає квести.' + text: + argNotSupported: '§cАргумент {0} не підтримується.' + chooseLvlRequired: '§aНапишіть необхідну кількість рівнів:' + chooseJobRequired: '§aНапишіть ім''я потрібної роботи:' + choosePermissionRequired: '§aНапишіть необхідні права, щоб розпочати квест:' + choosePermissionMessage: '§aВи можете вказати повідомлення про відхилення, якщо гравець не має обов''язкового дозволу. (Щоб пропустити цей крок, напишіть "null".)' + choosePlaceholderRequired: + identifier: '§aНапишіть ім''я потрібного заповнювача без відсоткових символів:' + value: '§aНапишіть необхідне значення для наповнювача §e%§e{0}§e%§a:' + chooseSkillRequired: '§aНапишіть обов''язкові навички:' + chooseMoneyRequired: '§aНапишіть кількість необхідних грошей:' + reward: + permissionName: '§aНапиши ім''я дозволу.' + permissionWorld: '§aНапишіть світ, в якому дозвіл буде відредаговано, або "null", якщо ви хочете застосувати глобально.' + money: '§aНапишіть кількість отриманих грошей:' + wait: '§aНапиши кількість ігрових тіків для очікування: (1 секунда = 20 ігрових тіків)' + random: + min: '§aНапишіть мінімальну кількість нагород, що буде видано гравцю (включно).' + max: '§aНапишіть максимальну кількість нагород, що буде видано гравцю (включно).' + chooseObjectiveRequired: '§aНапишіть ім''я цілі.' + chooseObjectiveTargetScore: '§aНапишіть кінцеву ціль для завдання.' + chooseRegionRequired: '§aНапишіть назву потрібного регіону (ви повинні бути в одному світі).' + selectWantedBlock: '§aНатисніть з паличкою на потрібному блоці для етапу.' + itemCreator: + itemType: '§aНапишіть ім''я потрібного типу предмету:' + itemAmount: '§aНапишіть кількість предметів:' + itemName: '§aНапишіть назву предмету:' + itemLore: '§aЗмінити опис предмета: (Напишіть "help" для допомоги)' + unknownItemType: '§cНевідомий тип предмета.' + invalidItemType: '§cНедійсний тип предмета. (Елемент не може бути блоком)' + unknownBlockType: '&cНевідомий тип блоку' + invalidBlockType: '§cНедійсний тип блока.' + dialog: + syntax: '§cПравильний синтаксис: {0}{1} ' + syntaxRemove: '§cПравильний синтаксис: remove ' + player: '§aПовідомлення "§7{0}§a" додано для гравця.' + npc: '§aПовідомлення "§7{0}§a" додано для NPC.' + noSender: '§aПовідомлення "§7{0}§a" додано без відправника.' + messageRemoved: '§aПовідомлення "§7{0}§a" видалено.' + edited: '§aПовідомлення "§7{0}§a" відредаговане.' + soundAdded: '§aЗвук "§7{0}§a" додано для повідомлення: "§7{1}§a".' + cleared: '§aВидалено §2§l{0}§a повідомлення.' + help: + header: '§6§lBeautyQuest - Довідник редактору діалогів' + npc: '§6npc : §eДодати повідомлення, написане NPC.' + player: '§6player : §eДодати повідомлення, написане гравцем.' + nothing: '§6noSender : §eДодати повідомлення без відправника.' + remove: '§6remove : §eВидалити повідомлення.' + list: '§6list: §eПереглянути всі повідомлення.' + npcInsert: '§6npcInsert : §eВставити повідомлення, яке буде сказано NPC.' + playerInsert: '§6playerInsert : §eВставте повідомлення сказане гравцем.' + nothingInsert: '§6nothingInsert : §eВставте повідомлення без префікса.' + edit: '§6edit : §eРедагування повідомлення.' + addSound: '§6addSound : §eДодати звук до повідомлення.' + clear: '§6clear: §eВидалити всі повідомлення.' + close: '§6close: §eПеревірити всі повідомлення.' + setTime: '§6setTime