entry : clicks.entrySet()) {
+ lore[i++] = "§8" + Utils.clickName(entry.getKey()) + " > §7" + entry.getValue();
+ }
+
+ return lore;
+ }
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/gui/close/CloseBehavior.java b/api/src/main/java/fr/skytasul/quests/api/gui/close/CloseBehavior.java
new file mode 100644
index 00000000..1aa64413
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/gui/close/CloseBehavior.java
@@ -0,0 +1,10 @@
+package fr.skytasul.quests.api.gui.close;
+
+/**
+ * Represents the behavior that a closing GUI must take.
+ *
+ * This class is effectively sealed: its only subclasses are {@link StandardCloseBehavior},
+ * {@link OpenCloseBehavior} and {@link DelayCloseBehavior}.
+ */
+public interface CloseBehavior {
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/gui/close/DelayCloseBehavior.java b/api/src/main/java/fr/skytasul/quests/api/gui/close/DelayCloseBehavior.java
new file mode 100644
index 00000000..e5a9f152
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/gui/close/DelayCloseBehavior.java
@@ -0,0 +1,22 @@
+package fr.skytasul.quests.api.gui.close;
+
+import java.util.Objects;
+import org.jetbrains.annotations.NotNull;
+
+/**
+ * This close behavior will remove the player from the GUI system and execute the runnable on the
+ * next tick.
+ */
+public class DelayCloseBehavior implements CloseBehavior {
+
+ private final @NotNull Runnable delayed;
+
+ public DelayCloseBehavior(@NotNull Runnable delayed) {
+ this.delayed = Objects.requireNonNull(delayed);
+ }
+
+ public @NotNull Runnable getDelayed() {
+ return delayed;
+ }
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/gui/close/OpenCloseBehavior.java b/api/src/main/java/fr/skytasul/quests/api/gui/close/OpenCloseBehavior.java
new file mode 100644
index 00000000..d68b7879
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/gui/close/OpenCloseBehavior.java
@@ -0,0 +1,19 @@
+package fr.skytasul.quests.api.gui.close;
+
+import java.util.Objects;
+import org.jetbrains.annotations.NotNull;
+import fr.skytasul.quests.api.gui.Gui;
+
+public class OpenCloseBehavior implements CloseBehavior {
+
+ private final @NotNull Gui other;
+
+ public OpenCloseBehavior(@NotNull Gui other) {
+ this.other = Objects.requireNonNull(other);
+ }
+
+ public @NotNull Gui getOther() {
+ return other;
+ }
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/gui/close/StandardCloseBehavior.java b/api/src/main/java/fr/skytasul/quests/api/gui/close/StandardCloseBehavior.java
new file mode 100644
index 00000000..94cd537b
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/gui/close/StandardCloseBehavior.java
@@ -0,0 +1,7 @@
+package fr.skytasul.quests.api.gui.close;
+
+public enum StandardCloseBehavior implements CloseBehavior {
+
+ REOPEN, CONFIRM, REMOVE, NOTHING;
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/gui/layout/LayoutedButton.java b/api/src/main/java/fr/skytasul/quests/api/gui/layout/LayoutedButton.java
new file mode 100644
index 00000000..6cf6d96d
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/gui/layout/LayoutedButton.java
@@ -0,0 +1,176 @@
+package fr.skytasul.quests.api.gui.layout;
+
+import java.util.List;
+import java.util.function.BooleanSupplier;
+import java.util.function.Supplier;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import com.cryptomorin.xseries.XMaterial;
+import fr.skytasul.quests.api.gui.ItemUtils;
+import fr.skytasul.quests.api.options.QuestOption;
+
+public interface LayoutedButton extends LayoutedClickHandler {
+
+ public void place(@NotNull Inventory inventory, int slot);
+
+ public default boolean isValid() {
+ return true;
+ }
+
+ interface ItemButton extends LayoutedButton {
+
+ public @Nullable ItemStack getItem();
+
+ @Override
+ default void place(@NotNull Inventory inventory, int slot) {
+ inventory.setItem(slot, getItem());
+ }
+
+ }
+
+ public static @NotNull LayoutedButton create(@NotNull XMaterial material, @Nullable String name, @Nullable List lore,
+ @NotNull LayoutedClickHandler click) {
+ return new ItemButton() {
+
+ @Override
+ public void click(@NotNull LayoutedClickEvent event) {
+ click.click(event);
+ }
+
+ @Override
+ public @Nullable ItemStack getItem() {
+ return ItemUtils.item(material, name, lore);
+ }
+
+ };
+ }
+
+ public static @NotNull LayoutedButton create(@NotNull XMaterial material, @Nullable String name,
+ @NotNull Supplier<@Nullable List> lore, @NotNull LayoutedClickHandler click) {
+ return new ItemButton() {
+
+ @Override
+ public void click(@NotNull LayoutedClickEvent event) {
+ click.click(event);
+ }
+
+ @Override
+ public @Nullable ItemStack getItem() {
+ return ItemUtils.item(material, name, lore.get());
+ }
+
+ };
+ }
+
+ public static @NotNull LayoutedButton createLoreValue(@NotNull XMaterial material, @Nullable String name,
+ @NotNull Supplier<@Nullable Object> loreValue, @NotNull LayoutedClickHandler click) {
+ return new ItemButton() {
+
+ @Override
+ public void click(@NotNull LayoutedClickEvent event) {
+ click.click(event);
+ }
+
+ @Override
+ public @Nullable ItemStack getItem() {
+ @Nullable
+ Object value = loreValue.get();
+ return ItemUtils.item(material, name,
+ QuestOption.formatNullableValue(value == null ? null : value.toString()));
+ }
+
+ };
+ }
+
+ public static @NotNull LayoutedButton create(@NotNull XMaterial material, @NotNull Supplier<@Nullable String> name,
+ @NotNull Supplier<@Nullable List> lore, @NotNull LayoutedClickHandler click) {
+ return new ItemButton() {
+
+ @Override
+ public void click(@NotNull LayoutedClickEvent event) {
+ click.click(event);
+ }
+
+ @Override
+ public @Nullable ItemStack getItem() {
+ return ItemUtils.item(material, name.get(), lore.get());
+ }
+
+ };
+ }
+
+ public static @NotNull LayoutedButton create(@NotNull XMaterial material, @NotNull Supplier<@Nullable String> name,
+ @Nullable List lore, @NotNull LayoutedClickHandler click) {
+ return new ItemButton() {
+
+ @Override
+ public void click(@NotNull LayoutedClickEvent event) {
+ click.click(event);
+ }
+
+ @Override
+ public @Nullable ItemStack getItem() {
+ return ItemUtils.item(material, name.get(), lore);
+ }
+
+ };
+ }
+
+ public static @NotNull LayoutedButton create(@NotNull Supplier<@NotNull XMaterial> material,
+ @NotNull Supplier<@Nullable String> name, @NotNull Supplier<@Nullable List> lore,
+ @NotNull LayoutedClickHandler click) {
+ return new ItemButton() {
+
+ @Override
+ public void click(@NotNull LayoutedClickEvent event) {
+ click.click(event);
+ }
+
+ @Override
+ public @Nullable ItemStack getItem() {
+ return ItemUtils.item(material.get(), name.get(), lore.get());
+ }
+
+ };
+ }
+
+ public static @NotNull LayoutedButton create(@NotNull Supplier<@Nullable ItemStack> item, @NotNull LayoutedClickHandler click) {
+ return new ItemButton() {
+
+ @Override
+ public void click(@NotNull LayoutedClickEvent event) {
+ click.click(event);
+ }
+
+ @Override
+ public @Nullable ItemStack getItem() {
+ return item.get();
+ }
+
+ };
+ }
+
+ public static @NotNull LayoutedButton create(@Nullable ItemStack item, @NotNull LayoutedClickHandler click) {
+ return new ItemButton() {
+
+ @Override
+ public void click(@NotNull LayoutedClickEvent event) {
+ click.click(event);
+ }
+
+ @Override
+ public @Nullable ItemStack getItem() {
+ return item;
+ }
+
+ };
+ }
+
+ public static @NotNull LayoutedButton createSwitch(@NotNull BooleanSupplier stateSupplier, @NotNull String name,
+ @Nullable List<@Nullable String> lore, @NotNull LayoutedClickHandler click) {
+ return create(() -> ItemUtils.itemSwitch(name, stateSupplier.getAsBoolean(), lore.toArray(new String[0])), click);
+ }
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/gui/layout/LayoutedClickEvent.java b/api/src/main/java/fr/skytasul/quests/api/gui/layout/LayoutedClickEvent.java
new file mode 100644
index 00000000..e4658366
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/gui/layout/LayoutedClickEvent.java
@@ -0,0 +1,43 @@
+package fr.skytasul.quests.api.gui.layout;
+
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import fr.skytasul.quests.api.gui.GuiClickEvent;
+
+public class LayoutedClickEvent extends GuiClickEvent {
+
+ private @NotNull LayoutedGUI gui;
+
+ public LayoutedClickEvent(@NotNull Player player, @NotNull LayoutedGUI gui, @Nullable ItemStack clicked,
+ @Nullable ItemStack cursor, int slot, @NotNull ClickType click) {
+ super(player, gui, clicked, cursor, slot, click);
+ this.gui = gui;
+ }
+
+ @Override
+ public void setCancelled(boolean cancelled) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public @NotNull LayoutedGUI getGui() {
+ return gui;
+ }
+
+ public void refreshItem() {
+ gui.refresh(getSlot());
+ }
+
+ public void refreshItemReopen() {
+ gui.refresh(getSlot());
+ gui.reopen(getPlayer());
+ }
+
+ public void refreshGuiReopen() {
+ gui.reopen(getPlayer(), true);
+ }
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/gui/layout/LayoutedClickHandler.java b/api/src/main/java/fr/skytasul/quests/api/gui/layout/LayoutedClickHandler.java
new file mode 100644
index 00000000..57223009
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/gui/layout/LayoutedClickHandler.java
@@ -0,0 +1,11 @@
+package fr.skytasul.quests.api.gui.layout;
+
+import org.jetbrains.annotations.NotNull;
+
+public interface LayoutedClickHandler {
+
+ public static final LayoutedClickHandler EMPTY = event -> {};
+
+ void click(@NotNull LayoutedClickEvent event);
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/gui/layout/LayoutedGUI.java b/api/src/main/java/fr/skytasul/quests/api/gui/layout/LayoutedGUI.java
new file mode 100644
index 00000000..c1cf952d
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/gui/layout/LayoutedGUI.java
@@ -0,0 +1,171 @@
+package fr.skytasul.quests.api.gui.layout;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import org.apache.commons.lang.Validate;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.InventoryType;
+import org.bukkit.inventory.Inventory;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import fr.skytasul.quests.api.gui.AbstractGui;
+import fr.skytasul.quests.api.gui.GuiClickEvent;
+import fr.skytasul.quests.api.gui.close.CloseBehavior;
+import fr.skytasul.quests.api.gui.close.StandardCloseBehavior;
+
+public abstract class LayoutedGUI extends AbstractGui {
+
+ protected final @Nullable String name;
+ protected final @NotNull Map buttons;
+ protected final @NotNull CloseBehavior closeBehavior;
+
+ protected LayoutedGUI(@Nullable String name, @NotNull Map buttons,
+ @NotNull CloseBehavior closeBehavior) {
+ this.name = name;
+ this.buttons = buttons;
+ this.closeBehavior = closeBehavior;
+ }
+
+ @Override
+ protected void populate(@NotNull Player player, @NotNull Inventory inventory) {
+ buttons.forEach((slot, button) -> {
+ if (button.isValid())
+ button.place(inventory, slot);
+ });
+ }
+
+ @Override
+ public final void onClick(GuiClickEvent event) {
+ LayoutedButton button = buttons.get(event.getSlot());
+ if (button == null || !button.isValid())
+ return;
+
+ button.click(new LayoutedClickEvent(event.getPlayer(), this, event.getClicked(), event.getCursor(), event.getSlot(),
+ event.getClick()));
+ }
+
+ public void refresh(int slot) {
+ if (getInventory() == null)
+ return;
+
+ LayoutedButton button = buttons.get(slot);
+ if (button == null || !button.isValid()) {
+ getInventory().setItem(slot, null);
+ } else {
+ button.place(getInventory(), slot);
+ }
+ }
+
+ public void refresh(@NotNull LayoutedButton button) {
+ if (getInventory() == null)
+ return;
+
+ buttons.forEach((slot, otherButton) -> {
+ if (otherButton.equals(button))
+ refresh(slot);
+ });
+ }
+
+ @Override
+ public @NotNull CloseBehavior onClose(@NotNull Player player) {
+ return closeBehavior;
+ }
+
+ public static @NotNull Builder newBuilder() {
+ return new Builder();
+ }
+
+ public static class LayoutedRowsGUI extends LayoutedGUI {
+
+ private final int rows;
+
+ protected LayoutedRowsGUI(@Nullable String name, @NotNull Map buttons,
+ @NotNull CloseBehavior closeBehavior, int rows) {
+ super(name, buttons, closeBehavior);
+ Validate.isTrue(rows >= 1);
+ this.rows = rows;
+ }
+
+ @Override
+ protected final Inventory instanciate(@NotNull Player player) {
+ return Bukkit.createInventory(null, rows * 9, name);
+ }
+
+ }
+
+ public static class LayoutedTypeGUI extends LayoutedGUI {
+
+ private @NotNull InventoryType type;
+
+ protected LayoutedTypeGUI(@Nullable String name, @NotNull Map buttons,
+ @NotNull CloseBehavior closeBehavior, @NotNull InventoryType type) {
+ super(name, buttons, closeBehavior);
+ this.type = Objects.requireNonNull(type);
+ }
+
+ @Override
+ protected final Inventory instanciate(@NotNull Player player) {
+ return Bukkit.createInventory(null, type, name);
+ }
+
+ }
+
+ public static class Builder {
+
+ private final Map buttons = new HashMap<>();
+ private @Nullable Integer rows = null;
+ private @Nullable InventoryType type = null;
+ private @Nullable String name = null;
+ private @NotNull CloseBehavior closeBehavior = StandardCloseBehavior.CONFIRM;
+
+ private Builder() {}
+
+ public @NotNull Builder addButton(int slot, @NotNull LayoutedButton button) {
+ Validate.isTrue(!buttons.containsKey(slot));
+ buttons.put(slot, button);
+ return this;
+ }
+
+ public @NotNull Builder setRowNumber(int rows) {
+ Validate.isTrue(rows >= 1);
+ this.rows = rows;
+ this.type = null;
+ return this;
+ }
+
+ public @NotNull Builder setInventoryType(@Nullable InventoryType type) {
+ this.type = type;
+ this.rows = null;
+ return this;
+ }
+
+ public @NotNull Builder setName(@Nullable String name) {
+ this.name = name;
+ return this;
+ }
+
+ public @NotNull Builder setCloseBehavior(@NotNull CloseBehavior closeBehavior) {
+ this.closeBehavior = closeBehavior;
+ return this;
+ }
+
+ public @NotNull LayoutedGUI build() {
+ if (buttons.isEmpty())
+ throw new IllegalArgumentException("Cannot build a layouted GUI with no buttons");
+
+ if (type != null)
+ return new LayoutedTypeGUI(name, buttons, closeBehavior, type);
+
+ if (rows == null) {
+ int maxSlot = buttons.keySet().stream().mapToInt(Integer::intValue).max().getAsInt();
+ rows = (int) Math.floor(maxSlot / 9D) + 1;
+ }
+
+ return new LayoutedRowsGUI(name, buttons, closeBehavior, rows);
+ }
+
+ }
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/gui/templates/ChooseGUI.java b/api/src/main/java/fr/skytasul/quests/api/gui/templates/ChooseGUI.java
new file mode 100644
index 00000000..1ed0998e
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/gui/templates/ChooseGUI.java
@@ -0,0 +1,61 @@
+package fr.skytasul.quests.api.gui.templates;
+
+import java.util.List;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.NotNull;
+import fr.skytasul.quests.api.gui.AbstractGui;
+import fr.skytasul.quests.api.gui.GuiClickEvent;
+import fr.skytasul.quests.api.gui.close.CloseBehavior;
+import fr.skytasul.quests.api.gui.close.StandardCloseBehavior;
+
+public abstract class ChooseGUI extends AbstractGui {
+
+ private List available;
+
+ protected ChooseGUI(List available) {
+ this.available = available;
+ }
+
+ @Override
+ protected Inventory instanciate(@NotNull Player player) {
+ return Bukkit.createInventory(null, (int) Math.ceil(available.size() * 1.0 / 9.0) * 9, name());
+ }
+
+ @Override
+ protected void populate(@NotNull Player player, @NotNull Inventory inventory) {
+ for (int i = 0; i < available.size(); i++) {
+ inventory.setItem(i, getItemStack(available.get(i)));
+ }
+ }
+
+ @Override
+ public void onClick(GuiClickEvent event) {
+ finish(available.get(event.getSlot()));
+ }
+
+ @Override
+ public CloseBehavior onClose(Player player) {
+ return StandardCloseBehavior.REOPEN;
+ }
+
+ /**
+ * @return Inventory's name
+ */
+ public abstract String name();
+
+ /**
+ * @param object existing object to represent
+ * @return ItemStack who represents the object
+ */
+
+ public abstract ItemStack getItemStack(T object);
+
+ /**
+ * Called when the player click on an item
+ */
+ public abstract void finish(T object);
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/gui/templates/ConfirmGUI.java b/api/src/main/java/fr/skytasul/quests/api/gui/templates/ConfirmGUI.java
new file mode 100644
index 00000000..ad8a04e8
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/gui/templates/ConfirmGUI.java
@@ -0,0 +1,42 @@
+package fr.skytasul.quests.api.gui.templates;
+
+import java.util.Collections;
+import java.util.List;
+import org.bukkit.event.inventory.InventoryType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import com.cryptomorin.xseries.XMaterial;
+import fr.skytasul.quests.api.gui.AbstractGui;
+import fr.skytasul.quests.api.gui.close.StandardCloseBehavior;
+import fr.skytasul.quests.api.gui.layout.LayoutedButton;
+import fr.skytasul.quests.api.gui.layout.LayoutedClickHandler;
+import fr.skytasul.quests.api.gui.layout.LayoutedGUI;
+import fr.skytasul.quests.api.localization.Lang;
+
+public final class ConfirmGUI {
+
+ private ConfirmGUI() {}
+
+ public static AbstractGui confirm(@Nullable Runnable yes, @Nullable Runnable no, @NotNull String indication,
+ @Nullable List<@Nullable String> lore) {
+ return LayoutedGUI.newBuilder()
+ .addButton(1,
+ LayoutedButton.create(XMaterial.LIME_DYE, Lang.confirmYes.toString(), Collections.emptyList(), event -> {
+ event.close();
+ if (yes != null)
+ yes.run();
+ }))
+ .addButton(3,
+ LayoutedButton.create(XMaterial.RED_DYE, Lang.confirmNo.toString(), Collections.emptyList(), event -> {
+ event.close();
+ if (no != null)
+ no.run();
+ }))
+ .addButton(2, LayoutedButton.create(XMaterial.PAPER, indication, lore, LayoutedClickHandler.EMPTY))
+ .setInventoryType(InventoryType.HOPPER)
+ .setName(Lang.INVENTORY_CONFIRM.toString())
+ .setCloseBehavior(StandardCloseBehavior.REOPEN)
+ .build();
+ }
+
+}
diff --git a/core/src/main/java/fr/skytasul/quests/gui/templates/ListGUI.java b/api/src/main/java/fr/skytasul/quests/api/gui/templates/ListGUI.java
similarity index 67%
rename from core/src/main/java/fr/skytasul/quests/gui/templates/ListGUI.java
rename to api/src/main/java/fr/skytasul/quests/api/gui/templates/ListGUI.java
index 361781a9..e472ed1a 100644
--- a/core/src/main/java/fr/skytasul/quests/gui/templates/ListGUI.java
+++ b/api/src/main/java/fr/skytasul/quests/api/gui/templates/ListGUI.java
@@ -1,4 +1,4 @@
-package fr.skytasul.quests.gui.templates;
+package fr.skytasul.quests.api.gui.templates;
import java.util.Collection;
import java.util.List;
@@ -7,11 +7,14 @@
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.gui.ItemUtils;
-import fr.skytasul.quests.utils.Lang;
-import fr.skytasul.quests.utils.XMaterial;
+import org.jetbrains.annotations.NotNull;
+import com.cryptomorin.xseries.XMaterial;
+import fr.skytasul.quests.api.gui.ItemUtils;
+import fr.skytasul.quests.api.gui.LoreBuilder;
+import fr.skytasul.quests.api.gui.close.CloseBehavior;
+import fr.skytasul.quests.api.gui.close.StandardCloseBehavior;
+import fr.skytasul.quests.api.localization.Lang;
/**
* An inventory which has up to 54 slots to store items. Each item is linked in a list to an instance of type T.
@@ -23,7 +26,7 @@ public abstract class ListGUI extends PagedGUI {
private ItemStack create = ItemUtils.item(XMaterial.SLIME_BALL, Lang.addObject.toString());
- public ListGUI(String name, DyeColor color, Collection objects) {
+ protected ListGUI(@NotNull String name, @NotNull DyeColor color, @NotNull Collection objects) {
super(name, color, objects);
if (objects.contains(null))
throw new IllegalArgumentException("Object cannot be null in a list GUI");
@@ -42,7 +45,7 @@ public final ItemStack getItemStack(T object) {
@Override
public final void click(T existing, ItemStack item, ClickType clickType) {
- if (clickType == ClickType.MIDDLE) {
+ if (existing != null && clickType == getRemoveClick(existing)) {
remove(existing);
}else {
if (existing == null) {
@@ -51,6 +54,15 @@ public final void click(T existing, ItemStack item, ClickType clickType) {
}
}
+ protected ClickType getRemoveClick(@NotNull T object) {
+ return ClickType.SHIFT_LEFT;
+ }
+
+ protected @NotNull LoreBuilder createLoreBuilder(@NotNull T object) {
+ return new LoreBuilder()
+ .addClick(getRemoveClick(object), "§c" + Lang.Remove.toString());
+ }
+
public boolean remove(T object) {
int index = objects.indexOf(object);
if (index == -1) return false;
@@ -74,12 +86,13 @@ public void updateObject(T object, T newObject) {
if (index == -1) return;
objects.set(index, newObject);
int slot = getObjectSlot(newObject);
- if (slot != -1) inv.setItem(slot, getItemStack(newObject));
+ if (slot != -1)
+ getInventory().setItem(slot, getItemStack(newObject));
}
@Override
- public CloseBehavior onClose(Player p, Inventory inv) {
- return CloseBehavior.REOPEN;
+ public CloseBehavior onClose(Player player) {
+ return StandardCloseBehavior.REOPEN;
}
/**
@@ -93,8 +106,8 @@ private ItemStack finishItem(T object) {
calcMaxPages();
page = maxPage - 1;
setItems();
- reopen();
- return inv.getItem(getObjectSlot(object));
+ reopen(player);
+ return getInventory().getItem(getObjectSlot(object));
}
/**
diff --git a/api/src/main/java/fr/skytasul/quests/api/gui/templates/PagedGUI.java b/api/src/main/java/fr/skytasul/quests/api/gui/templates/PagedGUI.java
new file mode 100644
index 00000000..29329b4b
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/gui/templates/PagedGUI.java
@@ -0,0 +1,256 @@
+package fr.skytasul.quests.api.gui.templates;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+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.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import com.cryptomorin.xseries.XMaterial;
+import fr.skytasul.quests.api.QuestsConfiguration;
+import fr.skytasul.quests.api.QuestsPlugin;
+import fr.skytasul.quests.api.editors.TextEditor;
+import fr.skytasul.quests.api.gui.AbstractGui;
+import fr.skytasul.quests.api.gui.GuiClickEvent;
+import fr.skytasul.quests.api.gui.ItemUtils;
+import fr.skytasul.quests.api.localization.Lang;
+import fr.skytasul.quests.api.utils.LevenshteinComparator;
+
+/**
+ * An inventory with an infinite amount of pages of 35 items (integer limit).
+ * @author SkytAsul
+ *
+ * @param type of objects stocked in the inventory
+ */
+public abstract class PagedGUI extends AbstractGui {
+
+ private static ItemStack itemSearch = ItemUtils.item(XMaterial.COMPASS, Lang.search.toString());
+
+ protected Player player;
+ protected int page = 0;
+ protected int maxPage;
+
+ private final int columns;
+ private final int dataSlots;
+
+ private String name;
+ private DyeColor color;
+ protected List objects;
+ protected Consumer> validate;
+ private ItemStack validationItem = QuestsPlugin.getPlugin().getGuiManager().getItemFactory().getDone();
+ protected LevenshteinComparator comparator;
+
+ protected PagedGUI(@NotNull String name, @Nullable DyeColor color, @NotNull Collection objects) {
+ this(name, color, objects, null, null);
+ }
+
+ protected PagedGUI(@NotNull String name, @Nullable DyeColor color, @NotNull Collection objects,
+ @Nullable Consumer> validate, @Nullable Function searchName) {
+ this.name = name;
+ this.color = color;
+ this.objects = new ArrayList<>(objects);
+ this.validate = validate;
+ if (searchName != null) this.comparator = new LevenshteinComparator<>(searchName);
+
+ columns = QuestsConfiguration.getConfig().getGuiConfig().showVerticalSeparator() ? 7 : 8;
+ dataSlots = columns * 5;
+ }
+
+ @Override
+ protected Inventory instanciate(@NotNull Player player) {
+ return Bukkit.createInventory(null, 45, name);
+ }
+
+ @Override
+ protected void populate(@NotNull Player player, @NotNull Inventory inventory) {
+ this.player = player;
+ calcMaxPages();
+
+ setBarItem(0, QuestsPlugin.getPlugin().getGuiManager().getItemFactory().getPreviousPage());
+ setBarItem(4, QuestsPlugin.getPlugin().getGuiManager().getItemFactory().getNextPage());
+ if (validate != null) setBarItem(2, validationItem);
+ if (comparator != null) setBarItem(3, itemSearch);
+
+ displaySeparator();
+
+ setItems();
+ }
+
+ private void displaySeparator() {
+ if (color != null && QuestsConfiguration.getConfig().getGuiConfig().showVerticalSeparator()) {
+ for (int i = 0; i < 5; i++)
+ getInventory().setItem(i * 9 + columns, ItemUtils.itemSeparator(color));
+ }
+ }
+
+ public PagedGUI setValidate(Consumer> validate, ItemStack validationItem) {
+ if (this.validate != null) throw new IllegalStateException("A validation has already be added.");
+ if (this.getInventory() != null)
+ throw new IllegalStateException("Cannot add a validation after inventory opening.");
+ if (validationItem == null) throw new IllegalArgumentException("Cannot set a null validation item.");
+ this.validate = validate;
+ this.validationItem = validationItem;
+ return this;
+ }
+
+ public PagedGUI sortValuesByName() {
+ Validate.notNull(comparator);
+ sortValues(comparator.getFunction());
+ return this;
+ }
+
+ public > PagedGUI sortValues(Function mapper) {
+ objects.sort((o1, o2) -> {
+ C map1;
+ if (o1 == null || (map1 = mapper.apply(o1)) == null) return 1;
+ C map2;
+ if (o2 == null || (map2 = mapper.apply(o2)) == null) return -1;
+ return map1.compareTo(map2);
+ });
+ return this;
+ }
+
+ public void setSeparatorColor(@Nullable DyeColor color) {
+ this.color = color;
+ if (getInventory() != null)
+ displaySeparator();
+ }
+
+ public void setObjects(@NotNull Collection objects) {
+ this.objects = new ArrayList<>(objects);
+ page = 0;
+ calcMaxPages();
+ setItems();
+ }
+
+ protected void calcMaxPages() {
+ this.maxPage = objects.isEmpty() ? 1 : (int) Math.ceil(objects.size() * 1D / 35D);
+ }
+
+ protected void setItems() {
+ for (int i = 0; i < dataSlots; i++)
+ setMainItem(i, null);
+ for (int i = page * dataSlots; i < objects.size(); i++) {
+ if (i == (page + 1) * dataSlots)
+ break;
+ T obj = objects.get(i);
+ setMainItem(i - page * dataSlots, getItemStack(obj));
+ }
+ }
+
+ private int setMainItem(int mainSlot, ItemStack is){
+ int line = (int) Math.floor(mainSlot * 1.0 / columns);
+ int slot = mainSlot + ((9 - columns) * line);
+ setItem(is, slot);
+ return slot;
+ }
+
+ protected int setBarItem(int barSlot, ItemStack is) {
+ int slot = barSlot * 9 + 8; // always at last column so +8
+ setItem(is, slot);
+ return slot;
+ }
+
+ private void setItem(ItemStack is, int rawSlot) {
+ getInventory().setItem(rawSlot, is);
+
+ if (is != null && is.getType() != Material.AIR) {
+ ItemStack invItem = getInventory().getItem(rawSlot);
+ if (invItem == null || invItem.getType() == Material.AIR) {
+ // means the item was a material that cannot be put in an inventory:
+ // fire, water block, crops...
+ is = is.clone();
+ is.setType(Material.STONE);
+ getInventory().setItem(rawSlot, is);
+ }
+ }
+ }
+
+ /**
+ * @param object T object to get the slot from
+ * @return slot in the inventory, -1 if the object is on another page
+ */
+ public int getObjectSlot(T object){
+ int index = objects.indexOf(object);
+ if (index < page * dataSlots || index > (page + 1) * dataSlots)
+ return -1;
+
+ int line = (int) Math.floor(index * 1.0 / columns);
+ return index + ((9 - columns) * line) - page * dataSlots;
+ }
+
+
+ @Override
+ public void onClick(GuiClickEvent event) {
+ int column = event.getSlot() % 9;
+ if (column == 8) {
+ int barSlot = (event.getSlot() - 8) / 9;
+ barClick(event, barSlot);
+ } else if (!QuestsConfiguration.getConfig().getGuiConfig().showVerticalSeparator() || column != 7) {
+ int line = (int) Math.floor(event.getSlot() * 1D / 9D);
+ int objectSlot = event.getSlot() - line * (9 - columns) + page * dataSlots;
+ click(objects.get(objectSlot), event.getClicked(), event.getClick());
+ // inv.setItem(slot, getItemStack(objects.get(objectSlot)));
+ }
+ }
+
+ protected void barClick(GuiClickEvent event, int barSlot) {
+ switch (barSlot) {
+ case 0:
+ if (page == 0) break;
+ page--;
+ setItems();
+ break;
+ case 4:
+ if (page+1 == maxPage) break;
+ page++;
+ setItems();
+ break;
+
+ case 2:
+ validate.accept(objects);
+ break;
+
+ case 3:
+ new TextEditor(player, this::reopen, obj -> {
+ objects.sort(comparator.setReference(obj));
+ page = 0;
+ setItems();
+ reopen();
+ }).start();
+ break;
+ }
+ }
+
+ public final void reopen() {
+ reopen(player);
+ }
+
+ public final void close() {
+ close(player);
+ }
+
+ /**
+ * @param object existing object to represent
+ * @return ItemStack who represents the object
+ */
+ public abstract @NotNull ItemStack getItemStack(@NotNull T object);
+
+ /**
+ * Called when an object is clicked
+ * @param existing clicked object
+ * @param item item clicked
+ * @param clickType click type
+ */
+ public abstract void click(@NotNull T existing, @NotNull ItemStack item, @NotNull ClickType clickType);
+
+}
diff --git a/core/src/main/java/fr/skytasul/quests/gui/templates/StaticPagedGUI.java b/api/src/main/java/fr/skytasul/quests/api/gui/templates/StaticPagedGUI.java
similarity index 74%
rename from core/src/main/java/fr/skytasul/quests/gui/templates/StaticPagedGUI.java
rename to api/src/main/java/fr/skytasul/quests/api/gui/templates/StaticPagedGUI.java
index 5129fb80..e02e5cd3 100644
--- a/core/src/main/java/fr/skytasul/quests/gui/templates/StaticPagedGUI.java
+++ b/api/src/main/java/fr/skytasul/quests/api/gui/templates/StaticPagedGUI.java
@@ -1,17 +1,16 @@
-package fr.skytasul.quests.gui.templates;
+package fr.skytasul.quests.api.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;
+import fr.skytasul.quests.api.gui.close.CloseBehavior;
+import fr.skytasul.quests.api.gui.close.DelayCloseBehavior;
+import fr.skytasul.quests.api.gui.close.StandardCloseBehavior;
public class StaticPagedGUI extends PagedGUI> {
@@ -39,12 +38,11 @@ public void click(Entry existing, ItemStack item, ClickType clickT
}
@Override
- public CloseBehavior onClose(Player p, Inventory inv) {
+ public CloseBehavior onClose(Player player) {
if (cancelAllowed) {
- Utils.runSync(() -> clicked.accept(null));
- return CloseBehavior.NOTHING;
+ return new DelayCloseBehavior(() -> clicked.accept(null));
}else {
- return CloseBehavior.REOPEN;
+ return StandardCloseBehavior.REOPEN;
}
}
diff --git a/core/src/main/java/fr/skytasul/quests/utils/Lang.java b/api/src/main/java/fr/skytasul/quests/api/localization/Lang.java
similarity index 81%
rename from core/src/main/java/fr/skytasul/quests/utils/Lang.java
rename to api/src/main/java/fr/skytasul/quests/api/localization/Lang.java
index c0eea28e..c2aee23a 100644
--- a/core/src/main/java/fr/skytasul/quests/utils/Lang.java
+++ b/api/src/main/java/fr/skytasul/quests/api/localization/Lang.java
@@ -1,12 +1,16 @@
-package fr.skytasul.quests.utils;
+package fr.skytasul.quests.api.localization;
-import fr.skytasul.quests.api.Locale;
+import java.util.Objects;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import fr.skytasul.quests.api.utils.messaging.MessageType;
/**
* Stores all string paths and methods to format and send them to players.
*/
+@SuppressWarnings("squid:S115")
public enum Lang implements Locale {
-
+
/* Formats (must be first to be used after) */
Prefix("misc.format.prefix"),
NpcText("misc.format.npcText"), // 0: npc, 1: msg, 2: index, 3: max
@@ -16,7 +20,7 @@ public enum Lang implements Locale {
ErrorPrefix("misc.format.errorPrefix"),
SuccessPrefix("misc.format.successPrefix"),
RequirementNotMetPrefix("misc.format.requirementNotMetPrefix"),
-
+
/* Messages */
FINISHED_BASE("msg.quest.finished.base"),
FINISHED_OBTAIN("msg.quest.finished.obtain"),
@@ -30,28 +34,27 @@ public enum Lang implements Locale {
POOL_INVALID("msg.quest.invalidPoolID"), // 0: pool id
ALREADY_STARTED("msg.quest.alreadyStarted"),
QUEST_NOT_STARTED("msg.quest.notStarted"),
-
+
QUESTS_MAX_LAUNCHED("msg.quests.maxLaunched"), // 0: max quests
- QUEST_NOSTEPS("msg.quests.nopStep"),
QUEST_UPDATED("msg.quests.updated"),
QUEST_CHECKPOINT("msg.quests.checkpoint"),
QUEST_FAILED("msg.quests.failed"),
-
- DIALOG_SKIPPED("msg.dialogs.skipped"),
+
+ DIALOG_SKIPPED("msg.dialogs.skipped", MessageType.DefaultMessageType.UNPREFIXED),
DIALOG_TOO_FAR("msg.dialogs.tooFar"), // 0: npc name
-
+
POOL_NO_TIME("msg.pools.noTime"), // 0: time left
POOL_ALL_COMPLETED("msg.pools.allCompleted"),
POOL_NO_AVAILABLE("msg.pools.noAvailable"),
POOL_MAX_QUESTS("msg.pools.maxQuests"), // 0: max quests
-
+
QUEST_ITEM_DROP("msg.questItem.drop"),
QUEST_ITEM_CRAFT("msg.questItem.craft"),
QUEST_ITEM_EAT("msg.questItem.eat"),
-
- STAGE_NOMOBS("msg.stageMobs.noMobs"),
+ QUEST_ITEM_PLACE("msg.questItem.place"),
+
STAGE_MOBSLIST("msg.stageMobs.listMobs"),
-
+
TYPE_CANCEL("msg.typeCancel"),
NPC_TEXT("msg.writeNPCText"),
REGION_NAME("msg.writeRegionName"),
@@ -66,8 +69,8 @@ public enum Lang implements Locale {
DESC_MESSAGE("msg.writeDescriptionText"),
START_TEXT("msg.writeStageText"),
MOVE_TELEPORT_POINT("msg.moveToTeleportPoint"),
- NPC_NAME("msg.writeNpcName"),
- NPC_SKIN("msg.writeNpcSkinName"),
+ NPC_NAME("msg.writeNpcName"),
+ NPC_SKIN("msg.writeNpcSkinName"),
QUEST_NAME("msg.writeQuestName"),
COMMAND("msg.writeCommand"),
COMMAND_DELAY("msg.writeCommandDelay"),
@@ -76,7 +79,7 @@ public enum Lang implements Locale {
CONFIRM_MESSAGE("msg.writeConfirmMessage"),
QUEST_DESCRIPTION("msg.writeQuestDescription"),
QUEST_MATERIAL("msg.writeQuestMaterial"),
-
+
REQUIREMENT_QUEST("msg.requirements.quest"),
REQUIREMENT_LEVEL("msg.requirements.level"),
REQUIREMENT_JOB("msg.requirements.job"),
@@ -84,45 +87,31 @@ public enum Lang implements Locale {
REQUIREMENT_COMBAT_LEVEL("msg.requirements.combatLevel"),
REQUIREMENT_MONEY("msg.requirements.money"), // 0: money
QUEST_WAIT("msg.requirements.waitTime"), //0: time
-
+
XP_EDITED("msg.experience.edited"),
SELECT_KILL_NPC("msg.selectNPCToKill"),
-
- NPC_REMOVE("msg.npc.remove"),
- TALK_NPC("msg.npc.talk"),
-
+
REGION_DOESNT_EXIST("msg.regionDoesntExists"),
NPC_DOESNT_EXIST("msg.npcDoesntExist"),
- OBJECT_DOESNT_EXIST("msg.objectDoesntExist"),
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"),
INVALID_BLOCK_DATA("msg.invalidBlockData"), // 0: blockdata, 1: material
INVALID_BLOCK_TAG("msg.invalidBlockTag"), // 0: tag
-
+
NEED_OBJECTS("msg.bringBackObjects"),
ITEM_DROPPED("msg.inventoryFull"),
-
- PLAYER_NEVER_CONNECTED("msg.playerNeverConnected"),
- PLAYER_NOT_ONLINE("msg.playerNotOnline"),
- PLAYER_DATA_NOT_FOUND("msg.playerDataNotFound"), // 0: player name
-
+
VERSION_REQUIRED("msg.versionRequired", ErrorPrefix), // 0: version
-
+
RESTART_SERVER("msg.restartServer"),
-
+
// * Commands *
-
- MUST_PLAYER("msg.command.playerNeeded"),
- INCORRECT_SYNTAX("msg.command.incorrectSyntax"),
- PERMISSION_REQUIRED("msg.command.noPermission"),
- COMMAND_DOESNT_EXIST("msg.command.invalidCommand.quests"),
+
COMMAND_DOESNT_EXIST_NOSLASH("msg.command.invalidCommand.simple"),
- MUST_HOLD_ITEM("msg.command.needItem"),
ITEM_CHANGED("msg.command.itemChanged"),
ITEM_REMOVED("msg.command.itemRemoved"),
SUCCESFULLY_REMOVED("msg.command.removed"),
@@ -145,7 +134,7 @@ public enum Lang implements Locale {
POOL_RESET_FULL("msg.command.resetPlayerPool.full"), // 0: pool ID, 1: player
POOL_START_ERROR("msg.command.startPlayerPool.error", ErrorPrefix), // 0: pool ID, 1: player
POOL_START_SUCCESS("msg.command.startPlayerPool.success", SuccessPrefix), // 0: pool ID, 1: player, 2: result
-
+
COMMAND_SCOREBOARD_LINESET("msg.command.scoreboard.lineSet"), // 0: line id
COMMAND_SCOREBOARD_LINERESET("msg.command.scoreboard.lineReset"), // 0: line id
COMMAND_SCOREBOARD_LINEREMOVE("msg.command.scoreboard.lineRemoved"), // 0: line id
@@ -170,27 +159,27 @@ public enum Lang implements Locale {
COMMAND_TRANSLATION_NOT_FOUND("msg.command.downloadTranslations.notFound"), // 0: lang, 1: version
COMMAND_TRANSLATION_EXISTS("msg.command.downloadTranslations.exists"), // 0: file
COMMAND_TRANSLATION_DOWNLOADED("msg.command.downloadTranslations.downloaded"), // 0: lang
-
- COMMAND_HELP("msg.command.help.header"),
- COMMAND_HELP_CREATE("msg.command.help.create"),
- COMMAND_HELP_EDIT("msg.command.help.edit"),
- COMMAND_HELP_REMOVE("msg.command.help.remove"),
- COMMAND_HELP_FINISH("msg.command.help.finishAll"),
- COMMAND_HELP_STAGE("msg.command.help.setStage"),
- COMMAND_HELP_DIALOG("msg.command.help.startDialog"),
- COMMAND_HELP_RESET("msg.command.help.resetPlayer"),
- COMMAND_HELP_RESETQUEST("msg.command.help.resetPlayerQuest"),
- COMMAND_HELP_SEE("msg.command.help.seePlayer"),
- COMMAND_HELP_RELOAD("msg.command.help.reload"),
- COMMAND_HELP_START("msg.command.help.start"),
- COMMAND_HELP_SETITEM("msg.command.help.setItem"),
- COMMAND_HELP_SETFIREWORK("msg.command.help.setFirework"),
- COMMAND_HELP_ADMINMODE("msg.command.help.adminMode"),
- COMMAND_HELP_VERSION("msg.command.help.version"),
- COMMAND_HELP_DOWNLOAD_TRANSLATIONS("msg.command.help.downloadTranslations"),
- COMMAND_HELP_SAVE("msg.command.help.save"),
- COMMAND_HELP_LIST("msg.command.help.list"),
-
+
+ COMMAND_HELP("msg.command.help.header", MessageType.DefaultMessageType.UNPREFIXED),
+ COMMAND_HELP_CREATE("msg.command.help.create", MessageType.DefaultMessageType.UNPREFIXED),
+ COMMAND_HELP_EDIT("msg.command.help.edit", MessageType.DefaultMessageType.UNPREFIXED),
+ COMMAND_HELP_REMOVE("msg.command.help.remove", MessageType.DefaultMessageType.UNPREFIXED),
+ COMMAND_HELP_FINISH("msg.command.help.finishAll", MessageType.DefaultMessageType.UNPREFIXED),
+ COMMAND_HELP_STAGE("msg.command.help.setStage", MessageType.DefaultMessageType.UNPREFIXED),
+ COMMAND_HELP_DIALOG("msg.command.help.startDialog", MessageType.DefaultMessageType.UNPREFIXED),
+ COMMAND_HELP_RESET("msg.command.help.resetPlayer", MessageType.DefaultMessageType.UNPREFIXED),
+ COMMAND_HELP_RESETQUEST("msg.command.help.resetPlayerQuest", MessageType.DefaultMessageType.UNPREFIXED),
+ COMMAND_HELP_SEE("msg.command.help.seePlayer", MessageType.DefaultMessageType.UNPREFIXED),
+ COMMAND_HELP_RELOAD("msg.command.help.reload", MessageType.DefaultMessageType.UNPREFIXED),
+ COMMAND_HELP_START("msg.command.help.start", MessageType.DefaultMessageType.UNPREFIXED),
+ COMMAND_HELP_SETITEM("msg.command.help.setItem", MessageType.DefaultMessageType.UNPREFIXED),
+ COMMAND_HELP_SETFIREWORK("msg.command.help.setFirework", MessageType.DefaultMessageType.UNPREFIXED),
+ COMMAND_HELP_ADMINMODE("msg.command.help.adminMode", MessageType.DefaultMessageType.UNPREFIXED),
+ COMMAND_HELP_VERSION("msg.command.help.version", MessageType.DefaultMessageType.UNPREFIXED),
+ COMMAND_HELP_DOWNLOAD_TRANSLATIONS("msg.command.help.downloadTranslations", MessageType.DefaultMessageType.UNPREFIXED),
+ COMMAND_HELP_SAVE("msg.command.help.save", MessageType.DefaultMessageType.UNPREFIXED),
+ COMMAND_HELP_LIST("msg.command.help.list", MessageType.DefaultMessageType.UNPREFIXED),
+
// * Editors *
ALREADY_EDITOR("msg.editor.already"),
ENTER_EDITOR_TITLE("msg.editor.enter.title"),
@@ -203,50 +192,50 @@ public enum Lang implements Locale {
CHOOSE_NPC_STARTER("msg.editor.npc.choseStarter"),
NPC_NOT_QUEST("msg.editor.npc.notStarter"),
-
+
CLICK_BLOCK("msg.editor.selectWantedBlock"),
BLOCKS_AMOUNT("msg.editor.blockAmount"),
BLOCK_NAME("msg.editor.blockName"),
BLOCK_DATA("msg.editor.blockData"), // 0: available block datas
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"),
LOCATION_WORLDPATTERN("msg.editor.stage.location.typeWorldPattern"),
-
+
GAME_TICKS("msg.editor.typeGameTicks"),
-
+
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
-
+
SCOREBOARD_OBJECTIVE_NOT_FOUND("msg.editor.scoreboardObjectiveNotFound"),
-
+
POOL_HOLOGRAM_TEXT("msg.editor.pool.hologramText", EditorPrefix),
POOL_MAXQUESTS("msg.editor.pool.maxQuests", EditorPrefix),
POOL_QUESTS_PER_LAUNCH("msg.editor.pool.questsPerLaunch", EditorPrefix),
POOL_TIME("msg.editor.pool.timeMsg", EditorPrefix),
-
+
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_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),
FIREWORK_EDITED("msg.editor.firework.edited", SuccessPrefix),
FIREWORK_REMOVED("msg.editor.firework.removed", SuccessPrefix),
-
+
// requirements
CHOOSE_XP_REQUIRED("msg.editor.text.chooseLvlRequired"),
CHOOSE_JOB_REQUIRED("msg.editor.text.chooseJobRequired"),
@@ -259,7 +248,10 @@ public enum Lang implements Locale {
CHOOSE_SCOREBOARD_OBJECTIVE("msg.editor.text.chooseObjectiveRequired"),
CHOOSE_SCOREBOARD_TARGET("msg.editor.text.chooseObjectiveTargetScore"),
CHOOSE_REGION_REQUIRED("msg.editor.text.chooseRegionRequired"),
-
+ CHOOSE_REQUIREMENT_CUSTOM_REASON("msg.editor.text.chooseRequirementCustomReason", EditorPrefix),
+ CHOOSE_REQUIREMENT_CUSTOM_DESCRIPTION("msg.editor.text.chooseRequirementCustomDescription", EditorPrefix),
+ CHOOSE_REWARD_CUSTOM_DESCRIPTION("msg.editor.text.chooseRewardCustomDescription", EditorPrefix),
+
// rewards
CHOOSE_PERM_REWARD("msg.editor.text.reward.permissionName"),
CHOOSE_PERM_WORLD("msg.editor.text.reward.permissionWorld"),
@@ -276,8 +268,8 @@ public enum Lang implements Locale {
INVALID_ITEM_TYPE("msg.editor.itemCreator.invalidItemType"),
UNKNOWN_BLOCK_TYPE("msg.editor.itemCreator.unknownBlockType"),
INVALID_BLOCK_TYPE("msg.editor.itemCreator.invalidBlockType"),
-
- DIALOG_SYNTAX("msg.editor.dialog.syntax"),
+
+ DIALOG_MESSAGE_SYNTAX("msg.editor.dialog.syntaxMessage"),
DIALOG_REMOVE_SYNTAX("msg.editor.dialog.syntaxRemove"),
DIALOG_MSG_ADDED_PLAYER("msg.editor.dialog.player"),
DIALOG_MSG_ADDED_NPC("msg.editor.dialog.npc"),
@@ -292,52 +284,52 @@ public enum Lang implements Locale {
DIALOG_SKIPPABLE_SET("msg.editor.dialog.skippable.set"), // 0: previous, 1: new
DIALOG_SKIPPABLE_UNSET("msg.editor.dialog.skippable.unset"), // 0: previous
DIALOG_CLEARED("msg.editor.dialog.cleared"),
- DIALOG_HELP_HEADER("msg.editor.dialog.help.header"),
- DIALOG_HELP_NPC("msg.editor.dialog.help.npc"),
- DIALOG_HELP_PLAYER("msg.editor.dialog.help.player"),
- DIALOG_HELP_NOTHING("msg.editor.dialog.help.nothing"),
- DIALOG_HELP_REMOVE("msg.editor.dialog.help.remove"),
- DIALOG_HELP_LIST("msg.editor.dialog.help.list"),
- DIALOG_HELP_NPCINSERT("msg.editor.dialog.help.npcInsert"),
- DIALOG_HELP_PLAYERINSERT("msg.editor.dialog.help.playerInsert"),
- DIALOG_HELP_NOTHINGINSERT("msg.editor.dialog.help.nothingInsert"),
- DIALOG_HELP_EDIT("msg.editor.dialog.help.edit"),
- DIALOG_HELP_ADDSOUND("msg.editor.dialog.help.addSound"),
- DIALOG_HELP_SETTIME("msg.editor.dialog.help.setTime"),
- DIALOG_HELP_NPCNAME("msg.editor.dialog.help.npcName"),
- DIALOG_HELP_SKIPPABLE("msg.editor.dialog.help.skippable"),
- DIALOG_HELP_CLEAR("msg.editor.dialog.help.clear"),
- DIALOG_HELP_CLOSE("msg.editor.dialog.help.close"),
-
+ DIALOG_HELP_HEADER("msg.editor.dialog.help.header", MessageType.DefaultMessageType.UNPREFIXED),
+ DIALOG_HELP_NPC("msg.editor.dialog.help.npc", MessageType.DefaultMessageType.UNPREFIXED),
+ DIALOG_HELP_PLAYER("msg.editor.dialog.help.player", MessageType.DefaultMessageType.UNPREFIXED),
+ DIALOG_HELP_NOTHING("msg.editor.dialog.help.nothing", MessageType.DefaultMessageType.UNPREFIXED),
+ DIALOG_HELP_REMOVE("msg.editor.dialog.help.remove", MessageType.DefaultMessageType.UNPREFIXED),
+ DIALOG_HELP_LIST("msg.editor.dialog.help.list", MessageType.DefaultMessageType.UNPREFIXED),
+ DIALOG_HELP_NPCINSERT("msg.editor.dialog.help.npcInsert", MessageType.DefaultMessageType.UNPREFIXED),
+ DIALOG_HELP_PLAYERINSERT("msg.editor.dialog.help.playerInsert", MessageType.DefaultMessageType.UNPREFIXED),
+ DIALOG_HELP_NOTHINGINSERT("msg.editor.dialog.help.nothingInsert", MessageType.DefaultMessageType.UNPREFIXED),
+ DIALOG_HELP_EDIT("msg.editor.dialog.help.edit", MessageType.DefaultMessageType.UNPREFIXED),
+ DIALOG_HELP_ADDSOUND("msg.editor.dialog.help.addSound", MessageType.DefaultMessageType.UNPREFIXED),
+ DIALOG_HELP_SETTIME("msg.editor.dialog.help.setTime", MessageType.DefaultMessageType.UNPREFIXED),
+ DIALOG_HELP_NPCNAME("msg.editor.dialog.help.npcName", MessageType.DefaultMessageType.UNPREFIXED),
+ DIALOG_HELP_SKIPPABLE("msg.editor.dialog.help.skippable", MessageType.DefaultMessageType.UNPREFIXED),
+ DIALOG_HELP_CLEAR("msg.editor.dialog.help.clear", MessageType.DefaultMessageType.UNPREFIXED),
+ DIALOG_HELP_CLOSE("msg.editor.dialog.help.close", MessageType.DefaultMessageType.UNPREFIXED),
+
MYTHICMOB_LIST("msg.editor.mythicmobs.list"),
MYTHICMOB_NOT_EXISTS("msg.editor.mythicmobs.isntMythicMob"),
MYTHICMOB_DISABLED("msg.editor.mythicmobs.disabled"),
ADVANCED_SPAWNERS_MOB("msg.editor.advancedSpawnersMob", EditorPrefix),
-
+
TEXTLIST_SYNTAX("msg.editor.textList.syntax"),
TEXTLIST_TEXT_ADDED("msg.editor.textList.added"),
TEXTLIST_TEXT_REMOVED("msg.editor.textList.removed"),
- TEXTLIST_TEXT_HELP_HEADER("msg.editor.textList.help.header"),
- TEXTLIST_TEXT_HELP_ADD("msg.editor.textList.help.add"),
- TEXTLIST_TEXT_HELP_REMOVE("msg.editor.textList.help.remove"),
- TEXTLIST_TEXT_HELP_LIST("msg.editor.textList.help.list"),
- TEXTLIST_TEXT_HELP_CLOSE("msg.editor.textList.help.close"),
-
+ TEXTLIST_TEXT_HELP_HEADER("msg.editor.textList.help.header", MessageType.DefaultMessageType.UNPREFIXED),
+ TEXTLIST_TEXT_HELP_ADD("msg.editor.textList.help.add", MessageType.DefaultMessageType.UNPREFIXED),
+ TEXTLIST_TEXT_HELP_REMOVE("msg.editor.textList.help.remove", MessageType.DefaultMessageType.UNPREFIXED),
+ TEXTLIST_TEXT_HELP_LIST("msg.editor.textList.help.list", MessageType.DefaultMessageType.UNPREFIXED),
+ TEXTLIST_TEXT_HELP_CLOSE("msg.editor.textList.help.close", MessageType.DefaultMessageType.UNPREFIXED),
+
// * Quests lists*
Finished("advancement.finished"),
Not_Started("advancement.notStarted"),
-
+
/* Inventories */
done("inv.validate"),
cancel("inv.cancel"),
search("inv.search"),
addObject("inv.addObject"),
-
+
INVENTORY_CONFIRM("inv.confirm.name"),
confirmYes("inv.confirm.yes"),
confirmNo("inv.confirm.no"),
-
+
stageCreate("inv.create.stageCreate"),
stageRemove("inv.create.stageRemove"),
stageUp("inv.create.stageUp"),
@@ -351,7 +343,8 @@ public enum Lang implements Locale {
stageMine("inv.create.mineBlocks"),
stagePlace("inv.create.placeBlocks"),
stageChat("inv.create.talkChat"),
- stageInteract("inv.create.interact"),
+ stageInteractBlock("inv.create.interact"),
+ stageInteractLocation("inv.create.interactLocation"),
stageFish("inv.create.fish"),
stageMelt("inv.create.melt"),
stageEnchant("inv.create.enchant"),
@@ -365,10 +358,8 @@ public enum Lang implements Locale {
stageDealDamage("inv.create.dealDamage"),
stageEatDrink("inv.create.eatDrink"),
stageText("inv.create.NPCText"),
- dialogLines("inv.create.dialogLines"), // 0: lines
stageNPCSelect("inv.create.NPCSelect"),
stageHide("inv.create.hideClues"),
- stageGPS("inv.create.gps"),
editMobs("inv.create.editMobsKill"),
mobsKillType("inv.create.mobsKillFromAFar"),
editBlocksMine("inv.create.editBlocksMine"),
@@ -395,7 +386,7 @@ public enum Lang implements Locale {
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
@@ -405,12 +396,17 @@ public enum Lang implements Locale {
stageDeathCauses("inv.create.stage.death.causes"),
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"),
-
+
stageEatDrinkItems("inv.create.stage.eatDrink.items"),
-
+
+ stagePlayTimeChangeTimeMode("inv.create.stage.playTime.changeTimeMode"),
+ stagePlayTimeModeOnline("inv.create.stage.playTime.modes.online"),
+ stagePlayTimeModeOffline("inv.create.stage.playTime.modes.offline"),
+ stagePlayTimeModeRealtime("inv.create.stage.playTime.modes.realtime"),
+
INVENTORY_STAGES("inv.stages.name"),
nextPage("inv.stages.nextPage"),
laterPage("inv.stages.laterPage"),
@@ -422,7 +418,7 @@ public enum Lang implements Locale {
descMessage("inv.stages.descriptionTextItem"),
previousBranch("inv.stages.previousBranch"),
newBranch("inv.stages.newBranch"),
-
+
INVENTORY_DETAILS("inv.details.name"),
multiple("inv.details.multipleTime.itemName"),
multipleLore("inv.details.multipleTime.itemLore"),
@@ -504,16 +500,16 @@ public enum Lang implements Locale {
optionValue("inv.details.optionValue"), // 0: value
defaultValue("inv.details.defaultValue"),
requiredParameter("inv.details.requiredParameter"),
-
+
INVENTORY_ITEMS("inv.itemsSelect.name"),
itemsNone("inv.itemsSelect.none"),
-
+
INVENTORY_ITEM("inv.itemSelect.name"),
-
+
INVENTORY_NPC("inv.npcCreate.name"),
name("inv.npcCreate.setName"),
skin("inv.npcCreate.setSkin"),
- type("inv.npcCreate.setType"),
+ npcType("inv.npcCreate.setType"),
move("inv.npcCreate.move.itemName"),
moveLore("inv.npcCreate.move.itemLore"),
moveItem("inv.npcCreate.moveItem"),
@@ -521,14 +517,15 @@ public enum Lang implements Locale {
INVENTORY_SELECT("inv.npcSelect.name"),
selectNPC("inv.npcSelect.selectStageNPC"),
createNPC("inv.npcSelect.createStageNPC"),
-
+
INVENTORY_TYPE("inv.entityType.name"),
INVENTORY_CHOOSE("inv.chooseQuest.name"),
questMenu("inv.chooseQuest.menu"),
questMenuLore("inv.chooseQuest.menuLore"),
-
+
INVENTORY_MOBS("inv.mobs.name"),
- click("inv.mobs.clickLore"),
+ editAmount("inv.mobs.editAmount"),
+ editMobName("inv.mobs.editMobName"),
setLevel("inv.mobs.setLevel"),
INVENTORY_MOBSELECT("inv.mobSelect.name"),
@@ -537,24 +534,25 @@ public enum Lang implements Locale {
epicBoss("inv.mobSelect.epicBoss"),
boss("inv.mobSelect.boss"),
advancedSpawners("inv.mobSelect.advancedSpawners"),
-
+
location("inv.stageEnding.locationTeleport"),
command("inv.stageEnding.command"),
-
+
INVENTORY_REQUIREMENTS("inv.requirements.name"),
-
+ setRequirementReason("inv.requirements.setReason"),
+ requirementReason("inv.requirements.reason"), // 0: message
+
INVENTORY_REWARDS("inv.rewards.name"),
commands("inv.rewards.commands"),
- teleportation("inv.rewards.teleportation"),
rewardRandomRewards("inv.rewards.random.rewards"),
rewardRandomMinMax("inv.rewards.random.minMax"),
-
+
INVENTORY_CHECKPOINT_ACTIONS("inv.checkpointActions.name"),
-
+
INVENTORY_CANCEL_ACTIONS("inv.cancelActions.name"),
INVENTORY_REWARDS_WITH_REQUIREMENTS("inv.rewardsWithRequirements.name"),
-
+
INVENTORY_QUESTS_LIST("inv.listAllQuests.name"),
INVENTORY_PLAYER_LIST("inv.listPlayerQuests.name"),
notStarteds("inv.listQuests.notStarted"),
@@ -569,26 +567,26 @@ public enum Lang implements Locale {
timesFinished("inv.listQuests.timesFinished"), // 0: times finished
formatNormal("inv.listQuests.format.normal"),
formatId("inv.listQuests.format.withId"),
-
+
INVENTORY_CREATOR("inv.itemCreator.name"),
itemType("inv.itemCreator.itemType"),
itemFlags("inv.itemCreator.itemFlags"),
itemName("inv.itemCreator.itemName"),
itemLore("inv.itemCreator.itemLore"),
itemQuest("inv.itemCreator.isQuestItem"),
-
+
INVENTORY_COMMAND("inv.command.name"),
commandValue("inv.command.value"),
commandConsole("inv.command.console"),
commandParse("inv.command.parse"),
commandDelay("inv.command.delay"),
-
+
INVENTORY_COMMANDS_LIST("inv.commandsList.name"),
commandsListValue("inv.commandsList.value"),
commandsListConsole("inv.commandsList.console"),
-
+
INVENTORY_CHOOSEACCOUNT("inv.chooseAccount.name"),
-
+
INVENTORY_BLOCK("inv.block.name"),
materialName("inv.block.material"),
materialNotItemLore("inv.block.materialNotItemLore"), // 0: block id
@@ -596,15 +594,15 @@ public enum Lang implements Locale {
blockData("inv.block.blockData"),
blockTag("inv.block.blockTag"),
blockTagLore("inv.block.blockTagLore"),
-
+
INVENTORY_BLOCKSLIST("inv.blocksList.name"),
-
+
INVENTORY_BLOCK_ACTION("inv.blockAction.name"),
clickLocation("inv.blockAction.location"),
clickMaterial("inv.blockAction.material"),
-
+
INVENTORY_BUCKETS("inv.buckets.name"),
-
+
INVENTORY_PERMISSION("inv.permission.name"),
perm("inv.permission.perm"),
world("inv.permission.world"),
@@ -615,10 +613,10 @@ public enum Lang implements Locale {
INVENTORY_PERMISSION_LIST("inv.permissionList.name"),
permRemoved("inv.permissionList.removed"),
permWorld("inv.permissionList.world"),
-
+
INVENTORY_CLASSES_REQUIRED("inv.classesRequired.name"),
INVENTORY_CLASSES_LIST("inv.classesList.name"),
-
+
INVENTORY_FACTIONS_REQUIRED("inv.factionsRequired.name"),
INVENTORY_FACTIONS_LIST("inv.factionsList.name"),
@@ -635,7 +633,7 @@ public enum Lang implements Locale {
poolEdit("inv.poolsManage.edit"),
poolChoose("inv.poolsManage.choose"),
poolCreate("inv.poolsManage.create"),
-
+
INVENTORY_POOL_CREATE("inv.poolCreation.name"),
poolEditHologramText("inv.poolCreation.hologramText"),
poolMaxQuests("inv.poolCreation.maxQuests"),
@@ -645,9 +643,9 @@ public enum Lang implements Locale {
poolAvoidDuplicates("inv.poolCreation.avoidDuplicates"),
poolAvoidDuplicatesLore("inv.poolCreation.avoidDuplicatesLore"),
poolRequirements("inv.poolCreation.requirements"),
-
+
INVENTORY_POOLS_LIST("inv.poolsList.name"),
-
+
INVENTORY_ITEM_COMPARISONS("inv.itemComparisons.name"),
comparisonBukkit("inv.itemComparisons.bukkit"),
comparisonBukkitLore("inv.itemComparisons.bukkitLore"),
@@ -665,34 +663,39 @@ public enum Lang implements Locale {
comparisonRepairCostLore("inv.itemComparisons.repairCostLore"),
comparisonItemsAdder("inv.itemComparisons.itemsAdder"),
comparisonItemsAdderLore("inv.itemComparisons.itemsAdderLore"),
-
+ comparisonMmoItems("inv.itemComparisons.mmoItems"),
+ comparisonMmoItemsLore("inv.itemComparisons.mmoItemsLore"),
+
INVENTORY_EDIT_TITLE("inv.editTitle.name"),
title_title("inv.editTitle.title"),
title_subtitle("inv.editTitle.subtitle"),
title_fadeIn("inv.editTitle.fadeIn"),
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"),
-
+
INVENTORY_DAMAGE_CAUSE("inv.damageCause.name"),
-
+
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"),
-
+
INVENTORY_EQUIPMENT_SLOTS("inv.equipmentSlots.name"),
-
+
+ object_description_set("inv.questObjects.setCustomDescription"),
+ object_description("inv.questObjects.description"), // 0: description
+
BOOK_NAME("inv.listBook.questName"),
BOOK_STARTER("inv.listBook.questStarter"),
BOOK_REWARDS("inv.listBook.questRewards"),
@@ -700,9 +703,9 @@ public enum Lang implements Locale {
BOOK_REQUIREMENTS("inv.listBook.requirements"),
BOOK_STAGES("inv.listBook.questStages"),
BOOK_NOQUEST("inv.listBook.noQuests"),
-
+
/* Scoreboard */
-
+
SCOREBOARD_NAME("scoreboard.name"),
SCOREBOARD_NONE("scoreboard.noLaunched"),
SCOREBOARD_NONE_NAME("scoreboard.noLaunchedName"),
@@ -716,7 +719,7 @@ public enum Lang implements Locale {
SCOREBOARD_MINE("scoreboard.stage.mine"),
SCOREBOARD_PLACE("scoreboard.stage.placeBlocks"),
SCOREBOARD_CHAT("scoreboard.stage.chat"),
- SCOREBOARD_INTERACT("scoreboard.stage.interact"),
+ SCOREBOARD_INTERACT_LOCATION("scoreboard.stage.interact"),
SCOREBOARD_INTERACT_MATERIAL("scoreboard.stage.interactMaterial"),
SCOREBOARD_FISH("scoreboard.stage.fish"),
SCOREBOARD_MELT("scoreboard.stage.melt"),
@@ -731,16 +734,17 @@ public enum Lang implements Locale {
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 */
-
+
INDICATION_START("indication.startQuest"), // 0: quest name
INDICATION_CLOSE("indication.closeInventory"),
INDICATION_CANCEL("indication.cancelQuest"), // 0: quest name
INDICATION_REMOVE("indication.removeQuest"), // 0: quest name
+ INDICATION_REMOVE_POOL("indication.removePool"),
/* Description */
-
+
RDTitle("description.requirement.title"),
RDLevel("description.requirement.level"), // 0: lvl
RDJobLevel("description.requirement.jobLevel"), // 0: lvl, 1: job
@@ -749,17 +753,17 @@ public enum Lang implements Locale {
RDClass("description.requirement.class"), // 0: classes
RDFaction("description.requirement.faction"), // 0: factions
RDQuest("description.requirement.quest"), // 0: quest
-
+
RWDTitle("description.reward.title"),
-
+
/* Misc */
-
+
TimeWeeks("misc.time.weeks"),
TimeDays("misc.time.days"),
TimeHours("misc.time.hours"),
TimeMinutes("misc.time.minutes"),
TimeLessMinute("misc.time.lessThanAMinute"),
-
+
Find("misc.stageType.region"),
Talk("misc.stageType.npc"),
Items("misc.stageType.items"),
@@ -767,20 +771,21 @@ public enum Lang implements Locale {
Mine("misc.stageType.mine"),
Place("misc.stageType.placeBlocks"),
Chat("misc.stageType.chat"),
- Interact("misc.stageType.interact"),
+ InteractBlock("misc.stageType.interact"),
+ InteractLocation("misc.stageType.interactLocation"),
Fish("misc.stageType.Fish"),
Melt("misc.stageType.Melt"),
Enchant("misc.stageType.Enchant"),
Craft("misc.stageType.Craft"),
Bucket("misc.stageType.Bucket"),
- Location("misc.stageType.location"),
+ StageLocation("misc.stageType.location"),
PlayTime("misc.stageType.playTime"),
Breed("misc.stageType.breedAnimals"),
Tame("misc.stageType.tameAnimals"),
Death("misc.stageType.die"),
DealDamage("misc.stageType.dealDamage"),
EatDrink("misc.stageType.eatDrink"),
-
+
ComparisonEquals("misc.comparison.equals"),
ComparisonDifferent("misc.comparison.different"),
ComparisonLess("misc.comparison.less"),
@@ -803,82 +808,96 @@ public enum Lang implements Locale {
RSkillLvl("misc.requirement.mcMMOSkillLevel"),
RMoney("misc.requirement.money"),
REquipment("misc.requirement.equipment"),
-
+
+ RWSkillApiXp("misc.reward.skillApiXp"),
+
BucketWater("misc.bucket.water"),
BucketLava("misc.bucket.lava"),
BucketMilk("misc.bucket.milk"),
BucketSnow("misc.bucket.snow"),
-
+
ClickRight("misc.click.right"),
ClickLeft("misc.click.left"),
ClickShiftRight("misc.click.shift-right"),
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
-
+ AmountXp("misc.amounts.xp"),
+
HologramText("misc.hologramText"),
PoolHologramText("misc.poolHologramText"),
- MobsProgression("misc.mobsProgression"),
EntityType("misc.entityType"),
EntityTypeAny("misc.entityTypeAny"),
QuestItemLore("misc.questItemLore"),
Ticks("misc.ticks"),
+ Location("misc.location"),
Enabled("misc.enabled"),
Disabled("misc.disabled"),
Unknown("misc.unknown"),
NotSet("misc.notSet"),
- Unused("misc.unused"),
- Used("misc.used"),
- RemoveMid("misc.remove"),
Remove("misc.removeRaw"),
Reset("misc.reset"),
Or("misc.or"),
Amount("misc.amount"),
- Item("misc.items"),
- Exp("misc.expPoints"),
Yes("misc.yes"),
No("misc.no"),
And("misc.and");
-
+
private static final String DEFAULT_STRING = "§cnot loaded";
-
- private final String path;
- private final Lang prefix;
-
- private String value = DEFAULT_STRING;
-
- private Lang(String path){
- this(path, null);
+
+ private final @NotNull String path;
+ private final @NotNull MessageType type;
+ private final @Nullable Lang prefix;
+
+ private @NotNull String value = DEFAULT_STRING;
+
+
+ private Lang(@NotNull String path) {
+ this(path, MessageType.DefaultMessageType.PREFIXED, null);
}
-
- private Lang(String path, Lang prefix) {
+
+ private Lang(@NotNull String path, @NotNull MessageType type) {
+ this(path, type, null);
+ }
+
+ private Lang(@NotNull String path, @Nullable Lang prefix) {
+ this(path, MessageType.DefaultMessageType.PREFIXED, prefix);
+ }
+
+ private Lang(@NotNull String path, @NotNull MessageType type, @Nullable Lang prefix) {
this.path = path;
+ this.type = type;
this.prefix = prefix;
}
-
+
@Override
- public String getPath(){
+ public @NotNull String getPath() {
return path;
}
-
+
@Override
- public void setValue(String value) {
- this.value = value;
+ public @NotNull MessageType getType() {
+ return type;
}
-
+
+ @Override
+ public void setValue(@NotNull String value) {
+ this.value = Objects.requireNonNull(value);
+ }
+
@Override
- public String getValue() {
+ public @NotNull String getValue() {
return prefix == null ? value : (prefix.toString() + value);
}
-
+
@Override
public String toString() {
return getValue();
}
-
+
}
diff --git a/api/src/main/java/fr/skytasul/quests/api/localization/Locale.java b/api/src/main/java/fr/skytasul/quests/api/localization/Locale.java
new file mode 100644
index 00000000..6643df08
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/localization/Locale.java
@@ -0,0 +1,131 @@
+package fr.skytasul.quests.api.localization;
+
+import java.io.File;
+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.Objects;
+import org.bukkit.ChatColor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.plugin.Plugin;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import fr.skytasul.quests.api.QuestsPlugin;
+import fr.skytasul.quests.api.utils.ChatColorUtils;
+import fr.skytasul.quests.api.utils.Utils;
+import fr.skytasul.quests.api.utils.messaging.HasPlaceholders;
+import fr.skytasul.quests.api.utils.messaging.MessageType;
+import fr.skytasul.quests.api.utils.messaging.MessageUtils;
+import fr.skytasul.quests.api.utils.messaging.PlaceholderRegistry;
+
+public interface Locale {
+
+ @NotNull
+ String getPath();
+
+ @NotNull
+ String getValue();
+
+ @NotNull
+ MessageType getType();
+
+ void setValue(@NotNull String value);
+
+ default @NotNull String format(@Nullable HasPlaceholders placeholdersHolder) {
+ return MessageUtils.format(getValue(),
+ placeholdersHolder == null ? null : placeholdersHolder.getPlaceholdersRegistry());
+ }
+
+ default @NotNull String format(@NotNull HasPlaceholders @NotNull... placeholdersHolders) {
+ return format(PlaceholderRegistry.combine(placeholdersHolders));
+ }
+
+ default @NotNull String quickFormat(@NotNull String key1, @Nullable Object value1) {
+ // for performance reason: no need to allocate a new placeholder registry with a new placeholder
+ String replacement = Objects.toString(value1);
+ return getValue()
+ .replace("{0}", replacement) // TODO migration 1.0
+ .replace("{" + key1 + "}", replacement);
+ }
+
+ default void send(@NotNull CommandSender sender) {
+ send(sender, (HasPlaceholders) null);
+ }
+
+ default void send(@NotNull CommandSender sender, @Nullable HasPlaceholders placeholdersHolder) {
+ MessageUtils.sendMessage(sender, getValue(), getType(),
+ placeholdersHolder == null ? null : placeholdersHolder.getPlaceholdersRegistry());
+ }
+
+ default void send(@NotNull CommandSender sender, @NotNull HasPlaceholders @NotNull... placeholdersHolders) {
+ send(sender, PlaceholderRegistry.combine(placeholdersHolders));
+ }
+
+ default void quickSend(@NotNull CommandSender sender, @NotNull String key1, @Nullable Object value1) {
+ send(sender, PlaceholderRegistry.of(key1, value1));
+ }
+
+ public static void loadStrings(@NotNull Locale @NotNull [] locales, @NotNull YamlConfiguration defaultConfig,
+ @NotNull 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)
+ QuestsPlugin.getPlugin().getLoggerExpanded().debug("Unavailable string in config for key " + l.getPath());
+ l.setValue(ChatColorUtils.translateHexColorCodes(ChatColor.translateAlternateColorCodes('&', value == null ? "§cunknown string" : value)));
+ }
+ }
+
+ public static YamlConfiguration loadLang(@NotNull Plugin plugin, @NotNull Locale @NotNull [] locales,
+ @NotNull String loadedLanguage) throws IOException, URISyntaxException {
+ long lastMillis = System.currentTimeMillis();
+
+ 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);
+ 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) QuestsPlugin.getPlugin().getLoggerExpanded().debug("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/mobs/LeveledMobFactory.java b/api/src/main/java/fr/skytasul/quests/api/mobs/LeveledMobFactory.java
similarity index 57%
rename from core/src/main/java/fr/skytasul/quests/api/mobs/LeveledMobFactory.java
rename to api/src/main/java/fr/skytasul/quests/api/mobs/LeveledMobFactory.java
index 01e356aa..563176c1 100644
--- a/core/src/main/java/fr/skytasul/quests/api/mobs/LeveledMobFactory.java
+++ b/api/src/main/java/fr/skytasul/quests/api/mobs/LeveledMobFactory.java
@@ -1,9 +1,10 @@
package fr.skytasul.quests.api.mobs;
import org.bukkit.entity.Entity;
+import org.jetbrains.annotations.NotNull;
public interface LeveledMobFactory extends MobFactory {
- double getMobLevel(T type, Entity entity);
+ double getMobLevel(@NotNull T type, @NotNull Entity entity);
}
diff --git a/core/src/main/java/fr/skytasul/quests/api/mobs/MobFactory.java b/api/src/main/java/fr/skytasul/quests/api/mobs/MobFactory.java
similarity index 56%
rename from core/src/main/java/fr/skytasul/quests/api/mobs/MobFactory.java
rename to api/src/main/java/fr/skytasul/quests/api/mobs/MobFactory.java
index a8f96782..f9d0b45f 100644
--- a/core/src/main/java/fr/skytasul/quests/api/mobs/MobFactory.java
+++ b/api/src/main/java/fr/skytasul/quests/api/mobs/MobFactory.java
@@ -1,10 +1,6 @@
package fr.skytasul.quests.api.mobs;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Objects;
-import java.util.OptionalInt;
+import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import org.apache.commons.lang.Validate;
@@ -16,77 +12,78 @@
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
-import fr.skytasul.quests.BeautyQuests;
import fr.skytasul.quests.api.QuestsAPI;
-import fr.skytasul.quests.utils.compatibility.mobs.CompatMobDeathEvent;
+import fr.skytasul.quests.api.QuestsPlugin;
+import fr.skytasul.quests.api.events.internal.BQMobDeathEvent;
+import fr.skytasul.quests.api.utils.AutoRegistered;
/**
- * This class implements {@link Listener} to permit the implementation to have at least one {@link EventHandler}.
- * This event method will be used to fire the {@link #callEvent(Event, Object, Entity, Player)}.
+ * This class should implement {@link Listener} to have at least one {@link EventHandler}. This
+ * event method will be used to fire the {@link #callEvent(Event, Object, Entity, Player)}.
*
* @param object which should represents a mob type from whatever plugin
*/
-public abstract interface MobFactory extends Listener {
+@AutoRegistered
+public abstract interface MobFactory {
/**
* @return internal ID of this Mob Factory
*/
- public abstract String getID();
+ public abstract @NotNull String getID();
/**
* @return item which will represent this Mob Factory in the Mobs Create GUI
*/
- public abstract ItemStack getFactoryItem();
+ public abstract @NotNull ItemStack getFactoryItem();
/**
* Called when a player click on the {@link #getFactoryItem()}
* @param p Player who clicked on the item
* @param run
*/
- public abstract void itemClick(Player p, Consumer run);
+ public abstract void itemClick(@NotNull Player p, @NotNull Consumer<@Nullable T> run);
/**
* @param value value returned from {@link #getValue(Object)}
* @return object created with the value
*/
- public abstract T fromValue(String value);
+ public abstract @NotNull T fromValue(@NotNull String value);
/**
* @param data object to get a String value from
* @return String value
*/
- public abstract String getValue(T data);
+ public abstract @NotNull String getValue(@NotNull T data);
/**
* @param data object to get a name from
* @return name of the object
*/
- public abstract String getName(T data);
+ public abstract @NotNull String getName(@NotNull T data);
/**
* @param data object to get the entity type from
* @return entity type of the object
*/
- public abstract EntityType getEntityType(T data);
+ public abstract @NotNull EntityType getEntityType(@NotNull T data);
/**
* @param data object to get a description from
* @return list of string which will be displayed as the lore of the mob item
*/
- public default List getDescriptiveLore(T data) {
+ public default @NotNull List<@Nullable String> getDescriptiveLore(@NotNull T data) {
return Collections.emptyList();
}
- public default boolean mobApplies(T first, Object other) {
+ public default boolean mobApplies(@Nullable T first, @Nullable Object other) {
return Objects.equals(first, other);
}
- public default boolean bukkitMobApplies(T first, Entity entity) { // TODO abstract (introduced in 0.20)
- BeautyQuests.logger.warning("The mob factory " + getID() + " has not been updated. Nag its author about it!");
- return false;
- }
+ public boolean bukkitMobApplies(@NotNull T first, @NotNull Entity entity);
/**
* Has to be called when a mob corresponding to this factory has been killed
@@ -96,29 +93,31 @@ public default boolean bukkitMobApplies(T first, Entity entity) { // TODO abstra
* @param entity bukkit entity killed
* @param player killer
*/
- public default void callEvent(Event originalEvent, T pluginMob, Entity entity, Player player) {
+ public default void callEvent(@Nullable Event originalEvent, @NotNull T pluginMob, @NotNull Entity entity,
+ @NotNull Player player) {
Validate.notNull(pluginMob, "Plugin mob object cannot be null");
Validate.notNull(player, "Player cannot be null");
if (originalEvent != null) {
- CompatMobDeathEvent existingCompat = eventsCache.getIfPresent(originalEvent);
+ BQMobDeathEvent existingCompat = eventsCache.getIfPresent(originalEvent);
if (existingCompat != null && mobApplies(pluginMob, existingCompat.getPluginMob())) {
- BeautyQuests.logger.warning("MobFactory.callEvent() called twice!");
+ QuestsPlugin.getPlugin().getLoggerExpanded().warning("MobFactory.callEvent() called twice!");
return;
}
}
- OptionalInt optionalStackSize = QuestsAPI.getMobStackers().stream()
+ OptionalInt optionalStackSize = QuestsAPI.getAPI().getMobStackers().stream()
.mapToInt(stacker -> stacker.getEntityStackSize(entity)).filter(size -> size > 1).max();
- CompatMobDeathEvent compatEvent = new CompatMobDeathEvent(pluginMob, player, entity, optionalStackSize.orElse(1));
+ BQMobDeathEvent compatEvent = new BQMobDeathEvent(pluginMob, player, entity, optionalStackSize.orElse(1));
if (originalEvent != null) eventsCache.put(originalEvent, compatEvent);
Bukkit.getPluginManager().callEvent(compatEvent);
}
- static final Cache eventsCache = CacheBuilder.newBuilder().expireAfterWrite(2, TimeUnit.SECONDS).build();
+ static final Cache eventsCache =
+ CacheBuilder.newBuilder().expireAfterWrite(2, TimeUnit.SECONDS).build();
public static final List> factories = new ArrayList<>();
- public static MobFactory> getMobFactory(String id) {
+ public static @Nullable MobFactory> getMobFactory(@NotNull String id) {
for (MobFactory> factory : factories) {
if (factory.getID().equals(id)) return factory;
}
diff --git a/core/src/main/java/fr/skytasul/quests/api/mobs/MobStacker.java b/api/src/main/java/fr/skytasul/quests/api/mobs/MobStacker.java
similarity index 53%
rename from core/src/main/java/fr/skytasul/quests/api/mobs/MobStacker.java
rename to api/src/main/java/fr/skytasul/quests/api/mobs/MobStacker.java
index f8f804a8..b5561877 100644
--- a/core/src/main/java/fr/skytasul/quests/api/mobs/MobStacker.java
+++ b/api/src/main/java/fr/skytasul/quests/api/mobs/MobStacker.java
@@ -1,9 +1,10 @@
package fr.skytasul.quests.api.mobs;
import org.bukkit.entity.Entity;
+import org.jetbrains.annotations.NotNull;
public interface MobStacker {
- int getEntityStackSize(Entity entity);
+ int getEntityStackSize(@NotNull Entity entity);
}
diff --git a/api/src/main/java/fr/skytasul/quests/api/npcs/BqInternalNpc.java b/api/src/main/java/fr/skytasul/quests/api/npcs/BqInternalNpc.java
new file mode 100644
index 00000000..425d7ce4
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/npcs/BqInternalNpc.java
@@ -0,0 +1,29 @@
+package fr.skytasul.quests.api.npcs;
+
+import org.bukkit.Location;
+import org.bukkit.entity.Entity;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public interface BqInternalNpc {
+
+ public String getInternalId();
+
+ public abstract @NotNull String getName();
+
+ public abstract boolean isSpawned();
+
+ public abstract @Nullable Entity getEntity();
+
+ public abstract @NotNull Location getLocation();
+
+ /**
+ * Sets the "paused" state of the NPC navigation
+ *
+ * @param paused should the navigation be paused
+ * @return true
if the navigation was paused before this call, false
+ * otherwise
+ */
+ public abstract boolean setNavigationPaused(boolean paused);
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/npcs/BqInternalNpcFactory.java b/api/src/main/java/fr/skytasul/quests/api/npcs/BqInternalNpcFactory.java
new file mode 100644
index 00000000..83fbfe7b
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/npcs/BqInternalNpcFactory.java
@@ -0,0 +1,49 @@
+package fr.skytasul.quests.api.npcs;
+
+import java.util.Collection;
+import org.bukkit.Location;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Cancellable;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import fr.skytasul.quests.api.QuestsPlugin;
+import fr.skytasul.quests.api.utils.AutoRegistered;
+
+@AutoRegistered
+public interface BqInternalNpcFactory {
+
+ int getTimeToWaitForNPCs();
+
+ boolean isNPC(@NotNull Entity entity);
+
+ @NotNull
+ Collection<@NotNull String> getIDs();
+
+ @Nullable
+ BqInternalNpc fetchNPC(String id);
+
+ default void npcClicked(@Nullable Cancellable event, String npcID, @NotNull Player p, @NotNull NpcClickType click) {
+ QuestsPlugin.getPlugin().getNpcManager().npcClicked(this, event, npcID, p, click);
+ }
+
+ default void npcRemoved(String id) {
+ QuestsPlugin.getPlugin().getNpcManager().npcRemoved(this, id);
+ }
+
+ default void npcsReloaded() {
+ QuestsPlugin.getPlugin().getNpcManager().reload(this);
+ }
+
+ public interface BqInternalNpcFactoryCreatable extends BqInternalNpcFactory {
+
+ boolean isValidEntityType(@NotNull EntityType type);
+
+ @NotNull
+ BqInternalNpc create(@NotNull Location location, @NotNull EntityType type, @NotNull String name,
+ @Nullable String skin);
+
+ }
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/npcs/BqNpc.java b/api/src/main/java/fr/skytasul/quests/api/npcs/BqNpc.java
new file mode 100644
index 00000000..f26d558d
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/npcs/BqNpc.java
@@ -0,0 +1,33 @@
+package fr.skytasul.quests.api.npcs;
+
+import java.util.Set;
+import java.util.function.Predicate;
+import org.bukkit.entity.Player;
+import fr.skytasul.quests.api.pools.QuestPool;
+import fr.skytasul.quests.api.quests.Quest;
+import fr.skytasul.quests.api.stages.types.Locatable;
+import fr.skytasul.quests.api.utils.messaging.HasPlaceholders;
+
+public interface BqNpc extends Locatable.Located.LocatedEntity, HasPlaceholders {
+
+ String getId();
+
+ BqInternalNpc getNpc();
+
+ Set getQuests();
+
+ boolean hasQuestStarted(Player p);
+
+ Set getPools();
+
+ void hideForPlayer(Player p, Object holder);
+
+ void removeHiddenForPlayer(Player p, Object holder);
+
+ boolean canGiveSomething(Player p);
+
+ void addStartablePredicate(Predicate predicate, Object holder);
+
+ void removeStartablePredicate(Object holder);
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/npcs/BqNpcManager.java b/api/src/main/java/fr/skytasul/quests/api/npcs/BqNpcManager.java
new file mode 100644
index 00000000..7e8fc5b1
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/npcs/BqNpcManager.java
@@ -0,0 +1,44 @@
+package fr.skytasul.quests.api.npcs;
+
+import java.util.Collection;
+import org.bukkit.Location;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Cancellable;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import fr.skytasul.quests.api.npcs.BqInternalNpcFactory.BqInternalNpcFactoryCreatable;
+
+public interface BqNpcManager {
+
+ boolean isEnabled();
+
+ /**
+ * Adds a new npc factory to the npc manager.
+ *
+ * @param key unique key of the npc factory
+ * @param internalFactory factory
+ */
+ void addInternalFactory(@NotNull String key, @NotNull BqInternalNpcFactory internalFactory);
+
+ @NotNull
+ Collection getAvailableIds();
+
+ boolean isNPC(@NotNull Entity entity);
+
+ @NotNull
+ BqNpc createNPC(@NotNull BqInternalNpcFactoryCreatable internalFactory, @NotNull Location location,
+ @NotNull EntityType type, @NotNull String name, @Nullable String skin);
+
+ @Nullable
+ BqNpc getById(String id);
+
+ void npcRemoved(@NotNull BqInternalNpcFactory internalFactory, String internalId);
+
+ void npcClicked(@NotNull BqInternalNpcFactory internalFactory, @Nullable Cancellable event, String internalId,
+ @NotNull Player p, @NotNull NpcClickType click);
+
+ void reload(@NotNull BqInternalNpcFactory internalFactory);
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/npcs/NpcClickType.java b/api/src/main/java/fr/skytasul/quests/api/npcs/NpcClickType.java
new file mode 100644
index 00000000..0dc6df25
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/npcs/NpcClickType.java
@@ -0,0 +1,13 @@
+package fr.skytasul.quests.api.npcs;
+
+public enum NpcClickType {
+ RIGHT, SHIFT_RIGHT, LEFT, SHIFT_LEFT;
+
+ public static NpcClickType of(boolean left, boolean shift) {
+ if (left) {
+ return shift ? SHIFT_LEFT : LEFT;
+ }else {
+ return shift ? SHIFT_RIGHT : RIGHT;
+ }
+ }
+}
\ No newline at end of file
diff --git a/core/src/main/java/fr/skytasul/quests/utils/types/Dialog.java b/api/src/main/java/fr/skytasul/quests/api/npcs/dialogs/Dialog.java
similarity index 61%
rename from core/src/main/java/fr/skytasul/quests/utils/types/Dialog.java
rename to api/src/main/java/fr/skytasul/quests/api/npcs/dialogs/Dialog.java
index d4b5d1ec..b0584534 100644
--- a/core/src/main/java/fr/skytasul/quests/utils/types/Dialog.java
+++ b/api/src/main/java/fr/skytasul/quests/api/npcs/dialogs/Dialog.java
@@ -1,39 +1,56 @@
-package fr.skytasul.quests.utils.types;
+package fr.skytasul.quests.api.npcs.dialogs;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-
import org.bukkit.configuration.ConfigurationSection;
-
-import fr.skytasul.quests.QuestsConfiguration;
-import fr.skytasul.quests.api.npcs.BQNPC;
-import fr.skytasul.quests.utils.Lang;
-import fr.skytasul.quests.utils.types.Message.Sender;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import fr.skytasul.quests.api.QuestsConfiguration;
+import fr.skytasul.quests.api.localization.Lang;
+import fr.skytasul.quests.api.npcs.BqNpc;
+import fr.skytasul.quests.api.npcs.dialogs.Message.Sender;
+import fr.skytasul.quests.api.utils.NumberedList;
public class Dialog implements Cloneable {
- public List messages;
- public String npcName = null;
- public Boolean skippable = null;
+ private @NotNull List messages;
+ private @Nullable String npcName = null;
+ private @Nullable Boolean skippable = null;
public Dialog() {
this(new ArrayList<>());
}
- public Dialog(List messages) {
+ public Dialog(@NotNull List messages) {
this.messages = messages;
}
- public String getNPCName(BQNPC defaultNPC) {
+ public @NotNull List getMessages() {
+ return messages;
+ }
+
+ public void setMessages(@NotNull List messages) {
+ this.messages = messages;
+ }
+
+ public @NotNull String getNPCName(@Nullable BqNpc defaultNPC) {
if (npcName != null)
return npcName;
if (defaultNPC == null)
return Lang.Unknown.toString();
- return defaultNPC.getName();
+ return defaultNPC.getNpc().getName();
}
+ public @Nullable String getNpcName() {
+ return npcName;
+ }
+
+ public void setNpcName(@Nullable String npcName) {
+ this.npcName = npcName;
+ }
+
public void add(String msg, Sender sender){
messages.add(new Message(msg, sender));
}
@@ -42,14 +59,15 @@ public void insert(String msg, Sender sender, int id){
messages.add(id, new Message(msg, sender));
}
- public void setNPCName(String npcName) {
- this.npcName = npcName;
- }
-
public boolean isSkippable() {
- return skippable == null ? QuestsConfiguration.getDialogsConfig().isSkippableByDefault() : skippable.booleanValue();
+ return skippable == null ? QuestsConfiguration.getConfig().getDialogsConfig().isSkippableByDefault()
+ : skippable.booleanValue();
}
+ public void setSkippable(@Nullable Boolean skippable) {
+ this.skippable = skippable;
+ }
+
public String getSkippableStatus() {
String msg = isSkippable() ? Lang.Enabled.toString() : Lang.Disabled.toString();
if (skippable == null) msg += " " + Lang.defaultValue.toString();
diff --git a/api/src/main/java/fr/skytasul/quests/api/npcs/dialogs/DialogRunner.java b/api/src/main/java/fr/skytasul/quests/api/npcs/dialogs/DialogRunner.java
new file mode 100644
index 00000000..dd062f94
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/npcs/dialogs/DialogRunner.java
@@ -0,0 +1,69 @@
+package fr.skytasul.quests.api.npcs.dialogs;
+
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+
+public interface DialogRunner {
+
+ void addTest(Predicate test);
+
+ void addTestCancelling(Predicate test);
+
+ void addEndAction(Consumer action);
+
+ /**
+ * Tests if the player is close enough to the NPC for the dialog to continue.
+ *
+ * This is not tested on player click, only for automatic messages with durations.
+ * @param p Player to check the distance from the NPC
+ * @return true
if the player is close enough to the NPC or if the distance feature is disabled, false
otherwise.
+ */
+ boolean canContinue(Player p);
+
+ /**
+ * Must be called when the player clicks on the NPC.
+ * This will send the dialog to the player if conditions are met.
+ * @param p player which
+ * @return the result of tests to run this dialog
+ */
+ TestResult onClick(Player p);
+
+ TestResult handleNext(Player p, DialogNextReason reason);
+
+ boolean isPlayerInDialog(Player p);
+
+ int getPlayerMessage(Player p);
+
+ boolean removePlayer(Player player);
+
+ /**
+ * Forces the end of the dialog, toggles the end actions and removes the player from this dialog
+ * runner.
+ *
+ * @param player player to end the dialog for
+ */
+ void forceFinish(@NotNull Player player);
+
+ public enum DialogNextReason {
+ NPC_CLICK, AUTO_TIME, COMMAND, PLUGIN;
+ }
+
+ public enum TestResult {
+ ALLOW, DENY, DENY_CANCEL;
+
+ public TestResult accumulate(TestResult other) {
+ if (this == DENY_CANCEL || other == DENY_CANCEL)
+ return DENY_CANCEL;
+ if (this == DENY || other == DENY)
+ return DENY;
+ return ALLOW;
+ }
+
+ public boolean shouldCancel() {
+ return this == DENY_CANCEL || this == ALLOW;
+ }
+ }
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/npcs/dialogs/Message.java b/api/src/main/java/fr/skytasul/quests/api/npcs/dialogs/Message.java
new file mode 100644
index 00000000..5116a3dd
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/npcs/dialogs/Message.java
@@ -0,0 +1,162 @@
+package fr.skytasul.quests.api.npcs.dialogs;
+
+import java.util.HashMap;
+import java.util.Map;
+import org.apache.commons.lang.StringUtils;
+import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitRunnable;
+import org.bukkit.scheduler.BukkitTask;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import fr.skytasul.quests.api.QuestsConfiguration;
+import fr.skytasul.quests.api.QuestsPlugin;
+import fr.skytasul.quests.api.localization.Lang;
+import fr.skytasul.quests.api.npcs.BqNpc;
+import fr.skytasul.quests.api.utils.messaging.MessageUtils;
+import fr.skytasul.quests.api.utils.messaging.PlaceholderRegistry;
+import fr.skytasul.quests.api.utils.messaging.PlaceholdersContext;
+import net.md_5.bungee.api.ChatMessageType;
+import net.md_5.bungee.api.chat.BaseComponent;
+import net.md_5.bungee.api.chat.TextComponent;
+
+public class Message implements Cloneable {
+ public String text;
+ public Sender sender;
+ public String sound;
+ public int wait = -1;
+
+ public Message(String msg, Sender sender) {
+ this.text = msg;
+ this.sender = sender;
+ }
+
+ public int getWaitTime() {
+ return wait == -1 ? QuestsConfiguration.getConfig().getDialogsConfig().getDefaultTime() : wait;
+ }
+
+ public BukkitTask sendMessage(@NotNull Player p, @Nullable BqNpc npc, @Nullable String npcCustomName, int id, int size) {
+ BukkitTask task = null;
+
+ String sent = formatMessage(p, npc, npcCustomName, id, size);
+ if (QuestsConfiguration.getConfig().getDialogsConfig().sendInActionBar()) {
+ BaseComponent[] components = TextComponent.fromLegacyText(sent.replace("{nl}", " "));
+ p.spigot().sendMessage(ChatMessageType.ACTION_BAR, components);
+ if (getWaitTime() > 60) {
+ task = new BukkitRunnable() {
+ int time = 40;
+
+ @Override
+ public void run() {
+ if (!p.isOnline()) {
+ cancel();
+ return;
+ }
+
+ time += 40;
+ if (time > getWaitTime())
+ cancel();
+ p.spigot().sendMessage(ChatMessageType.ACTION_BAR, components);
+ }
+ }.runTaskTimerAsynchronously(QuestsPlugin.getPlugin(), 40, 40);
+ }
+ } else
+ p.sendMessage(StringUtils.splitByWholeSeparator(sent, "{nl}"));
+
+ if (!"none".equals(sound)) {
+ 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.getConfig().getDialogsConfig().getDefaultPlayerSound();
+ } else if (sender == Sender.NPC) {
+ sentSound = QuestsConfiguration.getConfig().getDialogsConfig().getDefaultNPCSound();
+ }
+ }
+ return sentSound;
+ }
+
+ public String formatMessage(@NotNull Player p, @Nullable BqNpc npc, @Nullable String npcCustomName, int id, int size) {
+ PlaceholderRegistry registry = new PlaceholderRegistry()
+ .registerIndexed("player_name", p.getName())
+ .registerIndexed("npc_name_message", npcCustomName)
+ .registerIndexed("message_id", id + 1)
+ .registerIndexed("message_count", size);
+ if (npc != null)
+ registry.compose(npc);
+
+ String sent = MessageUtils.finalFormat(text, registry.withoutIndexes("npc_name_message", "player_name"),
+ PlaceholdersContext.of(p, true, null));
+ // ugly trick to have placeholders parsed in the message
+
+ registry.registerIndexed("text", sent);
+
+ switch (sender) {
+ case PLAYER:
+ sent = MessageUtils.finalFormat(Lang.SelfText.toString(), registry.withoutIndexes("npc_name_message"),
+ PlaceholdersContext.of(p, true, null));
+ break;
+ case NPC:
+ sent = MessageUtils.finalFormat(Lang.NpcText.toString(), registry.withoutIndexes("player_name"),
+ PlaceholdersContext.of(p, true, null));
+ break;
+ case NOSENDER:
+ // nothing to do: the placeholders has already been parsed
+ break;
+ }
+ return sent;
+ }
+
+ public void finished(Player p, boolean endOfDialog, boolean forced) {
+ if (endOfDialog || !forced)
+ return;
+ String sentSound = getSound();
+ if (sentSound != null)
+ p.stopSound(sentSound);
+ }
+
+ @Override
+ public Message clone() {
+ Message clone = new Message(text, sender);
+ clone.sound = sound;
+ clone.wait = wait;
+ return clone;
+ }
+
+ public Map serialize() {
+ Map map = new HashMap<>();
+ map.put("text", text);
+ map.put("sender", sender.name());
+ if (sound != null)
+ map.put("sound", sound);
+ if (wait != -1)
+ map.put("wait", wait);
+ return map;
+ }
+
+ public static Message deserialize(Map map) {
+ Message msg = new Message((String) map.get("text"), Sender.fromString((String) map.get("sender")));
+ if (map.containsKey("sound"))
+ msg.sound = (String) map.get("sound");
+ if (map.containsKey("wait"))
+ msg.wait = (int) map.get("wait");
+ return msg;
+ }
+
+ public enum Sender {
+ PLAYER, NPC, NOSENDER;
+
+ public static Sender fromString(String string) {
+ if (string.equalsIgnoreCase("NOTHING"))
+ return NOSENDER;
+ return valueOf(string.toUpperCase());
+ }
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java b/api/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java
new file mode 100644
index 00000000..fd020b12
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/objects/QuestObject.java
@@ -0,0 +1,170 @@
+package fr.skytasul.quests.api.objects;
+
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import fr.skytasul.quests.api.QuestsPlugin;
+import fr.skytasul.quests.api.editors.TextEditor;
+import fr.skytasul.quests.api.gui.ItemUtils;
+import fr.skytasul.quests.api.gui.LoreBuilder;
+import fr.skytasul.quests.api.localization.Lang;
+import fr.skytasul.quests.api.quests.Quest;
+import fr.skytasul.quests.api.serializable.SerializableObject;
+import fr.skytasul.quests.api.utils.messaging.HasPlaceholders;
+import fr.skytasul.quests.api.utils.messaging.MessageUtils;
+import fr.skytasul.quests.api.utils.messaging.PlaceholderRegistry;
+
+public abstract class QuestObject extends SerializableObject implements Cloneable, HasPlaceholders {
+
+ protected static final String CUSTOM_DESCRIPTION_KEY = "customDescription";
+
+ private Quest quest;
+ private String customDescription;
+
+ private @Nullable PlaceholderRegistry placeholders;
+
+ protected QuestObject(@NotNull QuestObjectsRegistry registry, @Nullable String customDescription) {
+ super(registry);
+ this.customDescription = customDescription;
+ }
+
+ @Override
+ public QuestObjectCreator getCreator() {
+ return (QuestObjectCreator) super.getCreator();
+ }
+
+ public void attach(@NotNull Quest quest) {
+ this.quest = quest;
+ }
+
+ public void detach() {
+ this.quest = null;
+ }
+
+ public @Nullable Quest getAttachedQuest() {
+ return quest;
+ }
+
+ public @Nullable String getCustomDescription() {
+ return customDescription;
+ }
+
+ public void setCustomDescription(@Nullable String customDescription) {
+ this.customDescription = customDescription;
+ }
+
+ public @NotNull String debugName() {
+ return getClass().getSimpleName() + (quest == null ? ", unknown quest" : (", quest " + quest.getId()));
+ }
+
+ public boolean isValid() {
+ return true;
+ }
+
+ protected @NotNull String getInvalidReason() {
+ return "invalid";
+ }
+
+ @Override
+ public final @NotNull PlaceholderRegistry getPlaceholdersRegistry() {
+ if (placeholders == null) {
+ placeholders = new PlaceholderRegistry();
+ createdPlaceholdersRegistry(placeholders);
+ }
+ return placeholders;
+ }
+
+ protected void createdPlaceholdersRegistry(@NotNull PlaceholderRegistry placeholders) {
+ placeholders.register("custom_description", () -> customDescription);
+ placeholders.register("object_type", creator.getID());
+ }
+
+ @Override
+ public abstract @NotNull QuestObject clone();
+
+ @Override
+ public void save(@NotNull ConfigurationSection section) {
+ if (customDescription != null)
+ section.set(CUSTOM_DESCRIPTION_KEY, customDescription);
+ }
+
+ @Override
+ public void load(@NotNull ConfigurationSection section) {
+ if (section.contains(CUSTOM_DESCRIPTION_KEY))
+ customDescription = section.getString(CUSTOM_DESCRIPTION_KEY);
+ }
+
+ public final @Nullable String getDescription(Player player) {
+ String string = customDescription == null ? getDefaultDescription(player) : customDescription;
+ if (string != null)
+ string = MessageUtils.format(string, getPlaceholdersRegistry());
+ return string;
+ }
+
+ /**
+ * Gets the description shown in the GUIs for this quest object.
+ *
+ * @param player player to get the description for
+ * @return the description of this object (nullable)
+ */
+ protected @Nullable String getDefaultDescription(@NotNull Player p) {
+ return null;
+ }
+
+ public @NotNull String @Nullable [] getItemLore() {
+ LoreBuilder lore = new LoreBuilder();
+ addLore(lore);
+ return lore.toLoreArray();
+ }
+
+ protected void addLore(@NotNull LoreBuilder loreBuilder) {
+ loreBuilder.addClick(getRemoveClick(), "§c" + Lang.Remove.toString());
+ loreBuilder.addClick(getCustomDescriptionClick(), Lang.object_description_set.toString());
+
+ String description;
+ try {
+ description = getDescription(null);
+ } catch (Exception ex) {
+ description = "§cerror";
+ QuestsPlugin.getPlugin().getLoggerExpanded().warning("Could not get quest object description during edition", ex);
+ }
+ if (description != null)
+ loreBuilder.addDescription(Lang.object_description.format(PlaceholderRegistry.of("description",
+ description + (customDescription == null ? " " + Lang.defaultValue : ""))));
+ }
+
+ public @NotNull ItemStack getItemStack() {
+ return ItemUtils.lore(getCreator().getItem().clone(), getItemLore());
+ }
+
+ public @Nullable ClickType getRemoveClick() {
+ return ClickType.SHIFT_LEFT;
+ }
+
+ protected @Nullable ClickType getCustomDescriptionClick() {
+ return ClickType.RIGHT;
+ }
+
+ protected abstract void sendCustomDescriptionHelpMessage(@NotNull Player p);
+
+ public final void click(@NotNull QuestObjectClickEvent event) {
+ if (event.getClick() == getRemoveClick())
+ return;
+
+ if (event.getClick() == getCustomDescriptionClick()) {
+ sendCustomDescriptionHelpMessage(event.getPlayer());
+ new TextEditor(event.getPlayer(), event::reopenGUI, msg -> {
+ setCustomDescription(msg);
+ event.reopenGUI();
+ }).passNullIntoEndConsumer().start();
+ } else {
+ clickInternal(event);
+ }
+ }
+
+ protected abstract void clickInternal(@NotNull QuestObjectClickEvent event);
+
+}
\ No newline at end of file
diff --git a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectClickEvent.java b/api/src/main/java/fr/skytasul/quests/api/objects/QuestObjectClickEvent.java
similarity index 55%
rename from core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectClickEvent.java
rename to api/src/main/java/fr/skytasul/quests/api/objects/QuestObjectClickEvent.java
index c33235cc..6fce3ee7 100644
--- a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectClickEvent.java
+++ b/api/src/main/java/fr/skytasul/quests/api/objects/QuestObjectClickEvent.java
@@ -3,20 +3,20 @@
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.ItemStack;
-
-import fr.skytasul.quests.gui.ItemUtils;
-import fr.skytasul.quests.gui.creation.QuestObjectGUI;
+import org.jetbrains.annotations.NotNull;
+import fr.skytasul.quests.api.gui.ItemUtils;
public class QuestObjectClickEvent {
- private final Player player;
- private final QuestObjectGUI gui;
- private final ItemStack item;
- private final ClickType click;
+ private final @NotNull Player player;
+ private final @NotNull QuestObjectGUI gui;
+ private final @NotNull ItemStack item;
+ private final @NotNull ClickType click;
+ private final @NotNull QuestObject clickedObject;
private final boolean creation;
- private final QuestObject clickedObject;
- public QuestObjectClickEvent(Player player, QuestObjectGUI gui, ItemStack item, ClickType click, boolean creation, QuestObject clickedObject) {
+ public QuestObjectClickEvent(@NotNull Player player, @NotNull QuestObjectGUI gui, @NotNull ItemStack item,
+ @NotNull ClickType click, boolean creation, @NotNull QuestObject clickedObject) {
this.player = player;
this.gui = gui;
this.item = item;
@@ -25,19 +25,19 @@ public QuestObjectClickEvent(Player player, QuestObjectGUI gui, ItemStack item,
this.clickedObject = clickedObject;
}
- public Player getPlayer() {
+ public @NotNull Player getPlayer() {
return player;
}
- public QuestObjectGUI getGUI() {
+ public @NotNull QuestObjectGUI getGUI() {
return gui;
}
- public ItemStack getItem() {
+ public @NotNull ItemStack getItem() {
return item;
}
- public ClickType getClick() {
+ public @NotNull ClickType getClick() {
return click;
}
@@ -66,7 +66,7 @@ public void updateItemLore(String... lore) {
}
public void updateItemLore() {
- ItemUtils.lore(item, clickedObject.getLore());
+ ItemUtils.lore(item, clickedObject.getItemLore());
}
}
diff --git a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectCreator.java b/api/src/main/java/fr/skytasul/quests/api/objects/QuestObjectCreator.java
similarity index 62%
rename from core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectCreator.java
rename to api/src/main/java/fr/skytasul/quests/api/objects/QuestObjectCreator.java
index ded73102..8ac223d0 100644
--- a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectCreator.java
+++ b/api/src/main/java/fr/skytasul/quests/api/objects/QuestObjectCreator.java
@@ -1,28 +1,27 @@
package fr.skytasul.quests.api.objects;
import java.util.function.Supplier;
-
import org.bukkit.inventory.ItemStack;
-
+import org.jetbrains.annotations.NotNull;
import fr.skytasul.quests.api.serializable.SerializableCreator;
-import fr.skytasul.quests.gui.creation.QuestObjectGUI;
-public class QuestObjectCreator extends SerializableCreator {
-
- private final ItemStack item;
+public abstract class QuestObjectCreator extends SerializableCreator {
+
+ private final @NotNull ItemStack item;
private final boolean multiple;
- private QuestObjectLocation[] allowedLocations;
-
+ private @NotNull QuestObjectLocation @NotNull [] allowedLocations;
+
/**
* @param id unique identifier for the object
* @param clazz Class extending {@link T}
* @param item ItemStack shown in {@link QuestObjectGUI}
* @param newObjectSupplier lambda returning an instance of this Object ({@link T}::new)
*/
- public QuestObjectCreator(String id, Class extends T> clazz, ItemStack item, Supplier newObjectSupplier) {
+ public QuestObjectCreator(@NotNull String id, @NotNull Class extends T> clazz, @NotNull ItemStack item,
+ @NotNull Supplier<@NotNull T> newObjectSupplier) {
this(id, clazz, item, newObjectSupplier, true);
}
-
+
/**
* @param id unique identifier for the object
* @param clazz Class extending {@link T}
@@ -32,27 +31,29 @@ public QuestObjectCreator(String id, Class extends T> clazz, ItemStack item, S
* @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 extends T> clazz, ItemStack item, Supplier newObjectSupplier, boolean multiple, QuestObjectLocation... allowedLocations) {
+ public QuestObjectCreator(@NotNull String id, @NotNull Class extends T> clazz, @NotNull ItemStack item,
+ @NotNull Supplier<@NotNull T> newObjectSupplier, boolean multiple,
+ @NotNull QuestObjectLocation @NotNull... allowedLocations) {
super(id, clazz, newObjectSupplier);
this.item = item;
this.multiple = multiple;
this.allowedLocations = allowedLocations;
}
-
- public ItemStack getItem() {
+
+ public @NotNull ItemStack getItem() {
return item;
}
-
+
public boolean canBeMultiple() {
return multiple;
}
-
- public boolean isAllowed(QuestObjectLocation location) {
+
+ public boolean isAllowed(@NotNull QuestObjectLocation location) {
if (allowedLocations.length == 0) return true;
for (QuestObjectLocation allowed : allowedLocations) {
if (allowed == location) return true;
}
return false;
}
-
+
}
diff --git a/api/src/main/java/fr/skytasul/quests/api/objects/QuestObjectGUI.java b/api/src/main/java/fr/skytasul/quests/api/objects/QuestObjectGUI.java
new file mode 100644
index 00000000..468a2dc2
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/objects/QuestObjectGUI.java
@@ -0,0 +1,87 @@
+package fr.skytasul.quests.api.objects;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.bukkit.DyeColor;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.NotNull;
+import fr.skytasul.quests.api.gui.close.CloseBehavior;
+import fr.skytasul.quests.api.gui.close.DelayCloseBehavior;
+import fr.skytasul.quests.api.gui.templates.ListGUI;
+import fr.skytasul.quests.api.gui.templates.PagedGUI;
+
+public class QuestObjectGUI extends ListGUI {
+
+ private String name;
+ private Collection> creators;
+ private Consumer> end;
+
+ public QuestObjectGUI(@NotNull String name, @NotNull QuestObjectLocation objectLocation,
+ @NotNull Collection<@NotNull QuestObjectCreator> creators, @NotNull Consumer<@NotNull List> end,
+ @NotNull 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))
+ .filter(creator -> creator.canBeMultiple()
+ || objects.stream().noneMatch(object -> object.getCreator() == creator))
+ .collect(Collectors.toList());
+ this.end = end;
+ }
+
+ @Override
+ public ItemStack getObjectItemStack(QuestObject object) {
+ return object.getItemStack();
+ }
+
+ @Override
+ protected ClickType getRemoveClick(@NotNull T object) {
+ return object.getRemoveClick();
+ }
+
+ @Override
+ protected void removed(T object) {
+ if (!object.getCreator().canBeMultiple()) creators.add(object.getCreator());
+ }
+
+ @Override
+ public void createObject(Function callback) {
+ new PagedGUI>(name, DyeColor.CYAN, creators) {
+
+ @Override
+ public ItemStack getItemStack(QuestObjectCreator object) {
+ return object.getItem();
+ }
+
+ @Override
+ public void click(QuestObjectCreator existing, ItemStack item, ClickType clickType) {
+ T object = existing.newObject();
+ if (!existing.canBeMultiple()) creators.remove(existing);
+ object.click(
+ new QuestObjectClickEvent(player, QuestObjectGUI.this, callback.apply(object), clickType, true, object));
+ }
+
+ @Override
+ public CloseBehavior onClose(Player p) {
+ return new DelayCloseBehavior(QuestObjectGUI.super::reopen);
+ }
+
+ }.open(player);
+ }
+
+ @Override
+ public void clickObject(QuestObject existing, ItemStack item, ClickType clickType) {
+ existing.click(new QuestObjectClickEvent(player, this, item, clickType, false, existing));
+ }
+
+ @Override
+ public void finish(List objects) {
+ end.accept(objects);
+ }
+
+}
\ No newline at end of file
diff --git a/core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectLocation.java b/api/src/main/java/fr/skytasul/quests/api/objects/QuestObjectLocation.java
similarity index 100%
rename from core/src/main/java/fr/skytasul/quests/api/objects/QuestObjectLocation.java
rename to api/src/main/java/fr/skytasul/quests/api/objects/QuestObjectLocation.java
diff --git a/api/src/main/java/fr/skytasul/quests/api/objects/QuestObjectsRegistry.java b/api/src/main/java/fr/skytasul/quests/api/objects/QuestObjectsRegistry.java
new file mode 100644
index 00000000..4fd5bb5c
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/objects/QuestObjectsRegistry.java
@@ -0,0 +1,45 @@
+package fr.skytasul.quests.api.objects;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import fr.skytasul.quests.api.serializable.SerializableRegistry;
+
+public class QuestObjectsRegistry> extends SerializableRegistry {
+
+ private final @NotNull String inventoryName;
+
+ public QuestObjectsRegistry(@NotNull String id, @NotNull String inventoryName) {
+ super(id);
+ this.inventoryName = inventoryName;
+ }
+
+ public @NotNull String getInventoryName() {
+ return inventoryName;
+ }
+
+ public QuestObjectGUI createGUI(@NotNull QuestObjectLocation location, @NotNull Consumer<@NotNull List> end,
+ @NotNull List objects) {
+ return createGUI(inventoryName, location, end, objects, null);
+ }
+
+ public QuestObjectGUI createGUI(@NotNull QuestObjectLocation location, @NotNull Consumer<@NotNull List> end,
+ @NotNull List objects, @Nullable Predicate filter) {
+ return createGUI(inventoryName, location, end, objects, filter);
+ }
+
+ public QuestObjectGUI createGUI(@NotNull String name, @NotNull QuestObjectLocation location,
+ @NotNull Consumer<@NotNull List> end, @NotNull List objects) {
+ return createGUI(name, location, end, objects, null);
+ }
+
+ public QuestObjectGUI createGUI(@NotNull String name, @NotNull QuestObjectLocation location,
+ @NotNull Consumer<@NotNull List> end, @NotNull List<@NotNull T> objects, @Nullable Predicate filter) {
+ return new QuestObjectGUI<>(name, location, (Collection>) (filter == null ? creators : creators.stream().filter(filter).collect(Collectors.toList())), end, objects);
+ }
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/options/OptionSet.java b/api/src/main/java/fr/skytasul/quests/api/options/OptionSet.java
new file mode 100644
index 00000000..033bdf6c
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/options/OptionSet.java
@@ -0,0 +1,21 @@
+package fr.skytasul.quests.api.options;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+@SuppressWarnings ("rawtypes")
+public interface OptionSet extends Iterable {
+
+ > @NotNull T getOption(@NotNull Class optionClass);
+
+ boolean hasOption(@NotNull Class extends QuestOption>> clazz);
+
+ default @Nullable D getOptionValueOrDef(@NotNull Class extends QuestOption> clazz) {
+ for (QuestOption> option : this) {
+ if (clazz.isInstance(option))
+ return (D) option.getValue();
+ }
+ return (D) QuestOptionCreator.creators.get(clazz).defaultValue;
+ }
+
+}
\ No newline at end of file
diff --git a/api/src/main/java/fr/skytasul/quests/api/options/QuestOption.java b/api/src/main/java/fr/skytasul/quests/api/options/QuestOption.java
new file mode 100644
index 00000000..09aa9970
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/options/QuestOption.java
@@ -0,0 +1,152 @@
+package fr.skytasul.quests.api.options;
+
+import java.util.Objects;
+import org.apache.commons.lang.Validate;
+import org.bukkit.Bukkit;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.Listener;
+import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import fr.skytasul.quests.api.QuestsPlugin;
+import fr.skytasul.quests.api.localization.Lang;
+import fr.skytasul.quests.api.options.description.QuestDescriptionProvider;
+import fr.skytasul.quests.api.quests.Quest;
+import fr.skytasul.quests.api.quests.creation.QuestCreationGuiClickEvent;
+import fr.skytasul.quests.api.utils.AutoRegistered;
+import fr.skytasul.quests.api.utils.messaging.PlaceholderRegistry;
+
+@AutoRegistered
+public abstract class QuestOption implements Cloneable {
+
+ private final @NotNull QuestOptionCreator> creator;
+
+ private @Nullable T value;
+ private @Nullable Quest attachedQuest;
+
+ private final @NotNull Class extends QuestOption>> @NotNull [] requiredQuestOptions;
+ private @Nullable Runnable valueUpdateListener;
+
+ protected QuestOption(@NotNull Class extends QuestOption>> @NotNull... requiredQuestOptions) {
+ this.requiredQuestOptions = requiredQuestOptions;
+
+ this.creator = (QuestOptionCreator>) QuestOptionCreator.creators.get(getClass());
+ if (creator == null) throw new IllegalArgumentException(getClass().getName() + " has not been registered as a quest option via the API.");
+
+ setValue(creator.defaultValue == null ? null : cloneValue(creator.defaultValue));
+ }
+
+ public @NotNull QuestOptionCreator> getOptionCreator() {
+ return creator;
+ }
+
+ public final boolean hasCustomValue() {
+ return !Objects.equals(this.value, creator.defaultValue);
+ }
+
+ public final @Nullable T getValue() {
+ return value;
+ }
+
+ public void setValue(@Nullable T value) {
+ this.value = value;
+ valueUpdated();
+ }
+
+ public void resetValue() {
+ setValue(creator.defaultValue);
+ }
+
+ protected void valueUpdated() {
+ if (valueUpdateListener != null) valueUpdateListener.run();
+ }
+
+ public void setValueUpdaterListener(@Nullable Runnable listener) {
+ this.valueUpdateListener = listener;
+ }
+
+ public @NotNull Class extends QuestOption>> @NotNull [] getRequiredQuestOptions() {
+ return requiredQuestOptions;
+ }
+
+ public @Nullable Quest getAttachedQuest() {
+ return attachedQuest;
+ }
+
+ public void attach(@NotNull 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, QuestsPlugin.getPlugin());
+ }
+
+ 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(this);
+ }
+ }
+
+ public abstract @Nullable Object save();
+
+ public abstract void load(@NotNull ConfigurationSection config, @NotNull String key);
+
+ public abstract @Nullable T cloneValue(@Nullable T value);
+
+ @Override
+ public @NotNull QuestOption clone() {
+ QuestOption clone = creator.optionSupplier.get();
+ clone.setValue(value == null ? null : cloneValue(value));
+ return clone;
+ }
+
+ public boolean shouldDisplay(@NotNull OptionSet options) {
+ return true;
+ }
+
+ public void onDependenciesUpdated(@NotNull OptionSet options) {}
+
+ public abstract @NotNull ItemStack getItemStack(@NotNull OptionSet options);
+
+ public abstract void click(@NotNull QuestCreationGuiClickEvent event);
+
+ public @NotNull String formatValue(@Nullable String valueString) {
+ return formatNullableValue(valueString, !hasCustomValue());
+ }
+
+ public static @Nullable String formatDescription(@Nullable String description) {
+ return description == null ? null : "§8> §7" + description;
+ }
+
+ public static @NotNull String formatNullableValue(@Nullable Object value) {
+ return formatNullableValue(value, false);
+ }
+
+ public static @NotNull String formatNullableValue(@Nullable Object value, @NotNull Object defaultValue) {
+ return formatNullableValue(value == null ? defaultValue : value, value == null);
+ }
+
+ public static @NotNull String formatNullableValue(@Nullable Object value, boolean isDefault) {
+ String valueString =
+ Lang.optionValue.format(PlaceholderRegistry.of("value", value == null ? Lang.NotSet.toString() : value));
+ if (isDefault)
+ valueString += " " + Lang.defaultValue.toString();
+ return valueString;
+ }
+
+}
diff --git a/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionBoolean.java b/api/src/main/java/fr/skytasul/quests/api/options/QuestOptionBoolean.java
similarity index 71%
rename from core/src/main/java/fr/skytasul/quests/api/options/QuestOptionBoolean.java
rename to api/src/main/java/fr/skytasul/quests/api/options/QuestOptionBoolean.java
index 0606aa7d..373bfee9 100644
--- a/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionBoolean.java
+++ b/api/src/main/java/fr/skytasul/quests/api/options/QuestOptionBoolean.java
@@ -1,12 +1,9 @@
package fr.skytasul.quests.api.options;
import org.bukkit.configuration.ConfigurationSection;
-import org.bukkit.entity.Player;
-import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.ItemStack;
-
-import fr.skytasul.quests.gui.ItemUtils;
-import fr.skytasul.quests.gui.creation.FinishGUI;
+import fr.skytasul.quests.api.gui.ItemUtils;
+import fr.skytasul.quests.api.quests.creation.QuestCreationGuiClickEvent;
public abstract class QuestOptionBoolean extends QuestOption {
@@ -41,9 +38,9 @@ public ItemStack getItemStack(OptionSet options) {
}
@Override
- public void click(FinishGUI gui, Player p, ItemStack item, int slot, ClickType click) {
+ public void click(QuestCreationGuiClickEvent event) {
setValue(!getValue());
- ItemUtils.set(item, getValue());
+ ItemUtils.setSwitch(event.getClicked(), getValue());
}
}
diff --git a/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionCreator.java b/api/src/main/java/fr/skytasul/quests/api/options/QuestOptionCreator.java
similarity index 70%
rename from core/src/main/java/fr/skytasul/quests/api/options/QuestOptionCreator.java
rename to api/src/main/java/fr/skytasul/quests/api/options/QuestOptionCreator.java
index 8f6b0944..155f7fca 100644
--- a/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionCreator.java
+++ b/api/src/main/java/fr/skytasul/quests/api/options/QuestOptionCreator.java
@@ -4,6 +4,8 @@
import java.util.HashMap;
import java.util.Map;
import java.util.function.Supplier;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
public class QuestOptionCreator> {
@@ -17,7 +19,8 @@ public class QuestOptionCreator> {
public int slot = -1;
- public QuestOptionCreator(String id, int preferedSlot, Class optionClass, Supplier optionSupplier, D defaultValue, String... oldNames) {
+ public QuestOptionCreator(@NotNull String id, int preferedSlot, @NotNull Class optionClass,
+ @NotNull Supplier<@NotNull T> optionSupplier, @Nullable D defaultValue, @NotNull String @NotNull... oldNames) {
this.id = id;
this.optionClass = optionClass;
this.optionSupplier = optionSupplier;
@@ -39,7 +42,11 @@ public static int calculateSlot(int preferedSlot) {
return prevSlot == preferedSlot ? prevSlot + 1 : preferedSlot;
}
- public boolean applies(String key) {
+ public static int getLastSlot() {
+ return creators.values().stream().mapToInt(creator -> creator.slot).max().getAsInt();
+ }
+
+ public boolean applies(@NotNull String key) {
return id.equals(key) || Arrays.stream(oldNames).anyMatch(key::equals);
}
diff --git a/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionItem.java b/api/src/main/java/fr/skytasul/quests/api/options/QuestOptionItem.java
similarity index 70%
rename from core/src/main/java/fr/skytasul/quests/api/options/QuestOptionItem.java
rename to api/src/main/java/fr/skytasul/quests/api/options/QuestOptionItem.java
index 66530cd1..1620fa1b 100644
--- a/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionItem.java
+++ b/api/src/main/java/fr/skytasul/quests/api/options/QuestOptionItem.java
@@ -3,18 +3,15 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
-
import org.bukkit.configuration.ConfigurationSection;
-import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.ItemStack;
-
-import fr.skytasul.quests.gui.ItemUtils;
-import fr.skytasul.quests.gui.creation.FinishGUI;
-import fr.skytasul.quests.gui.misc.ItemGUI;
-import fr.skytasul.quests.utils.Lang;
-import fr.skytasul.quests.utils.Utils;
-import fr.skytasul.quests.utils.XMaterial;
+import com.cryptomorin.xseries.XMaterial;
+import fr.skytasul.quests.api.QuestsPlugin;
+import fr.skytasul.quests.api.gui.ItemUtils;
+import fr.skytasul.quests.api.localization.Lang;
+import fr.skytasul.quests.api.quests.creation.QuestCreationGuiClickEvent;
+import fr.skytasul.quests.api.utils.Utils;
public abstract class QuestOptionItem extends QuestOption {
@@ -58,7 +55,7 @@ private List getLore() {
lore.add(Lang.defaultValue.toString());
}
lore.add("");
- lore.add(Lang.RemoveMid.toString());
+ lore.add("§8" + Lang.ClickShiftRight.toString() + " > §7" + Lang.Remove.toString());
}
return lore;
@@ -74,16 +71,16 @@ public ItemStack getItemStack(OptionSet options) {
}
@Override
- public void click(FinishGUI gui, Player p, ItemStack item, int slot, ClickType click) {
- if (click == ClickType.MIDDLE) {
+ public void click(QuestCreationGuiClickEvent event) {
+ if (event.getClick() == ClickType.SHIFT_RIGHT) {
setValue(null);
- gui.inv.setItem(slot, getItemStack(null));
+ event.getGui().updateOptionItem(this);
}else {
- new ItemGUI(is -> {
+ QuestsPlugin.getPlugin().getGuiManager().getFactory().createItemSelection(is -> {
setValue(is);
- gui.inv.setItem(slot, getItemStack(null));
- gui.reopen(p);
- }, () -> gui.reopen(p)).create(p);
+ event.getGui().updateOptionItem(this);
+ event.reopen();
+ }, event::reopen).open(event.getPlayer());
}
}
diff --git a/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionObject.java b/api/src/main/java/fr/skytasul/quests/api/options/QuestOptionObject.java
similarity index 66%
rename from core/src/main/java/fr/skytasul/quests/api/options/QuestOptionObject.java
rename to api/src/main/java/fr/skytasul/quests/api/options/QuestOptionObject.java
index b31de3f1..7656401a 100644
--- a/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionObject.java
+++ b/api/src/main/java/fr/skytasul/quests/api/options/QuestOptionObject.java
@@ -1,25 +1,23 @@
package fr.skytasul.quests.api.options;
-import java.util.ArrayList;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
-
import org.bukkit.configuration.ConfigurationSection;
-import org.bukkit.entity.Player;
-import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.ItemStack;
-
+import org.jetbrains.annotations.Nullable;
+import com.cryptomorin.xseries.XMaterial;
+import fr.skytasul.quests.api.gui.ItemUtils;
import fr.skytasul.quests.api.objects.QuestObject;
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.quests.Quest;
+import fr.skytasul.quests.api.quests.creation.QuestCreationGuiClickEvent;
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.XMaterial;
-public abstract class QuestOptionObject> extends QuestOption> {
+public abstract class QuestOptionObject, L extends List>
+ extends QuestOption {
@Override
public void attach(Quest quest) {
@@ -34,7 +32,7 @@ public void detach() {
}
@Override
- public void setValue(List value) {
+ public void setValue(L value) {
if (getValue() != null && getAttachedQuest() != null) detachObjects();
super.setValue(value);
if (getValue() != null && getAttachedQuest() != null) attachObjects();
@@ -59,22 +57,24 @@ public Object save() {
@Override
public void load(ConfigurationSection config, String key) {
- getValue().addAll(QuestObject.deserializeList(config.getMapList(key), this::deserialize));
- }
-
- @Override
- public List cloneValue(List value) {
- return new ArrayList<>(value);
+ getValue().addAll(SerializableObject.deserializeList(config.getMapList(key), this::deserialize));
}
protected abstract T deserialize(Map map);
- protected abstract String getSizeString(int size);
+ protected abstract String getSizeString();
protected abstract QuestObjectsRegistry getObjectsRegistry();
+ protected abstract L instanciate(Collection objects);
+
+ @Override
+ public @Nullable L cloneValue(@Nullable L value) {
+ return instanciate(value);
+ }
+
protected String[] getLore() {
- String count = "§7" + getSizeString(getValue().size());
+ String count = "§7" + getSizeString();
if (getItemDescription() == null) return new String[] { count };
return new String[] { formatDescription(getItemDescription()), "", count };
}
@@ -85,12 +85,12 @@ public ItemStack getItemStack(OptionSet options) {
}
@Override
- public void click(FinishGUI gui, Player p, ItemStack item, int slot, ClickType click) {
+ public void click(QuestCreationGuiClickEvent event) {
getObjectsRegistry().createGUI(QuestObjectLocation.QUEST, objects -> {
- setValue(objects);
- ItemUtils.lore(item, getLore());
- gui.reopen(p);
- }, getValue()).create(p);
+ setValue(instanciate(objects));
+ ItemUtils.lore(event.getClicked(), getLore());
+ event.reopen();
+ }, getValue()).open(event.getPlayer());
}
public abstract XMaterial getItemMaterial();
diff --git a/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionRewards.java b/api/src/main/java/fr/skytasul/quests/api/options/QuestOptionRewards.java
similarity index 57%
rename from core/src/main/java/fr/skytasul/quests/api/options/QuestOptionRewards.java
rename to api/src/main/java/fr/skytasul/quests/api/options/QuestOptionRewards.java
index 33c6db02..c57bb6ea 100644
--- a/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionRewards.java
+++ b/api/src/main/java/fr/skytasul/quests/api/options/QuestOptionRewards.java
@@ -1,22 +1,14 @@
package fr.skytasul.quests.api.options;
+import java.util.Collection;
import java.util.Map;
-
import fr.skytasul.quests.api.QuestsAPI;
import fr.skytasul.quests.api.objects.QuestObjectsRegistry;
import fr.skytasul.quests.api.rewards.AbstractReward;
import fr.skytasul.quests.api.rewards.RewardCreator;
-import fr.skytasul.quests.utils.Lang;
-
-public abstract class QuestOptionRewards extends QuestOptionObject {
+import fr.skytasul.quests.api.rewards.RewardList;
- @Override
- protected void attachObject(AbstractReward object) {
- super.attachObject(object);
- if (object.isAsync()) attachedAsyncReward(object);
- }
-
- protected abstract void attachedAsyncReward(AbstractReward reward);
+public abstract class QuestOptionRewards extends QuestOptionObject {
@Override
protected AbstractReward deserialize(Map map) {
@@ -24,13 +16,18 @@ protected AbstractReward deserialize(Map map) {
}
@Override
- protected String getSizeString(int size) {
- return Lang.rewards.format(size);
+ protected String getSizeString() {
+ return getValue().getSizeString();
}
@Override
protected QuestObjectsRegistry getObjectsRegistry() {
- return QuestsAPI.getRewards();
+ return QuestsAPI.getAPI().getRewards();
}
+ @Override
+ protected RewardList instanciate(Collection objects) {
+ return new RewardList(objects);
+ }
+
}
\ No newline at end of file
diff --git a/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionString.java b/api/src/main/java/fr/skytasul/quests/api/options/QuestOptionString.java
similarity index 59%
rename from core/src/main/java/fr/skytasul/quests/api/options/QuestOptionString.java
rename to api/src/main/java/fr/skytasul/quests/api/options/QuestOptionString.java
index 0a95ee59..5c3ec96f 100644
--- a/core/src/main/java/fr/skytasul/quests/api/options/QuestOptionString.java
+++ b/api/src/main/java/fr/skytasul/quests/api/options/QuestOptionString.java
@@ -4,86 +4,84 @@
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
-
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
-import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.ItemStack;
-
-import fr.skytasul.quests.editors.TextEditor;
-import fr.skytasul.quests.editors.TextListEditor;
-import fr.skytasul.quests.gui.ItemUtils;
-import fr.skytasul.quests.gui.creation.FinishGUI;
-import fr.skytasul.quests.utils.XMaterial;
+import com.cryptomorin.xseries.XMaterial;
+import fr.skytasul.quests.api.editors.TextEditor;
+import fr.skytasul.quests.api.editors.TextListEditor;
+import fr.skytasul.quests.api.gui.ItemUtils;
+import fr.skytasul.quests.api.quests.creation.QuestCreationGuiClickEvent;
public abstract class QuestOptionString extends QuestOption {
-
+
public QuestOptionString(Class extends QuestOption>>... requiredQuestOptions) {
super(requiredQuestOptions);
}
-
+
@Override
public Object save() {
return getValue();
}
-
+
@Override
public void load(ConfigurationSection config, String key) {
setValue(config.getString(key));
}
-
+
@Override
public String cloneValue(String value) {
return value;
}
-
+
private String[] getLore() {
if (getItemDescription() == null) return new String[] { formatValue(getValue()) };
-
+
String description = formatDescription(getItemDescription());
return new String[] { description, "", formatValue((isMultiline() && getValue() != null ? "{nl}" : "") + getValue()) };
}
-
+
@Override
public ItemStack getItemStack(OptionSet options) {
return ItemUtils.item(getItemMaterial(), getItemName(), getLore());
}
-
+
@Override
- public void click(FinishGUI gui, Player p, ItemStack item, int slot, ClickType click) {
- sendIndication(p);
+ public void click(QuestCreationGuiClickEvent event) {
+ sendIndication(event.getPlayer());
if (isMultiline()) {
- List splitText = getValue() == null ? new ArrayList<>() : new ArrayList<>(Arrays.asList(getValue().split("\\{nl\\}")));
- new TextListEditor(p, list -> {
+ List splitText = getValue() == null || getValue().isEmpty()
+ ? new ArrayList<>()
+ : new ArrayList<>(Arrays.asList(getValue().split("\\{nl\\}")));
+ new TextListEditor(event.getPlayer(), list -> {
setValue(list.stream().collect(Collectors.joining("{nl}")));
- ItemUtils.lore(item, getLore());
- gui.reopen(p);
- }, splitText).enter();
+ ItemUtils.lore(event.getClicked(), getLore());
+ event.reopen();
+ }, splitText).start();
}else {
- new TextEditor(p, () -> gui.reopen(p), obj -> {
- setValue(obj);
- ItemUtils.lore(item, getLore());
- gui.reopen(p);
- }, () -> {
- resetValue();
- ItemUtils.lore(item, getLore());
- gui.reopen(p);
- }).enter();
+ new TextEditor(event.getPlayer(), event::reopen, obj -> {
+ if (obj == null)
+ resetValue();
+ else
+ setValue(obj);
+ ItemUtils.lore(event.getClicked(), getLore());
+ event.reopen();
+ }).passNullIntoEndConsumer().start();
}
}
-
+
public abstract void sendIndication(Player p);
-
+
public abstract XMaterial getItemMaterial();
-
+
public abstract String getItemName();
-
+
public String getItemDescription() {
return null;
}
-
+
public boolean isMultiline() {
return false;
}
-
+
}
diff --git a/core/src/main/java/fr/skytasul/quests/api/options/UpdatableOptionSet.java b/api/src/main/java/fr/skytasul/quests/api/options/UpdatableOptionSet.java
similarity index 53%
rename from core/src/main/java/fr/skytasul/quests/api/options/UpdatableOptionSet.java
rename to api/src/main/java/fr/skytasul/quests/api/options/UpdatableOptionSet.java
index e54b52ce..b4eb44bd 100644
--- a/core/src/main/java/fr/skytasul/quests/api/options/UpdatableOptionSet.java
+++ b/api/src/main/java/fr/skytasul/quests/api/options/UpdatableOptionSet.java
@@ -1,15 +1,9 @@
package fr.skytasul.quests.api.options;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import fr.skytasul.quests.api.options.UpdatableOptionSet.Updatable;
+import java.util.*;
@SuppressWarnings ("rawtypes")
-public abstract class UpdatableOptionSet implements OptionSet {
+public class UpdatableOptionSet implements OptionSet {
private Map>, OptionWrapper> options = new HashMap<>();
@@ -18,8 +12,8 @@ public Iterator iterator() {
return options.values().stream().map(wrapper -> wrapper.option).iterator();
}
- protected void addOption(QuestOption option, U updatable) {
- options.put((Class extends QuestOption>>) option.getClass(), new OptionWrapper(option, updatable));
+ public void addOption(QuestOption option, Runnable update) {
+ options.put((Class extends QuestOption>>) option.getClass(), new OptionWrapper(option, update));
}
@Override
@@ -32,32 +26,29 @@ public boolean hasOption(Class extends QuestOption>> clazz) {
return options.containsKey(clazz);
}
- protected OptionWrapper getWrapper(Class extends QuestOption>> optionClass) {
+ public OptionWrapper getWrapper(Class extends QuestOption>> optionClass) {
return options.get(optionClass);
}
- protected void calculateDependencies() {
+ public void calculateDependencies() {
+ options.values().forEach(wrapper -> wrapper.dependent.clear());
for (OptionWrapper wrapper : options.values()) {
for (Class extends QuestOption>> requiredOptionClass : wrapper.option.getRequiredQuestOptions()) {
- options.get(requiredOptionClass).dependent.add(wrapper.updatable);
+ options.get(requiredOptionClass).dependent.add(wrapper.update);
}
}
}
public class OptionWrapper {
public final QuestOption option;
- public final U updatable;
- public final List dependent = new ArrayList<>();
+ public final Runnable update;
+ public final List dependent = new ArrayList<>();
- public OptionWrapper(QuestOption option, U updatable) {
+ public OptionWrapper(QuestOption option, Runnable update) {
this.option = option;
- this.updatable = updatable;
- option.setValueUpdaterListener(() -> dependent.forEach(U::update));
+ this.update = update;
+ option.setValueUpdaterListener(() -> dependent.forEach(Runnable::run));
}
}
- public interface Updatable {
- void update();
- }
-
}
diff --git a/api/src/main/java/fr/skytasul/quests/api/options/description/DescriptionSource.java b/api/src/main/java/fr/skytasul/quests/api/options/description/DescriptionSource.java
new file mode 100644
index 00000000..52963dc7
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/options/description/DescriptionSource.java
@@ -0,0 +1,10 @@
+package fr.skytasul.quests.api.options.description;
+
+/**
+ * Where do the description request come from
+ */
+public enum DescriptionSource {
+
+ SCOREBOARD, MENU, PLACEHOLDER, FORCESPLIT, FORCELINE;
+
+}
\ No newline at end of file
diff --git a/api/src/main/java/fr/skytasul/quests/api/options/description/QuestDescription.java b/api/src/main/java/fr/skytasul/quests/api/options/description/QuestDescription.java
new file mode 100644
index 00000000..1dc9f4e3
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/options/description/QuestDescription.java
@@ -0,0 +1,15 @@
+package fr.skytasul.quests.api.options.description;
+
+public interface QuestDescription {
+
+ public boolean showRewards();
+
+ public String getRewardsFormat();
+
+ public boolean showRequirements();
+
+ public String getRequirementsValid();
+
+ public String getRequirementsInvalid();
+
+}
diff --git a/core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionContext.java b/api/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionContext.java
similarity index 63%
rename from core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionContext.java
rename to api/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionContext.java
index 858b11e6..5c6543db 100644
--- a/core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionContext.java
+++ b/api/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionContext.java
@@ -3,25 +3,25 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
-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;
-import fr.skytasul.quests.structure.QuestBranch.Source;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import fr.skytasul.quests.api.players.PlayerAccount;
+import fr.skytasul.quests.api.players.PlayerQuestDatas;
+import fr.skytasul.quests.api.quests.Quest;
+import fr.skytasul.quests.api.utils.PlayerListCategory;
public class QuestDescriptionContext {
private final QuestDescription descriptionOptions;
private final Quest quest;
private final PlayerAccount acc;
- private final Category category;
- private final Source source;
+ private final PlayerListCategory category;
+ private final DescriptionSource source;
private PlayerQuestDatas cachedDatas;
- public QuestDescriptionContext(QuestDescription descriptionOptions, Quest quest, PlayerAccount acc,
- PlayerListGUI.Category category, Source source) {
+ public QuestDescriptionContext(@NotNull QuestDescription descriptionOptions, @NotNull Quest quest,
+ @NotNull PlayerAccount acc, @NotNull PlayerListCategory category, @NotNull DescriptionSource source) {
this.descriptionOptions = descriptionOptions;
this.quest = quest;
this.acc = acc;
@@ -29,32 +29,32 @@ public QuestDescriptionContext(QuestDescription descriptionOptions, Quest quest,
this.source = source;
}
- public QuestDescription getDescriptionOptions() {
+ public @NotNull QuestDescription getDescriptionOptions() {
return descriptionOptions;
}
- public Quest getQuest() {
+ public @NotNull Quest getQuest() {
return quest;
}
- public PlayerAccount getPlayerAccount() {
+ public @NotNull PlayerAccount getPlayerAccount() {
return acc;
}
- public Category getCategory() {
+ public @NotNull PlayerListCategory getCategory() {
return category;
}
- public Source getSource() {
+ public @NotNull DescriptionSource getSource() {
return source;
}
- public PlayerQuestDatas getQuestDatas() {
+ public @Nullable PlayerQuestDatas getQuestDatas() {
if (cachedDatas == null) cachedDatas = acc.getQuestDatasIfPresent(quest);
return cachedDatas;
}
- public List formatDescription() {
+ public @NotNull List<@Nullable String> formatDescription() {
List list = new ArrayList<>();
quest.getDescriptions()
diff --git a/core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionProvider.java b/api/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionProvider.java
similarity index 69%
rename from core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionProvider.java
rename to api/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionProvider.java
index 0b968a7a..59d36b21 100644
--- a/core/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionProvider.java
+++ b/api/src/main/java/fr/skytasul/quests/api/options/description/QuestDescriptionProvider.java
@@ -2,13 +2,17 @@
import java.util.Comparator;
import java.util.List;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
public interface QuestDescriptionProvider {
public static final Comparator COMPARATOR = Comparator.comparingDouble(QuestDescriptionProvider::getDescriptionPriority);
- List provideDescription(QuestDescriptionContext context);
+ @Nullable
+ List<@Nullable String> provideDescription(@NotNull QuestDescriptionContext context);
+ @NotNull
String getDescriptionId();
double getDescriptionPriority();
diff --git a/api/src/main/java/fr/skytasul/quests/api/players/PlayerAccount.java b/api/src/main/java/fr/skytasul/quests/api/players/PlayerAccount.java
new file mode 100644
index 00000000..776ddf2d
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/players/PlayerAccount.java
@@ -0,0 +1,68 @@
+package fr.skytasul.quests.api.players;
+
+import java.util.Collection;
+import java.util.concurrent.CompletableFuture;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.UnmodifiableView;
+import fr.skytasul.quests.api.data.SavableData;
+import fr.skytasul.quests.api.pools.QuestPool;
+import fr.skytasul.quests.api.quests.Quest;
+import fr.skytasul.quests.api.utils.messaging.HasPlaceholders;
+
+public interface PlayerAccount extends HasPlaceholders {
+
+ /**
+ * @return if this account is currently used by the player (if true, {@link #getPlayer()} cannot
+ * return a null player)
+ */
+ public boolean 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 @NotNull OfflinePlayer 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 @Nullable Player getPlayer();
+
+ public boolean hasQuestDatas(@NotNull Quest quest);
+
+ public @Nullable PlayerQuestDatas getQuestDatasIfPresent(@NotNull Quest quest);
+
+ public @NotNull PlayerQuestDatas getQuestDatas(@NotNull Quest quest);
+
+ public @NotNull CompletableFuture removeQuestDatas(@NotNull Quest quest);
+
+ public @NotNull CompletableFuture removeQuestDatas(int id);
+
+ public @UnmodifiableView @NotNull Collection<@NotNull PlayerQuestDatas> getQuestsDatas();
+
+ public boolean hasPoolDatas(@NotNull QuestPool pool);
+
+ public @NotNull PlayerPoolDatas getPoolDatas(@NotNull QuestPool pool);
+
+ public @NotNull CompletableFuture removePoolDatas(@NotNull QuestPool pool);
+
+ public @NotNull CompletableFuture removePoolDatas(int id);
+
+ public @UnmodifiableView @NotNull Collection<@NotNull PlayerPoolDatas> getPoolDatas();
+
+ public @Nullable T getData(@NotNull SavableData data);
+
+ public void setData(@NotNull SavableData data, @Nullable T value);
+
+ public void resetDatas();
+
+ public @NotNull String getName();
+
+ public @NotNull String getNameAndID();
+
+ public @NotNull String debugName();
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/players/PlayerPoolDatas.java b/api/src/main/java/fr/skytasul/quests/api/players/PlayerPoolDatas.java
new file mode 100644
index 00000000..77065ede
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/players/PlayerPoolDatas.java
@@ -0,0 +1,29 @@
+package fr.skytasul.quests.api.players;
+
+import java.util.Set;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.UnmodifiableView;
+import fr.skytasul.quests.api.pools.QuestPool;
+
+public interface PlayerPoolDatas {
+
+ @NotNull
+ PlayerAccount getAccount();
+
+ int getPoolID();
+
+ @Nullable
+ QuestPool getPool();
+
+ long getLastGive();
+
+ void setLastGive(long lastGive);
+
+ @NotNull
+ @UnmodifiableView
+ Set<@NotNull Integer> getCompletedQuests();
+
+ void setCompletedQuests(@NotNull Set completedQuests);
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/players/PlayerQuestDatas.java b/api/src/main/java/fr/skytasul/quests/api/players/PlayerQuestDatas.java
new file mode 100644
index 00000000..f5ba748a
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/players/PlayerQuestDatas.java
@@ -0,0 +1,67 @@
+package fr.skytasul.quests.api.players;
+
+import java.util.Map;
+import java.util.stream.Stream;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import fr.skytasul.quests.api.quests.Quest;
+import fr.skytasul.quests.api.stages.StageController;
+
+public interface PlayerQuestDatas {
+
+ int getQuestID();
+
+ @Nullable
+ Quest getQuest();
+
+ boolean isFinished();
+
+ void incrementFinished();
+
+ int getTimesFinished();
+
+ long getTimer();
+
+ void setTimer(long timer);
+
+ int getBranch();
+
+ void setBranch(int branch);
+
+ int getStage();
+
+ void setStage(int stage);
+
+ boolean hasStarted();
+
+ boolean isInQuestEnd();
+
+ void setInQuestEnd();
+
+ boolean isInEndingStages();
+
+ void setInEndingStages();
+
+ @Nullable T getAdditionalData(@NotNull String key);
+
+ @Nullable T setAdditionalData(@NotNull String key, @Nullable T value);
+
+ @Nullable
+ Map<@NotNull String, @Nullable Object> getStageDatas(int stage);
+
+ void setStageDatas(int stage, @Nullable Map<@NotNull String, @Nullable Object> datas);
+
+ long getStartingTime();
+
+ void setStartingTime(long time);
+
+ @NotNull
+ String getQuestFlow();
+
+ void addQuestFlow(@NotNull StageController finished);
+
+ void resetQuestFlow();
+
+ Stream getQuestFlowStages();
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/players/PlayersManager.java b/api/src/main/java/fr/skytasul/quests/api/players/PlayersManager.java
new file mode 100644
index 00000000..d9584d6d
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/players/PlayersManager.java
@@ -0,0 +1,24 @@
+package fr.skytasul.quests.api.players;
+
+import java.util.Collection;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.UnknownNullability;
+import fr.skytasul.quests.api.QuestsPlugin;
+import fr.skytasul.quests.api.data.SavableData;
+
+public interface PlayersManager {
+
+ public abstract void save();
+
+ public void addAccountData(@NotNull SavableData> data);
+
+ public @NotNull Collection<@NotNull SavableData>> getAccountDatas();
+
+ public @UnknownNullability PlayerAccount getAccount(@NotNull Player p);
+
+ public static @UnknownNullability PlayerAccount getPlayerAccount(@NotNull Player p) {
+ return QuestsPlugin.getPlugin().getPlayersManager().getAccount(p);
+ }
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/pools/QuestPool.java b/api/src/main/java/fr/skytasul/quests/api/pools/QuestPool.java
new file mode 100644
index 00000000..6c5f884c
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/pools/QuestPool.java
@@ -0,0 +1,58 @@
+package fr.skytasul.quests.api.pools;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import fr.skytasul.quests.api.players.PlayerAccount;
+import fr.skytasul.quests.api.players.PlayerPoolDatas;
+import fr.skytasul.quests.api.quests.Quest;
+import fr.skytasul.quests.api.requirements.RequirementList;
+import fr.skytasul.quests.api.utils.messaging.HasPlaceholders;
+
+public interface QuestPool extends HasPlaceholders {
+
+ int getId();
+
+ @Nullable
+ String getNpcId();
+
+ @Nullable
+ String getHologram();
+
+ int getMaxQuests();
+
+ int getQuestsPerLaunch();
+
+ boolean isRedoAllowed();
+
+ long getTimeDiff();
+
+ boolean doAvoidDuplicates();
+
+ @NotNull
+ RequirementList getRequirements();
+
+ @NotNull
+ List<@NotNull Quest> getQuests();
+
+ void addQuest(@NotNull Quest quest);
+
+ void removeQuest(@NotNull Quest quest);
+
+ @NotNull
+ ItemStack getItemStack(@NotNull String action);
+
+ @NotNull
+ CompletableFuture resetPlayer(@NotNull PlayerAccount acc);
+
+ void resetPlayerTimer(@NotNull PlayerAccount acc);
+
+ boolean canGive(@NotNull Player p);
+
+ @NotNull
+ CompletableFuture give(@NotNull Player p);
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/pools/QuestPoolsManager.java b/api/src/main/java/fr/skytasul/quests/api/pools/QuestPoolsManager.java
new file mode 100644
index 00000000..3a5614ca
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/pools/QuestPoolsManager.java
@@ -0,0 +1,21 @@
+package fr.skytasul.quests.api.pools;
+
+import java.util.Collection;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.UnmodifiableView;
+import fr.skytasul.quests.api.requirements.RequirementList;
+
+public interface QuestPoolsManager {
+
+ public @NotNull QuestPool createPool(@Nullable QuestPool editing, @Nullable String npcID, @Nullable String hologram,
+ int maxQuests, int questsPerLaunch, boolean redoAllowed, long timeDiff, boolean avoidDuplicates,
+ @NotNull RequirementList requirements);
+
+ public void removePool(int id);
+
+ public @Nullable QuestPool getPool(int id);
+
+ public @NotNull @UnmodifiableView Collection getPools();
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/quests/Quest.java b/api/src/main/java/fr/skytasul/quests/api/quests/Quest.java
new file mode 100644
index 00000000..dc02b749
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/quests/Quest.java
@@ -0,0 +1,85 @@
+package fr.skytasul.quests.api.quests;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+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.options.description.DescriptionSource;
+import fr.skytasul.quests.api.options.description.QuestDescriptionProvider;
+import fr.skytasul.quests.api.players.PlayerAccount;
+import fr.skytasul.quests.api.quests.branches.QuestBranchesManager;
+import fr.skytasul.quests.api.utils.QuestVisibilityLocation;
+import fr.skytasul.quests.api.utils.messaging.HasPlaceholders;
+
+public interface Quest extends OptionSet, Comparable, HasPlaceholders {
+
+ int getId();
+
+ boolean isValid();
+
+ void delete(boolean silently, boolean keepDatas);
+
+ @NotNull
+ QuestBranchesManager getBranchesManager();
+
+ public void addOption(@NotNull QuestOption> option);
+
+ public void removeOption(@NotNull Class extends QuestOption>> clazz);
+
+ public @NotNull List getDescriptions();
+
+ public @Nullable String getName();
+
+ public @Nullable String getDescription();
+
+ public @NotNull ItemStack getQuestItem();
+
+ public @Nullable BqNpc getStarterNpc();
+
+ public boolean isScoreboardEnabled();
+
+ public boolean isCancellable();
+
+ public boolean isRepeatable();
+
+ public boolean isHidden(QuestVisibilityLocation location);
+
+ public boolean isHiddenWhenRequirementsNotMet();
+
+ public boolean canBypassLimit();
+
+ public boolean hasStarted(@NotNull PlayerAccount acc);
+
+ public boolean hasFinished(@NotNull PlayerAccount acc);
+
+ public @NotNull String getDescriptionLine(@NotNull PlayerAccount acc, @NotNull DescriptionSource source);
+
+ public boolean canStart(@NotNull Player player, boolean sendMessage);
+
+ public boolean cancelPlayer(@NotNull PlayerAccount acc);
+
+ public @NotNull CompletableFuture resetPlayer(@NotNull PlayerAccount acc);
+
+ public @NotNull CompletableFuture attemptStart(@NotNull Player player);
+
+ public void doNpcClick(@NotNull Player player);
+
+ public default void start(@NotNull Player player) {
+ start(player, false);
+ }
+
+ public void start(@NotNull Player player, boolean silently);
+
+ public void finish(@NotNull Player player);
+
+ @Override
+ default int compareTo(Quest o) {
+ return Integer.compare(getId(), o.getId());
+ }
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/quests/QuestsManager.java b/api/src/main/java/fr/skytasul/quests/api/quests/QuestsManager.java
new file mode 100644
index 00000000..f049ebaa
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/quests/QuestsManager.java
@@ -0,0 +1,42 @@
+package fr.skytasul.quests.api.quests;
+
+import java.io.File;
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.UnmodifiableView;
+import fr.skytasul.quests.api.players.PlayerAccount;
+
+public interface QuestsManager {
+
+ @NotNull
+ File getSaveFolder();
+
+ @NotNull
+ @UnmodifiableView
+ List<@NotNull Quest> getQuests();
+
+ @Nullable
+ Quest getQuest(int id);
+
+ void addQuest(@NotNull Quest quest);
+
+ @NotNull
+ List<@NotNull Quest> getQuestsStarted(PlayerAccount acc);
+
+ @NotNull
+ List<@NotNull Quest> getQuestsStarted(@NotNull PlayerAccount acc, boolean hide,
+ boolean withoutScoreboard);
+
+ @NotNull
+ List<@NotNull Quest> getQuestsFinished(@NotNull PlayerAccount acc, boolean hide);
+
+ @NotNull
+ List<@NotNull Quest> getQuestsNotStarted(@NotNull PlayerAccount acc, boolean hide,
+ boolean clickableAndRedoable);
+
+ void updateQuestsStarted(@NotNull PlayerAccount acc, boolean withoutScoreboard, @NotNull List<@NotNull Quest> list);
+
+ int getStartedSize(@NotNull PlayerAccount acc);
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/quests/branches/EndingStage.java b/api/src/main/java/fr/skytasul/quests/api/quests/branches/EndingStage.java
new file mode 100644
index 00000000..2b32500e
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/quests/branches/EndingStage.java
@@ -0,0 +1,13 @@
+package fr.skytasul.quests.api.quests.branches;
+
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import fr.skytasul.quests.api.stages.StageController;
+
+public interface EndingStage {
+
+ public @NotNull StageController getStage();
+
+ public @Nullable QuestBranch getBranch();
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/quests/branches/QuestBranch.java b/api/src/main/java/fr/skytasul/quests/api/quests/branches/QuestBranch.java
new file mode 100644
index 00000000..7d2f53c1
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/quests/branches/QuestBranch.java
@@ -0,0 +1,36 @@
+package fr.skytasul.quests.api.quests.branches;
+
+import java.util.List;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.UnmodifiableView;
+import fr.skytasul.quests.api.options.description.DescriptionSource;
+import fr.skytasul.quests.api.players.PlayerAccount;
+import fr.skytasul.quests.api.quests.Quest;
+import fr.skytasul.quests.api.stages.StageController;
+
+public interface QuestBranch {
+
+ int getId();
+
+ @NotNull
+ QuestBranchesManager getManager();
+
+ @NotNull
+ default Quest getQuest() {
+ return getManager().getQuest();
+ }
+
+ public @NotNull @UnmodifiableView List<@NotNull StageController> getRegularStages();
+
+ public @NotNull StageController getRegularStage(int id);
+
+ public @NotNull @UnmodifiableView List getEndingStages();
+
+ public @NotNull StageController getEndingStage(int id);
+
+ public @NotNull String getDescriptionLine(@NotNull PlayerAccount acc, @NotNull DescriptionSource source);
+
+ public boolean hasStageLaunched(@Nullable PlayerAccount acc, @NotNull StageController stage);
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/quests/branches/QuestBranchesManager.java b/api/src/main/java/fr/skytasul/quests/api/quests/branches/QuestBranchesManager.java
new file mode 100644
index 00000000..b98a8c0d
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/quests/branches/QuestBranchesManager.java
@@ -0,0 +1,25 @@
+package fr.skytasul.quests.api.quests.branches;
+
+import java.util.Collection;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.annotations.UnmodifiableView;
+import fr.skytasul.quests.api.players.PlayerAccount;
+import fr.skytasul.quests.api.quests.Quest;
+
+public interface QuestBranchesManager {
+
+ @NotNull
+ Quest getQuest();
+
+ public int getId(@NotNull QuestBranch branch);
+
+ public @UnmodifiableView @NotNull Collection<@NotNull QuestBranch> getBranches();
+
+ public @Nullable QuestBranch getBranch(int id);
+
+ public @Nullable QuestBranch getPlayerBranch(@NotNull PlayerAccount acc);
+
+ public boolean hasBranchStarted(@NotNull PlayerAccount acc, @NotNull QuestBranch branch);
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/quests/creation/QuestCreationGui.java b/api/src/main/java/fr/skytasul/quests/api/quests/creation/QuestCreationGui.java
new file mode 100644
index 00000000..f097a1c8
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/quests/creation/QuestCreationGui.java
@@ -0,0 +1,15 @@
+package fr.skytasul.quests.api.quests.creation;
+
+import org.jetbrains.annotations.NotNull;
+import fr.skytasul.quests.api.gui.Gui;
+import fr.skytasul.quests.api.options.OptionSet;
+import fr.skytasul.quests.api.options.QuestOption;
+
+public interface QuestCreationGui extends Gui {
+
+ void updateOptionItem(@NotNull QuestOption> option);
+
+ @NotNull
+ OptionSet getOptionSet();
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/quests/creation/QuestCreationGuiClickEvent.java b/api/src/main/java/fr/skytasul/quests/api/quests/creation/QuestCreationGuiClickEvent.java
new file mode 100644
index 00000000..9ff2f7a6
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/quests/creation/QuestCreationGuiClickEvent.java
@@ -0,0 +1,27 @@
+package fr.skytasul.quests.api.quests.creation;
+
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import fr.skytasul.quests.api.gui.GuiClickEvent;
+
+public class QuestCreationGuiClickEvent extends GuiClickEvent {
+
+ public QuestCreationGuiClickEvent(@NotNull Player player, @NotNull QuestCreationGui gui, @Nullable ItemStack clicked,
+ @Nullable ItemStack cursor, int slot, @NotNull ClickType click) {
+ super(player, gui, clicked, cursor, slot, click);
+ }
+
+ @Override
+ public void setCancelled(boolean cancelled) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public @NotNull QuestCreationGui getGui() {
+ return (@NotNull QuestCreationGui) super.getGui();
+ }
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/requirements/AbstractRequirement.java b/api/src/main/java/fr/skytasul/quests/api/requirements/AbstractRequirement.java
new file mode 100644
index 00000000..32a07f6b
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/requirements/AbstractRequirement.java
@@ -0,0 +1,146 @@
+package fr.skytasul.quests.api.requirements;
+
+import java.util.Map;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import fr.skytasul.quests.api.QuestsAPI;
+import fr.skytasul.quests.api.editors.TextEditor;
+import fr.skytasul.quests.api.gui.LoreBuilder;
+import fr.skytasul.quests.api.localization.Lang;
+import fr.skytasul.quests.api.objects.QuestObject;
+import fr.skytasul.quests.api.objects.QuestObjectClickEvent;
+import fr.skytasul.quests.api.serializable.SerializableObject;
+import fr.skytasul.quests.api.utils.messaging.MessageType;
+import fr.skytasul.quests.api.utils.messaging.MessageUtils;
+import fr.skytasul.quests.api.utils.messaging.PlaceholderRegistry;
+
+public abstract class AbstractRequirement extends QuestObject {
+
+ protected static final String CUSTOM_REASON_KEY = "customReason";
+
+ private String customReason;
+
+ protected AbstractRequirement() {
+ this(null, null);
+ }
+
+ protected AbstractRequirement(@Nullable String customDescription, @Nullable String customReason) {
+ super(QuestsAPI.getAPI().getRequirements(), customDescription);
+ this.customReason = customReason;
+ }
+
+ public @Nullable String getCustomReason() {
+ return customReason;
+ }
+
+ public void setCustomReason(@Nullable String customReason) {
+ this.customReason = customReason;
+ }
+
+ /**
+ * Called when the plugin has to check if a player can start a quest with this requirement
+ * @param p Player to test
+ * @return if the player fills conditions of this requirement
+ */
+ public abstract boolean test(@NotNull Player p);
+
+ /**
+ * Called if the condition if not filled and if the plugin allows to send a message to the player
+ * @param player Player to send the reason
+ */
+ public final boolean sendReason(@NotNull Player player) {
+ String reason;
+
+ if (!isValid())
+ reason = "§cerror: " + getInvalidReason();
+ else if (customReason != null)
+ reason = customReason;
+ else
+ reason = getDefaultReason(player);
+
+ if (reason != null && !reason.isEmpty() && !"none".equals(reason)) {
+ MessageUtils.sendMessage(player, reason, MessageType.DefaultMessageType.PREFIXED, getPlaceholdersRegistry());
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Gets the reason sent to the player in the chat if it does not meet the requirements and the user
+ * has not set a particular requirement reason.
+ *
+ * @param player player to get the message for
+ * @return the reason of the requirement (nullable)
+ */
+ protected @Nullable String getDefaultReason(@NotNull Player player) {
+ return null;
+ }
+
+ @Override
+ protected void createdPlaceholdersRegistry(@NotNull PlaceholderRegistry placeholders) {
+ super.createdPlaceholdersRegistry(placeholders);
+ placeholders.register("custom_reason", () -> customReason);
+ }
+
+ protected @Nullable ClickType getCustomReasonClick() {
+ return ClickType.SHIFT_RIGHT;
+ }
+
+ protected void sendCustomReasonHelpMessage(@NotNull Player p) {
+ Lang.CHOOSE_REQUIREMENT_CUSTOM_REASON.send(p);
+ }
+
+ @Override
+ protected void sendCustomDescriptionHelpMessage(@NotNull Player p) {
+ Lang.CHOOSE_REQUIREMENT_CUSTOM_DESCRIPTION.send(p);
+ }
+
+ @Override
+ protected final void clickInternal(@NotNull QuestObjectClickEvent event) {
+ if (event.getClick() == getCustomReasonClick()) {
+ sendCustomReasonHelpMessage(event.getPlayer());
+ new TextEditor(event.getPlayer(), event::reopenGUI, msg -> {
+ setCustomReason(msg);
+ event.reopenGUI();
+ }).passNullIntoEndConsumer().start();
+ } else {
+ itemClick(event);
+ }
+ }
+
+ protected abstract void itemClick(@NotNull QuestObjectClickEvent event);
+
+ @Override
+ protected void addLore(@NotNull LoreBuilder loreBuilder) {
+ super.addLore(loreBuilder);
+ loreBuilder.addDescription(Lang.requirementReason.format(
+ PlaceholderRegistry.of("reason", customReason == null ? Lang.NotSet.toString() : customReason)));
+ loreBuilder.addClick(getCustomReasonClick(), Lang.setRequirementReason.toString());
+ }
+
+ @Override
+ public abstract @NotNull AbstractRequirement clone();
+
+ @Override
+ public void save(@NotNull ConfigurationSection section) {
+ super.save(section);
+ if (customReason != null)
+ section.set(CUSTOM_REASON_KEY, customReason);
+ }
+
+ @Override
+ public void load(@NotNull ConfigurationSection section) {
+ super.load(section);
+ if (section.contains(CUSTOM_REASON_KEY))
+ customReason = section.getString(CUSTOM_REASON_KEY);
+ }
+
+ public static @NotNull AbstractRequirement deserialize(Map map) {
+ return SerializableObject.deserialize(map, QuestsAPI.getAPI().getRequirements());
+ }
+
+}
diff --git a/core/src/main/java/fr/skytasul/quests/api/requirements/Actionnable.java b/api/src/main/java/fr/skytasul/quests/api/requirements/Actionnable.java
similarity index 55%
rename from core/src/main/java/fr/skytasul/quests/api/requirements/Actionnable.java
rename to api/src/main/java/fr/skytasul/quests/api/requirements/Actionnable.java
index c12af8a3..981fb495 100644
--- a/core/src/main/java/fr/skytasul/quests/api/requirements/Actionnable.java
+++ b/api/src/main/java/fr/skytasul/quests/api/requirements/Actionnable.java
@@ -1,9 +1,10 @@
package fr.skytasul.quests.api.requirements;
import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
public interface Actionnable {
- public void trigger(Player p);
+ public void trigger(@NotNull Player p);
}
diff --git a/api/src/main/java/fr/skytasul/quests/api/requirements/RequirementCreator.java b/api/src/main/java/fr/skytasul/quests/api/requirements/RequirementCreator.java
new file mode 100644
index 00000000..6470bae2
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/requirements/RequirementCreator.java
@@ -0,0 +1,22 @@
+package fr.skytasul.quests.api.requirements;
+
+import java.util.function.Supplier;
+import org.bukkit.inventory.ItemStack;
+import org.jetbrains.annotations.NotNull;
+import fr.skytasul.quests.api.objects.QuestObjectCreator;
+import fr.skytasul.quests.api.objects.QuestObjectLocation;
+
+public class RequirementCreator extends QuestObjectCreator {
+
+ public RequirementCreator(@NotNull String id, @NotNull Class extends AbstractRequirement> clazz, @NotNull ItemStack is,
+ @NotNull Supplier<@NotNull AbstractRequirement> newObjectSupplier) {
+ super(id, clazz, is, newObjectSupplier);
+ }
+
+ public RequirementCreator(@NotNull String id, @NotNull Class extends AbstractRequirement> clazz, @NotNull ItemStack is,
+ @NotNull Supplier<@NotNull AbstractRequirement> newObjectSupplier, boolean multiple,
+ @NotNull QuestObjectLocation @NotNull... allowedLocations) {
+ super(id, clazz, is, newObjectSupplier, multiple, allowedLocations);
+ }
+
+}
diff --git a/api/src/main/java/fr/skytasul/quests/api/requirements/RequirementList.java b/api/src/main/java/fr/skytasul/quests/api/requirements/RequirementList.java
new file mode 100644
index 00000000..156dda99
--- /dev/null
+++ b/api/src/main/java/fr/skytasul/quests/api/requirements/RequirementList.java
@@ -0,0 +1,84 @@
+package fr.skytasul.quests.api.requirements;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
+import fr.skytasul.quests.api.QuestsPlugin;
+import fr.skytasul.quests.api.localization.Lang;
+import fr.skytasul.quests.api.quests.Quest;
+import fr.skytasul.quests.api.serializable.SerializableObject;
+
+public class RequirementList extends ArrayList<@NotNull AbstractRequirement> {
+
+ private static final long serialVersionUID = 5568034962195448395L;
+
+ public RequirementList() {}
+
+ public RequirementList(@NotNull Collection<@NotNull AbstractRequirement> requirements) {
+ super(requirements);
+ }
+
+ public boolean allMatch(@NotNull Player p, boolean message) {
+ boolean match = true;
+ for (AbstractRequirement requirement : this) {
+ try {
+ if (!requirement.isValid() || !requirement.test(p)) {
+ if (!message || requirement.sendReason(p))
+ return false;
+
+ // if we are here, it means a reason has not yet been sent
+ // so we continue until a reason is sent OR there is no more requirement
+ match = false;
+ }
+ } catch (Exception ex) {
+ QuestsPlugin.getPlugin().getLoggerExpanded().severe(
+ "Cannot test requirement " + requirement.getClass().getSimpleName() + " for player " + p.getName(),
+ ex);
+ return false;
+ }
+ }
+ return match;
+ }
+
+ public boolean anyMatch(@NotNull Player p) {
+ for (AbstractRequirement requirement : this) {
+ try {
+ if (requirement.isValid() && requirement.test(p))
+ return true;
+ } catch (Exception ex) {
+ QuestsPlugin.getPlugin().getLoggerExpanded().severe(
+ "Cannot test requirement " + requirement.getClass().getSimpleName() + " for player " + p.getName(),
+ ex);
+ }
+ }
+ return false;
+ }
+
+ public void attachQuest(@NotNull Quest quest) {
+ forEach(requirement -> requirement.attach(quest));
+ }
+
+ public void detachQuest() {
+ forEach(requirement -> requirement.detach());
+ }
+
+ public String getSizeString() {
+ return getSizeString(size());
+ }
+
+ public @NotNull List