diff --git a/pom.xml b/pom.xml
index 8303459..6947217 100644
--- a/pom.xml
+++ b/pom.xml
@@ -75,13 +75,18 @@
com.github.TheSilentPro
SmartPlugin
- e565e96a72
+ 1ade8879c3
com.github.TheSilentPro
Warehouse
f40f72cb19
+
+ net.wesjd
+ anvilgui
+ 1.5.3-SNAPSHOT
+
@@ -90,12 +95,6 @@
1.7
provided
-
- me.lokka30
- treasury-api
- 1.1.0
- provided
-
me.clip
placeholderapi
@@ -126,38 +125,6 @@
-
-
- org.codehaus.mojo
- buildnumber-maven-plugin
- 3.0.0
-
-
- build-properties
- generate-resources
-
- create-metadata
-
-
-
-
- true
- true
-
- build.number
- build.revision
- build.branch
-
- false
- 7
- 0
-
- - buildNumber
- - scmVersion
-
-
-
-
org.apache.maven.plugins
diff --git a/src/main/java/tsp/headdb/HeadDB.java b/src/main/java/tsp/headdb/HeadDB.java
index 7449ea6..b858b51 100644
--- a/src/main/java/tsp/headdb/HeadDB.java
+++ b/src/main/java/tsp/headdb/HeadDB.java
@@ -1,6 +1,7 @@
package tsp.headdb;
import tsp.headdb.core.command.CommandCategory;
+import tsp.headdb.core.command.CommandGive;
import tsp.headdb.core.command.CommandHelp;
import tsp.headdb.core.command.CommandInfo;
import tsp.headdb.core.command.CommandLanguage;
@@ -11,6 +12,8 @@ import tsp.headdb.core.command.CommandSearch;
import tsp.headdb.core.command.CommandSettings;
import tsp.headdb.core.command.CommandTexture;
import tsp.headdb.core.command.CommandUpdate;
+import tsp.headdb.core.economy.BasicEconomyProvider;
+import tsp.headdb.core.economy.VaultProvider;
import tsp.headdb.core.listener.PlayerJoinListener;
import tsp.headdb.core.storage.Storage;
import tsp.headdb.core.task.UpdateTask;
@@ -23,6 +26,7 @@ import tsp.smartplugin.logger.PluginLogger;
import java.io.IOException;
import java.net.URISyntaxException;
+import java.util.Optional;
public class HeadDB extends SmartPlugin {
@@ -30,20 +34,23 @@ public class HeadDB extends SmartPlugin {
private PluginLogger logger;
private BuildProperties buildProperties;
private TranslatableLocalization localization;
+ private BasicEconomyProvider economyProvider;
private CommandManager commandManager;
private Storage storage;
@Override
public void onStart() {
instance = this;
+ instance.saveDefaultConfig();
instance.logger = new PluginLogger(this, getConfig().getBoolean("debug"));
instance.logger.info("Loading HeadDB - " + instance.getDescription().getVersion());
instance.buildProperties = new BuildProperties(this);
- instance.saveDefaultConfig();
- new UpdateTask(getConfig().getLong("refresh", 3600L)).schedule(this);
+ new UpdateTask(getConfig().getLong("refresh", 86400L)).schedule(this);
instance.logger.info("Loaded " + loadLocalization() + " languages!");
+ instance.initEconomy();
+
instance.storage = new Storage();
//instance.storage.load();
@@ -77,10 +84,27 @@ public class HeadDB extends SmartPlugin {
}
}
+ private void initEconomy() {
+ if (!getConfig().getBoolean("economy.enabled")) {
+ instance.logger.debug("Economy disabled by config.yml!");
+ instance.economyProvider = null;
+ return;
+ }
+
+ String raw = getConfig().getString("economy.provider", "VAULT");
+ if (raw.equalsIgnoreCase("VAULT")) {
+ economyProvider = new VaultProvider();
+ }
+
+ economyProvider.init();
+ instance.logger.info("Economy Provider: " + raw);
+ }
+
private void loadCommands() {
new CommandHelp().register();
new CommandCategory().register();
new CommandSearch().register();
+ new CommandGive().register();
new CommandUpdate().register();
new CommandTexture().register();
new CommandLanguage().register();
@@ -96,6 +120,10 @@ public class HeadDB extends SmartPlugin {
return commandManager;
}
+ public Optional getEconomyProvider() {
+ return Optional.ofNullable(economyProvider);
+ }
+
public TranslatableLocalization getLocalization() {
return localization;
}
diff --git a/src/main/java/tsp/headdb/core/api/HeadAPI.java b/src/main/java/tsp/headdb/core/api/HeadAPI.java
index ec14183..ef20d95 100644
--- a/src/main/java/tsp/headdb/core/api/HeadAPI.java
+++ b/src/main/java/tsp/headdb/core/api/HeadAPI.java
@@ -1,31 +1,116 @@
package tsp.headdb.core.api;
+import org.bukkit.Bukkit;
+
import tsp.headdb.HeadDB;
+import tsp.headdb.core.util.Utils;
import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.head.Head;
import tsp.headdb.implementation.head.HeadDatabase;
+import tsp.headdb.implementation.head.LocalHead;
import tsp.headdb.implementation.requester.HeadProvider;
+import javax.annotation.Nonnull;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+/**
+ * Head API for interacting with the main {@link HeadDatabase}.
+ *
+ * @author TheSilentPro (Silent)
+ * @see HeadDatabase
+ */
public final class HeadAPI {
+ /**
+ * Utility class. No initialization nor extension.
+ */
private HeadAPI() {}
+ /**
+ * The main {@link HeadDatabase}.
+ */
private static final HeadDatabase database = new HeadDatabase(HeadDB.getInstance(), HeadProvider.HEAD_STORAGE);
- public static synchronized Optional getHeadById(int id) {
+ /**
+ * Retrieve a {@link List} of {@link Head} matching the name.
+ *
+ * @param name The name to match against
+ * @param lenient Whether the filter should be lenient when matching
+ * @return {@link List Heads}
+ */
+ @Nonnull
+ public static List getHeadsByName(String name, boolean lenient) {
+ return getHeads().stream().filter(head -> (lenient ? Utils.matches(head.getName(), name) : head.getName().equalsIgnoreCase(name))).collect(Collectors.toList());
+ }
+
+ /**
+ * Retrieve a {@link List} of {@link Head} matching the name.
+ *
+ * @param name The name to match against
+ * @return {@link List Heads}
+ */
+ @Nonnull
+ public static List getHeadsByName(String name) {
+ return getHeadsByName(name, true);
+ }
+
+ /**
+ * Retrieve a {@link Head} by its exact name.
+ *
+ * @param name The name to look for
+ * @param lenient Whether the filter to be lenient when matching
+ * @return The {@link Head}, else empty
+ */
+ public static Optional getHeadByExactName(String name, boolean lenient) {
+ return getHeads().stream().filter(head -> (lenient ? Utils.matches(head.getName(), name) : head.getName().equalsIgnoreCase(name))).findAny();
+ }
+
+ /**
+ * Retrieve a {@link Head} by its exact name.
+ *
+ * @param name The name to look for
+ * @return The {@link Head}, else empty
+ */
+ @Nonnull
+ public static Optional getHeadByExactName(String name) {
+ return getHeadByExactName(name, false);
+ }
+
+ /**
+ * Retrieve a {@link Head} by its id.
+ *
+ * @param id The id to look for
+ * @return The {@link Head}, else empty
+ */
+ @Nonnull
+ public static Optional getHeadById(int id) {
return getHeads().stream().filter(head -> head.getId() == id).findAny();
}
- public static synchronized Optional getHeadByTexture(String texture) {
+ /**
+ * Retrieve a {@link Head} by its texture value.
+ *
+ * @param texture The texture to look for
+ * @return The {@link Head}, else empty
+ */
+ @Nonnull
+ public static Optional getHeadByTexture(String texture) {
return getHeads().stream().filter(head -> head.getTexture().equals(texture)).findAny();
}
+ /**
+ * Retrieve a {@link List} of {@link Head} within the main {@link HeadDatabase}.
+ *
+ * @return {@link List Heads}
+ */
+ @Nonnull
public static List getHeads() {
List result = new ArrayList<>();
for (Category category : getHeadsMap().keySet()) {
@@ -35,18 +120,53 @@ public final class HeadAPI {
return result;
}
+ /**
+ * Retrieve a {@link List} of {@link Head} within a {@link Category}.
+ *
+ * @param category The category to retrieve the heads from
+ * @return {@link List Heads}
+ */
+ @Nonnull
public static List getHeads(Category category) {
return getHeadsMap().get(category);
}
- public static synchronized Map> getHeadsMap() {
+ /**
+ * Retrieve an unmodifiable view of the database head map.
+ *
+ * @return The map
+ */
+ @Nonnull
+ public static Map> getHeadsMap() {
return Collections.unmodifiableMap(database.getHeads());
}
+ /**
+ * Retrieve the total amount of {@link Head heads} present in the main {@link HeadDatabase}.
+ *
+ * @return Amount of heads
+ */
public static int getTotalHeads() {
return getHeads().size();
}
+ /**
+ * Retrieve a {@link Set} of local heads.
+ * Note that this calculates the heads on every try.
+ *
+ * @return {@link Set Local Heads}
+ */
+ @Nonnull
+ public static Set getLocalHeads() {
+ return Arrays.stream(Bukkit.getOfflinePlayers()).map(player -> new LocalHead(player.getUniqueId(), player.getName())).collect(Collectors.toSet());
+ }
+
+ /**
+ * Retrieve the main {@link HeadDatabase} used by the plugin.
+ *
+ * @return {@link HeadDatabase Database}
+ */
+ @Nonnull
public static HeadDatabase getDatabase() {
return database;
}
diff --git a/src/main/java/tsp/headdb/core/api/event/HeadPurchaseEvent.java b/src/main/java/tsp/headdb/core/api/event/HeadPurchaseEvent.java
new file mode 100644
index 0000000..b5364c9
--- /dev/null
+++ b/src/main/java/tsp/headdb/core/api/event/HeadPurchaseEvent.java
@@ -0,0 +1,81 @@
+package tsp.headdb.core.api.event;
+
+import org.bukkit.entity.Player;
+import org.bukkit.event.Cancellable;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+import org.jetbrains.annotations.NotNull;
+import tsp.headdb.implementation.head.Head;
+
+import java.math.BigDecimal;
+
+/**
+ * Called BEFORE a head is purchased but AFTER the transaction is complete.
+ * This gives you the chance to cancel and refund the money.
+ * This event is fired asynchronously!
+ *
+ * @author TheSilentPro (Silent)
+ * @see tsp.headdb.core.util.Utils#processPayment(Player, Head)
+ * @see Event#isAsynchronous()
+ */
+public class HeadPurchaseEvent extends Event implements Cancellable {
+
+ private static final HandlerList HANDLER_LIST = new HandlerList();
+ private Player player;
+ private Head head;
+ private BigDecimal cost;
+ private final boolean success;
+ private boolean cancelled;
+
+ public HeadPurchaseEvent(Player player, Head head, BigDecimal cost, boolean success) {
+ super(true);
+ this.player = player;
+ this.head = head;
+ this.cost = cost;
+ this.success = success;
+ }
+
+ public boolean isSuccessful() {
+ return success;
+ }
+
+ @Override
+ public boolean isCancelled() {
+ return cancelled;
+ }
+
+ @Override
+ public void setCancelled(boolean cancel) {
+ this.cancelled = cancel;
+ }
+
+ @NotNull
+ @Override
+ public HandlerList getHandlers() {
+ return HANDLER_LIST;
+ }
+
+ public Player getPlayer() {
+ return player;
+ }
+
+ public void setPlayer(Player player) {
+ this.player = player;
+ }
+
+ public Head getHead() {
+ return head;
+ }
+
+ public void setHead(Head head) {
+ this.head = head;
+ }
+
+ public BigDecimal getCost() {
+ return cost;
+ }
+
+ public void setCost(BigDecimal cost) {
+ this.cost = cost;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/tsp/headdb/core/command/CommandCategory.java b/src/main/java/tsp/headdb/core/command/CommandCategory.java
index 2c2e453..cefc6c6 100644
--- a/src/main/java/tsp/headdb/core/command/CommandCategory.java
+++ b/src/main/java/tsp/headdb/core/command/CommandCategory.java
@@ -9,12 +9,14 @@ import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.head.Head;
import tsp.smartplugin.inventory.PagedPane;
+import java.util.Arrays;
import java.util.List;
+import java.util.stream.Collectors;
public class CommandCategory extends SubCommand {
public CommandCategory() {
- super("open", new String[]{"o"});
+ super("open", Arrays.stream(Category.VALUES).map(Category::getName).collect(Collectors.toList()), "o");
}
@Override
@@ -24,18 +26,23 @@ public class CommandCategory extends SubCommand {
return;
}
+ if (args.length < 2) {
+ getLocalization().sendMessage(player.getUniqueId(), "invalidArguments");
+ return;
+ }
+
Category.getByName(args[1]).ifPresentOrElse(category -> {
boolean requirePermission = HeadDB.getInstance().getConfig().getBoolean("requireCategoryPermission");
- if (requirePermission && !player.hasPermission("headdb.category." + category.getName())) {
+ if (requirePermission
+ && !player.hasPermission("headdb.category." + category.getName())
+ && !player.hasPermission("headdb.category.*")) {
getLocalization().sendMessage(player.getUniqueId(), "noPermission");
return;
}
int page = 0;
if (args.length >= 3) {
- try {
- page = Integer.parseInt(args[2]) - 1;
- } catch (NumberFormatException ignored) {}
+ page = Utils.resolveInt(args[2]) - 1;
}
List heads = HeadAPI.getHeads(category);
diff --git a/src/main/java/tsp/headdb/core/command/CommandGive.java b/src/main/java/tsp/headdb/core/command/CommandGive.java
new file mode 100644
index 0000000..522e332
--- /dev/null
+++ b/src/main/java/tsp/headdb/core/command/CommandGive.java
@@ -0,0 +1,57 @@
+package tsp.headdb.core.command;
+
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import tsp.headdb.core.api.HeadAPI;
+import tsp.headdb.core.util.Utils;
+import tsp.headdb.implementation.head.Head;
+
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+public class CommandGive extends SubCommand {
+
+ public CommandGive() {
+ super("give", HeadAPI.getHeads().stream().map(Head::getName).collect(Collectors.toList()), "g");
+ }
+
+ @Override
+ public void handle(CommandSender sender, String[] args) {
+ // /hdb give [amount]
+ if (args.length < 3) {
+ getLocalization().sendMessage(sender, "invalidArguments");
+ return;
+ }
+
+ int amount = args.length >= 4 ? Utils.resolveInt(args[3]) : 1;
+
+ Optional head;
+ String id = args[1];
+ if (id.startsWith("t:")) {
+ head = HeadAPI.getHeadByTexture(id.substring(2));
+ } else {
+ try {
+ head = HeadAPI.getHeadById(Integer.parseInt(id));
+ } catch (NumberFormatException nfe) {
+ // Attempt to find it by exact name (useful for tab completions)
+ head = HeadAPI.getHeadByExactName(id);
+ }
+ }
+
+ Player player = Bukkit.getPlayer(args[2]);
+ if (player == null) {
+ getLocalization().sendMessage(sender, "invalidTarget", msg -> msg.replace("%name%", args[2]));
+ return;
+ }
+
+ head.ifPresentOrElse(
+ value -> {
+ player.getInventory().addItem(value.getItem(player.getUniqueId()));
+ getLocalization().sendMessage(sender, "giveCommand", msg -> msg.replace("%size%", String.valueOf(amount)).replace("%name%", value.getName()).replace("%receiver%", player.getName()));
+ },
+ () -> getLocalization().sendMessage(sender, "giveCommandInvalid", msg -> msg.replace("%name%", id))
+ );
+ }
+
+}
diff --git a/src/main/java/tsp/headdb/core/command/CommandHelp.java b/src/main/java/tsp/headdb/core/command/CommandHelp.java
index 2828bd2..497282c 100644
--- a/src/main/java/tsp/headdb/core/command/CommandHelp.java
+++ b/src/main/java/tsp/headdb/core/command/CommandHelp.java
@@ -6,7 +6,7 @@ import tsp.smartplugin.player.PlayerUtils;
public class CommandHelp extends SubCommand {
public CommandHelp() {
- super("help", new String[]{"h"});
+ super("help", "h");
}
@Override
@@ -18,6 +18,7 @@ public class CommandHelp extends SubCommand {
PlayerUtils.sendMessage(sender, "&7/hdb &9info(i) &7- Show plugin information.");
PlayerUtils.sendMessage(sender, "&7/hdb &9open(o) &c &b[page] &7- Open a specific category.");
PlayerUtils.sendMessage(sender, "&7/hdb &9search(s) &b(id:|tg:)&c &7- Search for specific heads.");
+ PlayerUtils.sendMessage(sender, "&7/hdb &9give(g) &b(t:)&c &b[amount] &7- Give the player a specific head.");
PlayerUtils.sendMessage(sender, "&7/hdb &9update(u) &7- Manually update the database.");
PlayerUtils.sendMessage(sender, "&7/hdb &9language(l) &7- Change your language.");
PlayerUtils.sendMessage(sender, "&7/hdb &9settings(st) &7- Open the settings menu.");
diff --git a/src/main/java/tsp/headdb/core/command/CommandInfo.java b/src/main/java/tsp/headdb/core/command/CommandInfo.java
index 4db5fd4..69145b2 100644
--- a/src/main/java/tsp/headdb/core/command/CommandInfo.java
+++ b/src/main/java/tsp/headdb/core/command/CommandInfo.java
@@ -8,14 +8,14 @@ import tsp.smartplugin.player.PlayerUtils;
public class CommandInfo extends SubCommand {
public CommandInfo() {
- super("info", new String[]{"i"});
+ super("info", "i");
}
@Override
public void handle(CommandSender sender, String[] args) {
if (HeadDB.getInstance().getConfig().getBoolean("showAdvancedPluginInfo")) {
BuildProperties build = HeadDB.getInstance().getBuildProperties();
- PlayerUtils.sendMessage(sender, "&7Running &6HeadDB - " + build.getVersion() + " &7(&6Build " + build.getBuildNumber() + "&7)");
+ PlayerUtils.sendMessage(sender, "&7Running &6HeadDB - " + build.getVersion());
PlayerUtils.sendMessage(sender, "&7Created by &6" + HeadDB.getInstance().getDescription().getAuthors());
PlayerUtils.sendMessage(sender, "&7Compiled on &6" + build.getTimestamp() + " &7by &6" + build.getAuthor());
} else {
diff --git a/src/main/java/tsp/headdb/core/command/CommandLanguage.java b/src/main/java/tsp/headdb/core/command/CommandLanguage.java
index f53fcca..ea53642 100644
--- a/src/main/java/tsp/headdb/core/command/CommandLanguage.java
+++ b/src/main/java/tsp/headdb/core/command/CommandLanguage.java
@@ -2,32 +2,26 @@ package tsp.headdb.core.command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
-import tsp.smartplugin.localization.Message;
+import tsp.headdb.HeadDB;
import java.util.Set;
public class CommandLanguage extends SubCommand {
public CommandLanguage() {
- super("language", new String[]{"l", "lang"});
+ super("language", HeadDB.getInstance().getLocalization().getData().keySet(), "l", "lang");
}
@Override
public void handle(CommandSender sender, String[] args) {
- String lang = "en";
- if (args.length < 1) {
- getLocalization().sendMessage(new Message()
- .receiver(sender)
- .text("invalidArguments")
- );
+ if (args.length < 2) {
+ getLocalization().sendMessage(sender, "invalidArguments");
return;
}
+ String lang = args[1];
if (!getLocalization().getData().containsKey(lang)) {
- getLocalization().sendMessage(new Message()
- .receiver(sender)
- .text("invalidLanguage")
- .function(msg -> msg.replace("%languages%", toString(getLocalization().getData().keySet()))));
+ getLocalization().sendMessage(sender, "invalidLanguage", msg -> msg.replace("%languages%", toString(getLocalization().getData().keySet())));
return;
}
@@ -37,7 +31,7 @@ public class CommandLanguage extends SubCommand {
getLocalization().setLanguage(player.getUniqueId(), lang);
}
- getLocalization().sendMessage(new Message().receiver(sender).text("languageChanged").function(msg -> msg.replace("%language%", lang)));
+ getLocalization().sendMessage(sender, "languageChanged", msg -> msg.replace("%language%", lang));
}
private String toString(Set set) {
diff --git a/src/main/java/tsp/headdb/core/command/CommandMain.java b/src/main/java/tsp/headdb/core/command/CommandMain.java
index d8e6769..8e83195 100644
--- a/src/main/java/tsp/headdb/core/command/CommandMain.java
+++ b/src/main/java/tsp/headdb/core/command/CommandMain.java
@@ -1,30 +1,42 @@
package tsp.headdb.core.command;
+import net.wesjd.anvilgui.AnvilGUI;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
-import org.bukkit.command.ConsoleCommandSender;
-import org.bukkit.command.RemoteConsoleCommandSender;
+import org.bukkit.command.PluginCommand;
+import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
+import org.jetbrains.annotations.NotNull;
import tsp.headdb.HeadDB;
import tsp.headdb.core.api.HeadAPI;
import tsp.headdb.core.util.Utils;
import tsp.headdb.implementation.category.Category;
+import tsp.headdb.implementation.head.Head;
import tsp.smartplugin.inventory.Button;
+import tsp.smartplugin.inventory.PagedPane;
import tsp.smartplugin.inventory.Pane;
import tsp.smartplugin.localization.TranslatableLocalization;
-import tsp.smartplugin.utils.Validate;
+import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
-public class CommandMain extends HeadDBCommand implements CommandExecutor {
+public class CommandMain extends HeadDBCommand implements CommandExecutor, TabCompleter {
private final TranslatableLocalization localization = HeadDB.getInstance().getLocalization();
public CommandMain() {
- super("headdb", "headdb.command.open");
+ super(
+ "headdb",
+ "headdb.command.open",
+ HeadDB.getInstance().getCommandManager().getCommandsMap().values().stream().map(HeadDBCommand::getName).collect(Collectors.toList())
+ );
}
@Override
@@ -37,7 +49,7 @@ public class CommandMain extends HeadDBCommand implements CommandExecutor {
}
if (!player.hasPermission(getPermission())) {
- sendMessage(sender, "noPermission");
+ localization.sendMessage(sender, "noPermission");
return;
}
localization.sendMessage(player.getUniqueId(), "openDatabase");
@@ -45,13 +57,30 @@ public class CommandMain extends HeadDBCommand implements CommandExecutor {
Pane pane = new Pane(6, Utils.translateTitle(localization.getMessage(player.getUniqueId(), "menu.main.title").orElse("&cHeadDB &7(" + HeadAPI.getTotalHeads() + ")"), HeadAPI.getTotalHeads(), "Main"));
for (Category category : Category.VALUES) {
pane.addButton(new Button(category.getItem(player.getUniqueId()), e -> {
+ e.setCancelled(true);
if (e.isLeftClick()) {
Bukkit.dispatchCommand(e.getWhoClicked(), "hdb open " + category.getName());
} else if (e.isRightClick()) {
- // todo: specific page
+ new AnvilGUI.Builder()
+ .onComplete((p, text) -> {
+ try {
+ int page = Integer.parseInt(text);
+ // Remove when AnvilGUI adds option to return a void response
+ List heads = HeadAPI.getHeads(category);
+ PagedPane main = Utils.createPaged(player, Utils.translateTitle(getLocalization().getMessage(player.getUniqueId(), "menu.category.name").orElse(category.getName()), heads.size(), category.getName()));
+ Utils.addHeads(player, category, main, heads);
+ main.selectPage(page);
+ main.reRender();
+ return AnvilGUI.Response.openInventory(main.getInventory());
+ } catch (NumberFormatException nfe) {
+ return AnvilGUI.Response.text("Invalid number...");
+ }
+ })
+ .text("1")
+ .title(localization.getMessage(player.getUniqueId(), "menu.main.category.page.name").orElse("Enter page"))
+ .plugin(HeadDB.getInstance())
+ .open(player);
}
-
- e.setCancelled(true);
}));
}
@@ -66,19 +95,7 @@ public class CommandMain extends HeadDBCommand implements CommandExecutor {
}
command.handle(sender, args);
- }, () -> sendMessage(sender, "invalidSubCommand"));
- }
-
- @ParametersAreNonnullByDefault
- private void sendMessage(CommandSender sender, String key) {
- Validate.notNull(sender, "Message sender can not be null!");
- Validate.notNull(key, "Key can not be null!");
-
- if (sender instanceof Player player) {
- localization.sendMessage(player.getUniqueId(), key);
- } else if (sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender){
- localization.sendConsoleMessage(key);
- }
+ }, () -> localization.sendMessage(sender, "invalidSubCommand"));
}
@Override
@@ -88,4 +105,24 @@ public class CommandMain extends HeadDBCommand implements CommandExecutor {
return true;
}
+ @Nullable
+ @Override
+ public List onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
+ PluginCommand provided = HeadDB.getInstance().getCommand(label);
+ if (provided == null || !provided.getName().equalsIgnoreCase(getName())) {
+ return new ArrayList<>(); // not this command
+ }
+
+ if (args.length == 0) {
+ return new ArrayList<>(getCompletions());
+ } else {
+ Optional sub = HeadDB.getInstance().getCommandManager().getCommand(args[1]);
+ if (sub.isPresent()) {
+ return new ArrayList<>(sub.get().getCompletions());
+ }
+ }
+
+ return new ArrayList<>();
+ }
+
}
diff --git a/src/main/java/tsp/headdb/core/command/CommandSearch.java b/src/main/java/tsp/headdb/core/command/CommandSearch.java
index dc5f1f5..fd88e16 100644
--- a/src/main/java/tsp/headdb/core/command/CommandSearch.java
+++ b/src/main/java/tsp/headdb/core/command/CommandSearch.java
@@ -13,7 +13,7 @@ import java.util.List;
public class CommandSearch extends SubCommand {
public CommandSearch() {
- super("search", new String[]{"s"});
+ super("search", "s");
}
@Override
@@ -23,6 +23,11 @@ public class CommandSearch extends SubCommand {
return;
}
+ if (args.length < 2) {
+ getLocalization().sendMessage(player, "invalidArguments");
+ return;
+ }
+
StringBuilder builder = new StringBuilder();
for (int i = 1; i < args.length; i++) {
builder.append(args[i]);
diff --git a/src/main/java/tsp/headdb/core/command/CommandSettings.java b/src/main/java/tsp/headdb/core/command/CommandSettings.java
index dc4015d..af3134e 100644
--- a/src/main/java/tsp/headdb/core/command/CommandSettings.java
+++ b/src/main/java/tsp/headdb/core/command/CommandSettings.java
@@ -16,7 +16,7 @@ import java.util.Set;
public class CommandSettings extends SubCommand {
public CommandSettings() {
- super("settings", new String[]{"st"});
+ super("settings", "st");
}
@Override
@@ -32,16 +32,22 @@ public class CommandSettings extends SubCommand {
.name(getLocalization().getMessage(player.getUniqueId(), "menu.settings.language.name").orElse("&cLanguage"))
.setLore(getLocalization().getMessage(player.getUniqueId(), "menu.settings.language.available").orElse("&7Languages Available: &e%size%").replace("%size%", String.valueOf(langs.size())))
.build(), e -> {
+ e.setCancelled(true);
PagedPane langPane = new PagedPane(4, 6, Utils.translateTitle(getLocalization().getMessage(player.getUniqueId(), "menu.settings.language.title").orElse("&cHeadDB &7- &eSelect Language").replace("%languages%", "%size%"), langs.size(), "Selector: Language"));
for (String lang : langs) {
langPane.addButton(new Button(new ItemBuilder(Material.PAPER)
.name(getLocalization().getMessage(player.getUniqueId(), "menu.settings.language.format").orElse(ChatColor.YELLOW + lang).replace("%language%", lang))
.build(), langEvent -> {
+ e.setCancelled(true);
getLocalization().setLanguage(player.getUniqueId(), lang);
getLocalization().sendMessage(player.getUniqueId(), "languageChanged", msg -> msg.replace("%language%", lang));
}));
}
+
+ langPane.open(player);
}));
+
+ pane.open(player);
}
}
diff --git a/src/main/java/tsp/headdb/core/command/CommandTexture.java b/src/main/java/tsp/headdb/core/command/CommandTexture.java
index 94dd857..9e38942 100644
--- a/src/main/java/tsp/headdb/core/command/CommandTexture.java
+++ b/src/main/java/tsp/headdb/core/command/CommandTexture.java
@@ -11,7 +11,7 @@ import tsp.headdb.core.util.Utils;
public class CommandTexture extends SubCommand {
public CommandTexture() {
- super("texture", new String[]{"t"});
+ super("texture", "t");
}
@Override
@@ -26,7 +26,7 @@ public class CommandTexture extends SubCommand {
component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(getLocalization().getMessage(player.getUniqueId(), "copyTexture").orElse("Click to copy!"))));
component.setClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, texture));
player.spigot().sendMessage(component);
- }), () -> getLocalization().sendMessage("itemNoTexture"));
+ }), () -> getLocalization().sendMessage(sender,"itemNoTexture"));
}
}
diff --git a/src/main/java/tsp/headdb/core/command/CommandUpdate.java b/src/main/java/tsp/headdb/core/command/CommandUpdate.java
index dd3ec93..711e527 100644
--- a/src/main/java/tsp/headdb/core/command/CommandUpdate.java
+++ b/src/main/java/tsp/headdb/core/command/CommandUpdate.java
@@ -1,27 +1,19 @@
package tsp.headdb.core.command;
import org.bukkit.command.CommandSender;
+import tsp.headdb.HeadDB;
import tsp.headdb.core.api.HeadAPI;
-import tsp.smartplugin.localization.Message;
public class CommandUpdate extends SubCommand {
public CommandUpdate() {
- super("update", new String[]{"u"});
+ super("update", "u");
}
@Override
public void handle(CommandSender sender, String[] args) {
- getLocalization().sendMessage(new Message()
- .receiver(sender)
- .text("updateDatabase")
- );
-
- HeadAPI.getDatabase().update((time, heads) -> getLocalization().sendMessage(new Message()
- .receiver(sender)
- .text("updateDatabaseDone")
- .function(msg -> msg.replace("%size%", String.valueOf(heads.size()).replace("%time%", String.valueOf(time)))))
- );
+ getLocalization().sendMessage(sender, "updateDatabase");
+ HeadAPI.getDatabase().update((time, result) -> HeadDB.getInstance().getLog().debug("Database Updated! Heads: " + result.values().size() + " | Took: " + time + "ms"));
}
}
diff --git a/src/main/java/tsp/headdb/core/command/HeadDBCommand.java b/src/main/java/tsp/headdb/core/command/HeadDBCommand.java
index 69fafe7..3d4577b 100644
--- a/src/main/java/tsp/headdb/core/command/HeadDBCommand.java
+++ b/src/main/java/tsp/headdb/core/command/HeadDBCommand.java
@@ -3,20 +3,43 @@ package tsp.headdb.core.command;
import org.bukkit.command.CommandSender;
import tsp.headdb.HeadDB;
import tsp.smartplugin.localization.TranslatableLocalization;
+import tsp.smartplugin.utils.Validate;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
public abstract class HeadDBCommand {
private static final TranslatableLocalization localization = HeadDB.getInstance().getLocalization();
+
private final String name;
private final String permission;
+ private final Collection completions;
+
+ @ParametersAreNonnullByDefault
+ public HeadDBCommand(String name, String permission, Collection completions) {
+ Validate.notNull(name, "Name can not be null!");
+ Validate.notNull(permission, "Permission can not be null!");
+ Validate.notNull(completions, "Completions can not be null!");
- public HeadDBCommand(String name, String permission) {
this.name = name;
this.permission = permission;
+ this.completions = completions;
+ }
+
+ @ParametersAreNonnullByDefault
+ public HeadDBCommand(String name, String permission) {
+ this(name, permission, new ArrayList<>());
}
public abstract void handle(CommandSender sender, String[] args);
+ public Collection getCompletions() {
+ return completions;
+ }
+
public String getName() {
return name;
}
diff --git a/src/main/java/tsp/headdb/core/command/SubCommand.java b/src/main/java/tsp/headdb/core/command/SubCommand.java
index 380b226..f4fd5a8 100644
--- a/src/main/java/tsp/headdb/core/command/SubCommand.java
+++ b/src/main/java/tsp/headdb/core/command/SubCommand.java
@@ -3,6 +3,8 @@ package tsp.headdb.core.command;
import tsp.headdb.HeadDB;
import javax.annotation.Nullable;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.Optional;
// args[0] = sub-command | args[1+] = params
@@ -10,13 +12,17 @@ public abstract class SubCommand extends HeadDBCommand {
private final String[] aliases;
- public SubCommand(String name, @Nullable String[] aliases) {
- super(name, "headdb.command." + name);
+ public SubCommand(String name, Collection completions, @Nullable String... aliases) {
+ super(name, "headdb.command." + name, completions);
this.aliases = aliases;
}
+ public SubCommand(String name, String... aliases) {
+ this(name, new ArrayList<>(), aliases);
+ }
+
public SubCommand(String name) {
- this(name, null);
+ this(name, (String[]) null);
}
public Optional getAliases() {
diff --git a/src/main/java/tsp/headdb/core/economy/BasicEconomyProvider.java b/src/main/java/tsp/headdb/core/economy/BasicEconomyProvider.java
new file mode 100644
index 0000000..286cfb7
--- /dev/null
+++ b/src/main/java/tsp/headdb/core/economy/BasicEconomyProvider.java
@@ -0,0 +1,20 @@
+package tsp.headdb.core.economy;
+
+import org.bukkit.entity.Player;
+
+import java.math.BigDecimal;
+import java.util.concurrent.CompletableFuture;
+
+public interface BasicEconomyProvider {
+
+ CompletableFuture canPurchase(Player player, BigDecimal cost);
+
+ CompletableFuture withdraw(Player player, BigDecimal amount);
+
+ default CompletableFuture purchase(Player player, BigDecimal amount) {
+ return canPurchase(player, amount).thenCompose(result -> result ? withdraw(player, amount) : CompletableFuture.completedFuture(false));
+ }
+
+ void init();
+
+}
diff --git a/src/main/java/tsp/headdb/core/economy/VaultProvider.java b/src/main/java/tsp/headdb/core/economy/VaultProvider.java
new file mode 100644
index 0000000..1f8eaaa
--- /dev/null
+++ b/src/main/java/tsp/headdb/core/economy/VaultProvider.java
@@ -0,0 +1,45 @@
+package tsp.headdb.core.economy;
+
+import net.milkbowl.vault.economy.Economy;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.RegisteredServiceProvider;
+import tsp.headdb.HeadDB;
+
+import java.math.BigDecimal;
+import java.util.concurrent.CompletableFuture;
+
+public class VaultProvider implements BasicEconomyProvider {
+
+ private Economy economy;
+
+ @Override
+ public CompletableFuture canPurchase(Player player, BigDecimal cost) {
+ double effectiveCost = cost.doubleValue();
+ return CompletableFuture.supplyAsync(() -> economy.has(player, effectiveCost >= 0 ? effectiveCost : 0)); // Vault is really old...
+ }
+
+ @Override
+ public CompletableFuture withdraw(Player player, BigDecimal amount) {
+ double effectiveCost = amount.doubleValue();
+ return CompletableFuture.supplyAsync(() -> economy.withdrawPlayer(player, effectiveCost >= 0 ? effectiveCost : 0).transactionSuccess());
+ }
+
+
+ @Override
+ public void init() {
+ if (!Bukkit.getServer().getPluginManager().isPluginEnabled("Vault")) {
+ HeadDB.getInstance().getLog().error("Vault is not installed!");
+ return;
+ }
+
+ RegisteredServiceProvider economyProvider = Bukkit.getServer().getServicesManager().getRegistration(Economy.class);
+ if (economyProvider == null) {
+ HeadDB.getInstance().getLog().error("Could not find vault economy provider!");
+ return;
+ }
+
+ economy = economyProvider.getProvider();
+ }
+
+}
diff --git a/src/main/java/tsp/headdb/core/hook/Hooks.java b/src/main/java/tsp/headdb/core/hook/Hooks.java
new file mode 100644
index 0000000..d52ed61
--- /dev/null
+++ b/src/main/java/tsp/headdb/core/hook/Hooks.java
@@ -0,0 +1,9 @@
+package tsp.headdb.core.hook;
+
+import org.bukkit.Bukkit;
+
+public class Hooks {
+
+ public static PluginHook PAPI = new PluginHook(Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null);
+
+}
diff --git a/src/main/java/tsp/headdb/core/hook/PluginHook.java b/src/main/java/tsp/headdb/core/hook/PluginHook.java
new file mode 100644
index 0000000..bb224f4
--- /dev/null
+++ b/src/main/java/tsp/headdb/core/hook/PluginHook.java
@@ -0,0 +1,3 @@
+package tsp.headdb.core.hook;
+
+public record PluginHook(boolean enabled) {}
\ No newline at end of file
diff --git a/src/main/java/tsp/headdb/core/storage/HeadStorage.java b/src/main/java/tsp/headdb/core/storage/HeadStorage.java
index 643600a..ce8fb71 100644
--- a/src/main/java/tsp/headdb/core/storage/HeadStorage.java
+++ b/src/main/java/tsp/headdb/core/storage/HeadStorage.java
@@ -2,6 +2,7 @@ package tsp.headdb.core.storage;
import tsp.headdb.HeadDB;
import tsp.headdb.core.util.Utils;
+import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.head.Head;
import tsp.warehouse.storage.sql.SQLiteDataManager;
@@ -32,7 +33,8 @@ public class HeadStorage extends SQLiteDataManager> {
set.getString("name"),
set.getString("texture"),
set.getString("tags"),
- set.getString("updated")
+ set.getString("updated"),
+ Category.valueOf(set.getString("category"))
));
}
@@ -51,13 +53,14 @@ public class HeadStorage extends SQLiteDataManager> {
StringBuilder builder = new StringBuilder();
for (Head head : data) {
- builder.append(String.format("(%d, %s, %s, %s, %s, %s),",
+ builder.append(String.format("(%d, %s, %s, %s, %s, %s, %s),",
head.getId(),
head.getUniqueId().toString(),
head.getName(),
head.getTexture(),
head.getTags(),
- head.getUpdated()
+ head.getUpdated(),
+ head.getCategory().name()
));
}
diff --git a/src/main/java/tsp/headdb/core/util/BuildProperties.java b/src/main/java/tsp/headdb/core/util/BuildProperties.java
index caef439..2c5a157 100644
--- a/src/main/java/tsp/headdb/core/util/BuildProperties.java
+++ b/src/main/java/tsp/headdb/core/util/BuildProperties.java
@@ -14,9 +14,6 @@ import java.io.InputStreamReader;
public class BuildProperties {
private String version = "unknown";
- private int buildNumber = 0;
- private String revision = "unknown";
- private String branch = "unknown";
private String timestamp = "unknown";
private String author = "unknown";
@@ -29,9 +26,6 @@ public class BuildProperties {
YamlConfiguration data = YamlConfiguration.loadConfiguration(new InputStreamReader(in));
this.version = data.getString("version", "unknown");
- this.buildNumber = data.getInt("build", 0);
- this.revision = data.getString("buildRevision", "unknown");
- this.branch = data.getString("buildBranch", "unknown");
this.timestamp = data.getString("buildTimestamp", "unknown");
this.author = data.getString("buildAuthor", "unknown");
}
@@ -40,18 +34,6 @@ public class BuildProperties {
return version;
}
- public int getBuildNumber() {
- return buildNumber;
- }
-
- public String getRevision() {
- return revision;
- }
-
- public String getBranch() {
- return branch;
- }
-
public String getTimestamp() {
return timestamp;
}
diff --git a/src/main/java/tsp/headdb/core/util/Utils.java b/src/main/java/tsp/headdb/core/util/Utils.java
index 15ad225..dfc23af 100644
--- a/src/main/java/tsp/headdb/core/util/Utils.java
+++ b/src/main/java/tsp/headdb/core/util/Utils.java
@@ -2,12 +2,17 @@ package tsp.headdb.core.util;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
+import me.clip.placeholderapi.PlaceholderAPI;
+import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import tsp.headdb.HeadDB;
import tsp.headdb.core.api.HeadAPI;
+import tsp.headdb.core.api.event.HeadPurchaseEvent;
+import tsp.headdb.core.economy.BasicEconomyProvider;
+import tsp.headdb.core.hook.Hooks;
import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.head.Head;
import tsp.smartplugin.inventory.Button;
@@ -18,10 +23,12 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.lang.reflect.Field;
+import java.math.BigDecimal;
import java.util.Collection;
import java.util.Locale;
import java.util.Optional;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
public class Utils {
@@ -59,16 +66,16 @@ public class Utils {
public static PagedPane createPaged(Player player, String title) {
PagedPane main = new PagedPane(4, 6, title);
- main.getInventory().clear();
HeadAPI.getHeadByTexture("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODY1MmUyYjkzNmNhODAyNmJkMjg2NTFkN2M5ZjI4MTlkMmU5MjM2OTc3MzRkMThkZmRiMTM1NTBmOGZkYWQ1ZiJ9fX0=").ifPresent(head -> main.setBackItem(head.getItem(player.getUniqueId())));
HeadAPI.getHeadByTexture("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2Q5MWY1MTI2NmVkZGM2MjA3ZjEyYWU4ZDdhNDljNWRiMDQxNWFkYTA0ZGFiOTJiYjc2ODZhZmRiMTdmNGQ0ZSJ9fX0=").ifPresent(head -> main.setCurrentItem(head.getItem(player.getUniqueId())));
HeadAPI.getHeadByTexture("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmEzYjhmNjgxZGFhZDhiZjQzNmNhZThkYTNmZTgxMzFmNjJhMTYyYWI4MWFmNjM5YzNlMDY0NGFhNmFiYWMyZiJ9fX0=").ifPresent(head -> main.setNextItem(head.getItem(player.getUniqueId())));
+
+ main.setControlCurrent(new Button(main.getCurrentItem(), e -> Bukkit.dispatchCommand(player, "hdb")));
return main;
}
@ParametersAreNonnullByDefault
public static void addHeads(Player player, @Nullable Category category, PagedPane pane, Collection heads) {
- pane.getInventory().clear();
for (Head head : heads) {
ItemStack item = head.getItem(player.getUniqueId());
pane.addButton(new Button(item, e -> {
@@ -92,6 +99,42 @@ public class Utils {
}
}
+ public static CompletableFuture processPayment(Player player, Head head) {
+ BigDecimal cost = BigDecimal.valueOf(HeadDB.getInstance().getConfig().getDouble("economy.cost." + head.getCategory().getName()));
+
+ Optional optional = HeadDB.getInstance().getEconomyProvider();
+ if (optional.isEmpty()) {
+ return CompletableFuture.completedFuture(false);
+ } else {
+ return optional.get().purchase(player, cost).thenApply(success -> {
+ HeadPurchaseEvent event = new HeadPurchaseEvent(player, head, cost, success);
+ Bukkit.getPluginManager().callEvent(event);
+ return !event.isCancelled() && success;
+ });
+ }
+ }
+
+ public static void purchase(Player player, Head head) {
+ processPayment(player, head).whenComplete((success, ex) -> {
+ if (ex != null) {
+ HeadDB.getInstance().getLog().error("Failed to purchase head '" + head.getName() + "' for player: " + player.getName());
+ ex.printStackTrace();
+ } else {
+ // Bukkit API, therefore task is ran sync. Needs testing not sure if this can be ran async.
+ Bukkit.getScheduler().runTask(HeadDB.getInstance(), () -> {
+ player.getInventory().addItem(head.getItem(player.getUniqueId()));
+ HeadDB.getInstance().getConfig().getStringList("commands.purchase").forEach(command -> {
+ if (Hooks.PAPI.enabled()) {
+ command = PlaceholderAPI.setPlaceholders(player, command);
+ }
+
+ Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
+ });
+ });
+ }
+ });
+ }
+
public static Optional getTexture(ItemStack head) {
ItemMeta meta = head.getItemMeta();
if (meta == null) {
@@ -107,12 +150,21 @@ public class Utils {
}
return profile.getProperties().get("textures").stream()
- .filter(p -> p.getValue().equals("textures"))
+ .filter(p -> p.getName().equals("textures"))
.findAny()
- .map(Property::getName);
+ .map(Property::getValue);
} catch (NoSuchFieldException | SecurityException | IllegalAccessException e ) {
+ e.printStackTrace();
return Optional.empty();
}
}
+ public static int resolveInt(String raw) {
+ try {
+ return Integer.parseInt(raw);
+ } catch (NumberFormatException nfe) {
+ return 1;
+ }
+ }
+
}
diff --git a/src/main/java/tsp/headdb/implementation/category/Category.java b/src/main/java/tsp/headdb/implementation/category/Category.java
index e789b86..303aa60 100644
--- a/src/main/java/tsp/headdb/implementation/category/Category.java
+++ b/src/main/java/tsp/headdb/implementation/category/Category.java
@@ -2,15 +2,19 @@ package tsp.headdb.implementation.category;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
import tsp.headdb.HeadDB;
import tsp.headdb.core.api.HeadAPI;
import tsp.headdb.core.util.Utils;
import tsp.smartplugin.builder.item.ItemBuilder;
+import tsp.smartplugin.utils.StringUtils;
import javax.annotation.Nonnull;
+import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.UUID;
+import java.util.stream.Collectors;
public enum Category {
@@ -52,14 +56,25 @@ public enum Category {
public ItemStack getItem(UUID receiver) {
if (item == null) {
HeadAPI.getHeads(this).stream().findFirst()
- .ifPresentOrElse(head -> item = new ItemBuilder(head.getItem(receiver))
- .name(Utils.translateTitle(HeadDB.getInstance().getLocalization().getMessage(receiver, "menu.category.name").orElse("&e" + getName()), HeadAPI.getHeads(this).size(), getName().toUpperCase(Locale.ROOT)))
- .setLore((String[]) null)
- .build(),
+ .ifPresentOrElse(head -> {
+ ItemStack retrieved = new ItemStack(head.getItem(receiver));
+ ItemMeta meta = retrieved.getItemMeta();
+ if (meta != null && meta.getLore() != null) {
+ meta.setDisplayName(Utils.translateTitle(HeadDB.getInstance().getLocalization().getMessage(receiver, "menu.main.category.name").orElse("&e" + getName()), HeadAPI.getHeads(this).size(), getName().toUpperCase(Locale.ROOT)));
+ meta.setLore(HeadDB.getInstance().getConfig().getStringList("menu.main.category.lore").stream()
+ .map(StringUtils::colorize)
+ .collect(Collectors.toList()));
+ retrieved.setItemMeta(meta);
+ item = retrieved;
+ } else {
+ item = new ItemStack(Material.PLAYER_HEAD);
+ HeadDB.getInstance().getLog().debug("Failed to get null-meta category item for: " + name());
+ }
+ },
() -> item = new ItemBuilder(Material.PLAYER_HEAD).name(getName().toUpperCase(Locale.ROOT)).build());
}
- return item;
+ return item; // Return clone that changes are not reflected
}
}
diff --git a/src/main/java/tsp/headdb/implementation/head/Head.java b/src/main/java/tsp/headdb/implementation/head/Head.java
index 8ee432c..a67e05b 100644
--- a/src/main/java/tsp/headdb/implementation/head/Head.java
+++ b/src/main/java/tsp/headdb/implementation/head/Head.java
@@ -6,6 +6,7 @@ import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import tsp.headdb.HeadDB;
+import tsp.headdb.implementation.category.Category;
import tsp.smartplugin.builder.item.ItemBuilder;
import tsp.smartplugin.localization.TranslatableLocalization;
import tsp.smartplugin.utils.Validate;
@@ -23,15 +24,17 @@ public class Head {
private final String texture;
private final String tags;
private final String updated;
+ private final Category category;
private ItemStack item;
@ParametersAreNonnullByDefault
- public Head(int id, UUID uniqueId, String name, String texture, String tags, String updated) {
+ public Head(int id, UUID uniqueId, String name, String texture, String tags, String updated, Category category) {
Validate.notNull(uniqueId, "Unique id can not be null!");
Validate.notNull(name, "Name can not be null!");
Validate.notNull(texture, "Texture can not be null!");
Validate.notNull(tags, "Tags can not be null!");
Validate.notNull(updated, "Updated can not be null!");
+ Validate.notNull(category, "Category can not be null!");
this.id = id;
this.uniqueId = uniqueId;
@@ -39,6 +42,7 @@ public class Head {
this.texture = texture;
this.tags = tags;
this.updated = updated;
+ this.category = category;
}
public ItemStack getItem(UUID receiver) {
@@ -65,7 +69,7 @@ public class Head {
item.setItemMeta(meta);
}
- return item;
+ return item.clone(); // Return clone that changes are not reflected
}
public int getId() {
@@ -92,4 +96,8 @@ public class Head {
return updated;
}
+ public Category getCategory() {
+ return category;
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/tsp/headdb/implementation/head/HeadDatabase.java b/src/main/java/tsp/headdb/implementation/head/HeadDatabase.java
index f5f5490..437a214 100644
--- a/src/main/java/tsp/headdb/implementation/head/HeadDatabase.java
+++ b/src/main/java/tsp/headdb/implementation/head/HeadDatabase.java
@@ -2,19 +2,14 @@ package tsp.headdb.implementation.head;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitScheduler;
-import tsp.headdb.HeadDB;
import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.requester.HeadProvider;
import tsp.headdb.implementation.requester.Requester;
-import java.util.Collections;
-import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
-import java.util.function.Consumer;
public class HeadDatabase {
@@ -28,7 +23,7 @@ public class HeadDatabase {
this.plugin = plugin;
this.scheduler = plugin.getServer().getScheduler();
this.requester = new Requester(plugin, provider);
- this.heads = Collections.synchronizedMap(new EnumMap<>(Category.class));
+ this.heads = new HashMap<>();
}
public Map> getHeads() {
@@ -49,9 +44,7 @@ public class HeadDatabase {
public void update(BiConsumer>> fetched) {
getHeadsNoCache((elapsed, result) -> {
- synchronized (heads) {
- heads.putAll(result);
- }
+ heads.putAll(result);
timestamp = System.currentTimeMillis();
fetched.accept(elapsed, result);
});
diff --git a/src/main/java/tsp/headdb/implementation/head/LocalHead.java b/src/main/java/tsp/headdb/implementation/head/LocalHead.java
new file mode 100644
index 0000000..7ee1785
--- /dev/null
+++ b/src/main/java/tsp/headdb/implementation/head/LocalHead.java
@@ -0,0 +1,5 @@
+package tsp.headdb.implementation.head;
+
+import java.util.UUID;
+
+public record LocalHead(UUID uniqueId, String name) {}
\ No newline at end of file
diff --git a/src/main/java/tsp/headdb/implementation/requester/HeadProvider.java b/src/main/java/tsp/headdb/implementation/requester/HeadProvider.java
index bfabc3b..062330e 100644
--- a/src/main/java/tsp/headdb/implementation/requester/HeadProvider.java
+++ b/src/main/java/tsp/headdb/implementation/requester/HeadProvider.java
@@ -4,9 +4,9 @@ import tsp.headdb.implementation.category.Category;
public enum HeadProvider {
+ HEAD_API("https://minecraft-heads.com/scripts/api.php?cat=%s&tags=true"), // No ids
HEAD_STORAGE("https://raw.githubusercontent.com/TheSilentPro/HeadStorage/master/storage/%s.json"),
- HEAD_WORKER(""), // TODO: implement
- HEAD_API("https://minecraft-heads.com/scripts/api.php?cat=%s&tags=true"),
+ HEAD_WORKER(""), // Unimplemented yet.
HEAD_ARCHIVE("https://heads.pages.dev/archive/%s.json");
private final String url;
diff --git a/src/main/java/tsp/headdb/implementation/requester/Requester.java b/src/main/java/tsp/headdb/implementation/requester/Requester.java
index c8dada1..ef03c19 100644
--- a/src/main/java/tsp/headdb/implementation/requester/Requester.java
+++ b/src/main/java/tsp/headdb/implementation/requester/Requester.java
@@ -48,7 +48,8 @@ public class Requester {
obj.get("name").getAsString(),
obj.get("value").getAsString(),
obj.get("tags").getAsString(),
- response.date()
+ response.date(),
+ category
));
}
@@ -56,8 +57,10 @@ public class Requester {
});
} catch (IOException ex) {
HeadDB.getInstance().getLog().debug("Failed to load from provider: " + provider.name());
- provider = HeadProvider.HEAD_ARCHIVE;
- fetchAndResolve(category, heads);
+ if (HeadDB.getInstance().getConfig().getBoolean("fallback") && provider != HeadProvider.HEAD_ARCHIVE) { // prevent recursion. Maybe switch to an attempts counter down in the future
+ provider = HeadProvider.HEAD_ARCHIVE;
+ fetchAndResolve(category, heads);
+ }
}
}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
index 452e35b..2d2eb44 100644
--- a/src/main/resources/config.yml
+++ b/src/main/resources/config.yml
@@ -1,5 +1,5 @@
# How often the database should be updated in seconds.
-refresh: 3600
+refresh: 86400
# If local heads should be enabled. Only starts keeping track of joined players when enabled!
localHeads: true
@@ -11,27 +11,10 @@ requireCategoryPermission: false
# If enabled, the menu will close after purchasing a head (even if the purchase fails)
closeOnPurchase: false
-# Hidden heads from the menu
-hidden:
- enabled: false
- # If enabled it will also hide any heads matching these tags in the favorites menu
- hideFavorites: true
- # If the head name matches any of the listen words it will be hidden (case-sensitive)
- names:
- - ""
- # If the head has one of the listed tags it will be hidden
- tags:
- - ""
-
-# Economy options
+# Economy Options
economy:
enable: false
- # Supported: VAULT, TREASURY
- provider: "VAULT"
- # Providers like treasury support multiple currencies
- # Set the ID of the one used for head purchasing below.
- # Leave empty to use the primary currency or if the provider does not support multiple currencies
- currency: ""
+ provider: "VAULT" # Supported: VAULT
cost:
alphabet: 100
animals: 100
@@ -43,112 +26,14 @@ economy:
miscellaneous: 100
monsters: 100
plants: 100
- local: 1000
-
-# UI customization options.
-ui:
- category:
- # Head categories. You can use item: instead of head: here, but AIR is not supported.
- alphabet:
- location: 20
- head: 1788
- animals:
- location: 21
- head: 5741
- blocks:
- location: 22
- head: 8624
- decoration:
- location: 23
- head: 11046
- food-drinks:
- location: 24
- head: 17442
- humans:
- location: 29
- head: 19361
- humanoid:
- location: 30
- head: 28320
- miscellaneous:
- location: 31
- head: 32746
- monsters:
- location: 32
- head: 34819
- plants:
- location: 33
- head: 37278
- # Meta categories, used for UI elements. AIR is not supported. You can use head: instead of item: here.
- favorites:
- location: 39
- item: BOOK
- search:
- location: 40
- item: DARK_OAK_SIGN
- local:
- location: 41
- item: COMPASS
- # Item used to fill unused slots in the categories' menu. AIR is supported. You can use head: instead of item: here.
- fill:
- item: BLACK_STAINED_GLASS_PANE
- sound:
- # Whether the sounds should be played
- enabled: true
-
- # Played when a head is taken with no economy plugin
- noEconomy:
- # The name of the sound.
- # Must be one from: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Sound.html
- name: ENTITY_EXPERIENCE_ORB_PICKUP
- volume: 1
- pitch: 1
- # Played when the player doesn't have permission for the menu
- noPermission:
- name: BLOCK_ANVIL_BREAK
- volume: 1
- pitch: 1
- # Played when they purchase a head
- paid:
- name: ENTITY_EXPERIENCE_ORB_PICKUP
- volume: 1
- pitch: 1
- # Played when the head is free
- free:
- name: ENTITY_EXPERIENCE_ORB_PICKUP
- volume: 1
- pitch: 1
- # Played when the player does not have enough funds for the head
- unavailable:
- name: BLOCK_ANVIL_BREAK
- volume: 1
- pitch: 1
- # Played when a category/menu is opened
- open:
- name: ENTITY_EXPERIENCE_ORB_PICKUP
- volume: 1
- pitch: 1
- # Played when a head is added to the favorites list
- addFavorite:
- name: ENTITY_EXPERIENCE_ORB_PICKUP
- volume: 1
- pitch: 1
- # Played when a head is removed to from favorites list
- removeFavorite:
- name: BLOCK_ANVIL_BREAK
- volume: 1
- pitch: 1
# Command Configuration
commands:
# Commands to run ONLY if the purchase is successful.
+ # They are run as CONSOLE after the player has receiver the head in their inventory.
purchase:
- ""
-# The head provider used.
-# Don't touch this if you don't know what you are doing!
-headProvider: "HEAD_STORAGE"
-
# If the original fetching fails and this is enabled,
# the plugin will attempt to fetch the heads from an archive.
# The archive is static (manually updated) so some heads may be missing, this will only be used when all else fails.
diff --git a/src/main/resources/messages/en.yml b/src/main/resources/messages/en.yml
index bc874a0..abb6bf3 100644
--- a/src/main/resources/messages/en.yml
+++ b/src/main/resources/messages/en.yml
@@ -2,14 +2,17 @@ noConsole: "&cOnly for in-game players!"
noPermission: "&cNo permission!"
invalidSubCommand: "&cInvalid sub-command! Run &e/hdb help&c for help."
invalidArguments: "&cInvalid Arguments! Use &e/hdb help&c for help."
+invalidTarget: "&cInvalid target: &e%name%"
invalidCategory: "&cInvalid Category!"
+invalidNumber: "&e%name% &cis not a number!"
invalidPageIndex: "&cThat page is out of bounds! Max: %pages%"
-openDatabase: "&7Opening &cHeadDatabase&7..."
+openDatabase: "" # Intentionally empty. Sent when the main gui is opened
updateDatabase: "&7Updating..."
-updateDatabaseDone: "&7Database was updated! Heads: &6%size% &7| Took: &a%time%"
searchCommand: "&7Searching for heads matching: &e%query%"
searchCommandResults: "&7Found &e%size% &7matches!"
+giveCommand: "&7Gave &ex%size% %name% &7to &e%receiver%"
+giveCommandInvalid: "&cInvalid head: &e%name%"
itemTexture: "&7Texture: %texture%"
itemNoTexture: "&cThis item does not have a texture!"
copyTexture: "&6Click to copy texture!"
@@ -22,14 +25,21 @@ menu:
title: "&cHeadDB &7(%size%)"
category:
name: "&e&l%category%"
+ page:
+ name: "&eGo to specific page"
+ lore:
+ - "&7Left-Click to open"
+ - "&7Right-Click to open specific page"
search:
name: "&eSearch"
category:
- name: "&e%category% &7(%size%)"
+ name: "&cHeadDB &7- &e%category% &7(%size%)"
head:
name: "&e%name%"
search:
name: "&cHeadDB &7- &eSearch: %query% &7(%size%)"
+ page:
+ name: "&cHeadDB &7- &eEnter page number"
settings:
name: "&cHeadDB &7- &eSettings"
language:
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
index 690c33e..ae8c845 100644
--- a/src/main/resources/plugin.yml
+++ b/src/main/resources/plugin.yml
@@ -8,18 +8,16 @@ api-version: 1.19
author: TheSilentPro (Silent)
spigot-id: 84967
-build: ${build.number}
-buildRevision: ${build.revision}
-buildBranch: ${build.branch}
buildTimestamp: ${build.timestamp}
buildAuthor: ${build.author}
-libraries:
- - "com.google.code.gson:gson:2.10"
+# Although not up-to-date, spigot already includes gson. This is here just in case.
+#libraries:
+# - "com.google.code.gson:gson:2.10"
commands:
headdb:
- usage: /headdb
+ usage: /headdb help
description: Open the database
aliases: [hdb, headdatabase, headmenu]
@@ -27,27 +25,24 @@ permissions:
headdb.admin:
default: op
children:
- headdb.open: true
- headdb.search: true
- headdb.give: true
+ headdb.command.open: true
+ headdb.command.search: true
+ headdb.command.give: true
+ headdb.command.update: true
headdb.favorites: true
headdb.local: true
- headdb.tagsearch: true
- headdb.update: true
- headdb.reload: true
- headdb.open:
+ headdb.category.*: true
+ headdb.command.open:
default: op
- headdb.search:
+ headdb.command.search:
default: op
- headdb.give:
+ headdb.command.give:
+ default: op
+ headdb.command.update:
default: op
headdb.favorites:
default: op
headdb.local:
default: op
- headdb.tagsearch:
- default: op
- headdb.update:
- default: op
- headdb.reload:
+ headdb.category.*:
default: op
\ No newline at end of file