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