diff --git a/pom.xml b/pom.xml index fcbdbe0..f468c53 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ tsp.headdb HeadDB - 3.3.0 + 4.0.0 jar HeadDB @@ -63,6 +63,13 @@ 1.7 provided + + + me.lokka30 + treasury-api + 1.1.0 + provided + diff --git a/src/main/java/tsp/headdb/HeadDB.java b/src/main/java/tsp/headdb/HeadDB.java index 396ea0b..7cd0a45 100644 --- a/src/main/java/tsp/headdb/HeadDB.java +++ b/src/main/java/tsp/headdb/HeadDB.java @@ -3,9 +3,10 @@ package tsp.headdb; import org.bukkit.Bukkit; import org.bukkit.plugin.java.JavaPlugin; import tsp.headdb.api.HeadAPI; -import tsp.headdb.command.CommandHeadDB; -import tsp.headdb.database.DatabaseUpdateTask; -import tsp.headdb.economy.HEconomyProvider; +import tsp.headdb.command.HeadDBCommand; +import tsp.headdb.economy.TreasuryProvider; +import tsp.headdb.implementation.DatabaseUpdateTask; +import tsp.headdb.economy.BasicEconomyProvider; import tsp.headdb.economy.VaultProvider; import tsp.headdb.listener.JoinListener; import tsp.headdb.listener.MenuListener; @@ -22,7 +23,7 @@ import java.io.File; public class HeadDB extends JavaPlugin { private static HeadDB instance; - private HEconomyProvider economyProvider; + private BasicEconomyProvider economyProvider; private PlayerDataFile playerData; private Localization localization; @@ -42,11 +43,14 @@ public class HeadDB extends JavaPlugin { if (rawProvider.equalsIgnoreCase("vault")) { economyProvider = new VaultProvider(); economyProvider.initProvider(); + } else if (rawProvider.equalsIgnoreCase("treasury")) { + economyProvider = new TreasuryProvider(); + economyProvider.initProvider(); } } long refresh = getConfig().getLong("refresh") * 20; - HeadAPI.getDatabase().updateAsync(heads -> Log.info("Fetched " + HeadAPI.getHeads().size() + " heads!")); // Update database on startup + HeadAPI.getDatabase().update(heads -> Log.info("Fetched " + HeadAPI.getHeads().size() + " heads!")); // Update database on startup Bukkit.getScheduler().runTaskTimerAsynchronously(this, new DatabaseUpdateTask(), refresh, refresh); // Update database on set interval (also saves data) HeadAPI.getDatabase().setRefresh(refresh); @@ -54,7 +58,7 @@ public class HeadDB extends JavaPlugin { new MenuListener(this); new PagedPaneListener(this); - getCommand("headdb").setExecutor(new CommandHeadDB()); + getCommand("headdb").setExecutor(new HeadDBCommand()); Log.debug("Starting metrics..."); new Metrics(this, 9152); @@ -83,7 +87,7 @@ public class HeadDB extends JavaPlugin { } @Nullable - public HEconomyProvider getEconomyProvider() { + public BasicEconomyProvider getEconomyProvider() { return economyProvider; } diff --git a/src/main/java/tsp/headdb/api/HeadAPI.java b/src/main/java/tsp/headdb/api/HeadAPI.java index 1ee8736..80a6b53 100644 --- a/src/main/java/tsp/headdb/api/HeadAPI.java +++ b/src/main/java/tsp/headdb/api/HeadAPI.java @@ -4,8 +4,10 @@ import org.bukkit.Bukkit; import org.bukkit.OfflinePlayer; import org.bukkit.entity.Player; import tsp.headdb.HeadDB; -import tsp.headdb.database.Category; -import tsp.headdb.database.HeadDatabase; +import tsp.headdb.implementation.Category; +import tsp.headdb.implementation.Head; +import tsp.headdb.implementation.HeadDatabase; +import tsp.headdb.implementation.LocalHead; import tsp.headdb.inventory.InventoryUtils; import tsp.headdb.storage.PlayerDataFile; @@ -14,6 +16,7 @@ import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; /** * This class provides simple methods @@ -21,7 +24,6 @@ import java.util.UUID; * * @author TheSilentPro */ -// TODO: Possibly change to singleton class public final class HeadAPI { private HeadAPI() {} @@ -195,14 +197,9 @@ public final class HeadAPI { */ @Nonnull public static List getFavoriteHeads(UUID uuid) { - List result = new ArrayList<>(); - - List textures = HeadDB.getInstance().getPlayerData().getFavoriteHeadsByTexture(uuid); - for (String texture : textures) { - result.add(HeadAPI.getHeadByValue(texture)); - } - - return result; + return HeadDB.getInstance().getPlayerData().getFavoriteHeadsByTexture(uuid).stream() + .map(HeadAPI::getHeadByValue) + .collect(Collectors.toList()); } /** @@ -214,13 +211,10 @@ public final class HeadAPI { */ @Nonnull public static List getLocalHeads() { - List result = new ArrayList<>(); - for (String entry : HeadDB.getInstance().getPlayerData().getEntries()) { - OfflinePlayer player = Bukkit.getOfflinePlayer(UUID.fromString(entry)); - result.add(new LocalHead(player.getUniqueId()).name(player.getName())); - } - - return result; + return HeadDB.getInstance().getPlayerData().getEntries().stream() + .map(entry -> Bukkit.getOfflinePlayer(UUID.fromString(entry))) + .map(player -> new LocalHead(player.getUniqueId()).name(player.getName())) + .collect(Collectors.toList()); } } diff --git a/src/main/java/tsp/headdb/event/DatabaseUpdateEvent.java b/src/main/java/tsp/headdb/api/event/DatabaseUpdateEvent.java similarity index 82% rename from src/main/java/tsp/headdb/event/DatabaseUpdateEvent.java rename to src/main/java/tsp/headdb/api/event/DatabaseUpdateEvent.java index b25b02f..f46f54f 100644 --- a/src/main/java/tsp/headdb/event/DatabaseUpdateEvent.java +++ b/src/main/java/tsp/headdb/api/event/DatabaseUpdateEvent.java @@ -1,16 +1,16 @@ -package tsp.headdb.event; +package tsp.headdb.api.event; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; -import tsp.headdb.api.Head; -import tsp.headdb.database.Category; -import tsp.headdb.database.HeadDatabase; +import tsp.headdb.implementation.Head; +import tsp.headdb.implementation.Category; +import tsp.headdb.implementation.HeadDatabase; import java.util.List; import java.util.Map; /** - * This event is called AFTER a {@link tsp.headdb.database.HeadDatabase} updates. + * This event is called AFTER a {@link HeadDatabase} updates. * The event is called asynchronously and can not be cancelled. * * @author TheSilentPro diff --git a/src/main/java/tsp/headdb/event/PlayerHeadPurchaseEvent.java b/src/main/java/tsp/headdb/api/event/PlayerHeadPurchaseEvent.java similarity index 95% rename from src/main/java/tsp/headdb/event/PlayerHeadPurchaseEvent.java rename to src/main/java/tsp/headdb/api/event/PlayerHeadPurchaseEvent.java index 0feaf18..55cbbc5 100644 --- a/src/main/java/tsp/headdb/event/PlayerHeadPurchaseEvent.java +++ b/src/main/java/tsp/headdb/api/event/PlayerHeadPurchaseEvent.java @@ -1,10 +1,10 @@ -package tsp.headdb.event; +package tsp.headdb.api.event; import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; -import tsp.headdb.api.Head; +import tsp.headdb.implementation.Head; /** * This event is called when a player purchases a {@link Head} diff --git a/src/main/java/tsp/headdb/command/CommandHeadDB.java b/src/main/java/tsp/headdb/command/CommandHeadDB.java deleted file mode 100644 index 43f70e7..0000000 --- a/src/main/java/tsp/headdb/command/CommandHeadDB.java +++ /dev/null @@ -1,175 +0,0 @@ -package tsp.headdb.command; - -import org.bukkit.Bukkit; -import org.bukkit.command.Command; -import org.bukkit.command.CommandExecutor; -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import tsp.headdb.HeadDB; -import tsp.headdb.api.Head; -import tsp.headdb.api.HeadAPI; -import tsp.headdb.util.Localization; -import tsp.headdb.util.Utils; - -import java.util.concurrent.TimeUnit; - -// TODO: Cleaner way to handle this command -public class CommandHeadDB implements CommandExecutor { - - @Override - public boolean onCommand(CommandSender sender, Command command, String s, String[] args) { - Localization localization = HeadDB.getInstance().getLocalization(); - if (args.length == 0) { - if (!sender.hasPermission("headdb.open")) { - Utils.sendMessage(sender, localization.getMessage("noPermission")); - return true; - } - if (!(sender instanceof Player)) { - Utils.sendMessage(sender, localization.getMessage("onlyPlayers")); - return true; - } - Player player = (Player) sender; - - Utils.sendMessage(player, localization.getMessage("databaseOpen")); - HeadAPI.openDatabase(player); - return true; - } - String sub = args[0]; - - if (sub.equalsIgnoreCase("info") || sub.equalsIgnoreCase("i")) { - // These should not be customizable - Utils.sendMessage(sender, "&7Running &cHeadDB - " + HeadDB.getInstance().getDescription().getVersion()); - Utils.sendMessage(sender, "&7Created by &c" + HeadDB.getInstance().getDescription().getAuthors()); - Utils.sendMessage(sender, "&7There are currently &c" + HeadAPI.getHeads().size() + " &7heads in the database."); - return true; - } - - if (sub.equalsIgnoreCase("reload") || sub.equalsIgnoreCase("r")) { - if (!sender.hasPermission("headdb.reload")) { - Utils.sendMessage(sender, localization.getMessage("noPermission")); - return true; - } - HeadDB.getInstance().getLocalization().load(); - Utils.sendMessage(sender, localization.getMessage("reloadMessages")); - return true; - } - - if (sub.equalsIgnoreCase("search") || sub.equalsIgnoreCase("s")) { - if (!sender.hasPermission("headdb.search")) { - Utils.sendMessage(sender, localization.getMessage("noPermission")); - return true; - } - if (!(sender instanceof Player)) { - Utils.sendMessage(sender, localization.getMessage("onlyPlayers")); - return true; - } - Player player = (Player) sender; - - if (args.length < 2) { - Utils.sendMessage(player, "&c/hdb search "); - return true; - } - - StringBuilder builder = new StringBuilder(); - for (int i = 1; i < args.length; i++) { - builder.append(args[i]); - if (i != args.length - 1) { - builder.append(" "); - } - } - String name = builder.toString(); - Utils.sendMessage(sender, "&7Searching for &e" + name); - HeadAPI.openSearchDatabase(player, name); - return true; - } - - if (sub.equalsIgnoreCase("tagsearch") || sub.equalsIgnoreCase("ts")) { - if (!sender.hasPermission("headdb.tagsearch")) { - Utils.sendMessage(sender, localization.getMessage("noPermission")); - return true; - } - if (args.length < 2) { - Utils.sendMessage(sender, "&c/hdb tagsearch "); - return true; - } - if (!(sender instanceof Player)) { - Utils.sendMessage(sender, localization.getMessage("onlyPlayers")); - return true; - } - Player player = (Player) sender; - - String tag = args[1]; - Utils.sendMessage(sender, "&7Searching for heads with tag &e" + tag); - HeadAPI.openTagSearchDatabase(player, tag); - return true; - } - - if (sub.equalsIgnoreCase("give") || sub.equalsIgnoreCase("g")) { - if (!sender.hasPermission("headdb.give")) { - Utils.sendMessage(sender, localization.getMessage("noPermission")); - return true; - } - if (args.length < 3) { - Utils.sendMessage(sender, "&c/hdb give &6[amount]"); - return true; - } - try { - int id = Integer.parseInt(args[1]); - Player target = Bukkit.getPlayer(args[2]); - if (target == null) { - Utils.sendMessage(sender, localization.getMessage("invalidPlayer")); - return true; - } - - int amount = 1; - if (args.length > 3) { - amount = Integer.parseInt(args[3]); - } - - Head head = HeadAPI.getHeadByID(id); - if (head == null) { - Utils.sendMessage(sender, "&cCould not find head with id &e" + id); - return true; - } - ItemStack item = head.getMenuItem(); - item.setAmount(amount); - target.getInventory().addItem(item); - Utils.sendMessage(sender, "&7Gave &6" + target.getName() + " &ex" + amount + " " + head.getName()); - return true; - } catch (NumberFormatException nfe) { - Utils.sendMessage(sender, "&cID/Amount must be a number!"); - return true; - } - } - - if (sub.equalsIgnoreCase("update") || sub.equalsIgnoreCase("u")) { - if (!sender.hasPermission("headdb.update")) { - Utils.sendMessage(sender, "&cNo permission!"); - return true; - } - - Utils.sendMessage(sender, "&7Updating..."); - long start = System.currentTimeMillis(); - HeadAPI.getDatabase().updateAsync(heads -> { - Utils.sendMessage(sender, "&7Done! Took: &a" + TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - start) + " &7seconds"); - Utils.sendMessage(sender, "&7There are &a" + HeadAPI.getHeads().size() + " &7heads in the database!"); - }); - return true; - } - - Utils.sendMessage(sender, " "); - Utils.sendMessage(sender, "&c&lHeadDB &c- &5Commands"); - Utils.sendMessage(sender, "&7&oParameters:&c command &9(aliases)&c arguments... &7- Description"); - Utils.sendMessage(sender, "&7 > &c/hdb &7- Opens the database"); - Utils.sendMessage(sender, "&7 > &c/hdb info &9(i) &7- Plugin Information"); - Utils.sendMessage(sender, "&7 > &c/hdb reload &9(r) &7- Reloads the messages file"); - Utils.sendMessage(sender, "&7 > &c/hdb search &9(s) &c &7- Search for heads matching a name"); - Utils.sendMessage(sender, "&7 > &c/hdb tagsearch &9(ts) &c &7- Search for heads matching a tag"); - Utils.sendMessage(sender, "&7 > &c/hdb update &9(u) &7- Forcefully update the database"); - Utils.sendMessage(sender, "&7 > &c/hdb give &9(g) &c &6[amount] &7- Give player a head"); - Utils.sendMessage(sender, " "); - return true; - } - -} \ No newline at end of file diff --git a/src/main/java/tsp/headdb/command/GiveCommand.java b/src/main/java/tsp/headdb/command/GiveCommand.java new file mode 100644 index 0000000..99cd4f8 --- /dev/null +++ b/src/main/java/tsp/headdb/command/GiveCommand.java @@ -0,0 +1,53 @@ +package tsp.headdb.command; + +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import tsp.headdb.api.HeadAPI; +import tsp.headdb.implementation.Head; +import tsp.headdb.util.Utils; + +public class GiveCommand implements HeadSubCommand { + + @Override + public void handle(CommandSender sender, String[] args) { + if (!sender.hasPermission("headdb.give")) { + Utils.sendMessage(sender, getLocalization().getMessage("noPermission")); + return; + } + + if (args.length < 3) { + Utils.sendMessage(sender, "&c/hdb give &6[amount]"); + return; + } + + try { + int id = Integer.parseInt(args[1]); + Player target = Bukkit.getPlayer(args[2]); + if (target == null) { + Utils.sendMessage(sender, getLocalization().getMessage("invalidPlayer")); + return; + } + + int amount = 1; + if (args.length > 3) { + amount = Integer.parseInt(args[3]); + } + + Head head = HeadAPI.getHeadByID(id); + if (head == null) { + Utils.sendMessage(sender, "&cCould not find head with id &e" + id); + return; + } + + ItemStack item = head.getMenuItem(); + item.setAmount(amount); + target.getInventory().addItem(item); + Utils.sendMessage(sender, "&7Gave &6" + target.getName() + " &ex" + amount + " " + head.getName()); + } catch (NumberFormatException nfe) { + Utils.sendMessage(sender, "&cID/Amount must be a number!"); + } + } + +} diff --git a/src/main/java/tsp/headdb/command/HeadDBCommand.java b/src/main/java/tsp/headdb/command/HeadDBCommand.java new file mode 100644 index 0000000..e9f7516 --- /dev/null +++ b/src/main/java/tsp/headdb/command/HeadDBCommand.java @@ -0,0 +1,68 @@ +package tsp.headdb.command; + +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import tsp.headdb.HeadDB; +import tsp.headdb.api.HeadAPI; +import tsp.headdb.util.Localization; +import tsp.headdb.util.Utils; + +public class HeadDBCommand implements CommandExecutor { + + @Override + public boolean onCommand(CommandSender sender, Command command, String s, String[] args) { + Localization localization = HeadDB.getInstance().getLocalization(); + if (args.length == 0) { + if (!sender.hasPermission("headdb.open")) { + Utils.sendMessage(sender, localization.getMessage("noPermission")); + return true; + } + if (!(sender instanceof Player)) { + Utils.sendMessage(sender, localization.getMessage("onlyPlayers")); + return true; + } + Player player = (Player) sender; + + Utils.sendMessage(player, localization.getMessage("databaseOpen")); + HeadAPI.openDatabase(player); + return true; + } + + String sub = args[0]; + HeadSubCommand subCommand = null; + if (sub.equalsIgnoreCase("info") || sub.equalsIgnoreCase("i")) { + subCommand = new InfoCommand(); + } else if (sub.equalsIgnoreCase("reload") || sub.equalsIgnoreCase("r")) { + subCommand = new ReloadCommand(); + } else if (sub.equalsIgnoreCase("search") || sub.equalsIgnoreCase("s")) { + subCommand = new SearchCommand(); + } else if (sub.equalsIgnoreCase("tagsearch") || sub.equalsIgnoreCase("ts")) { + subCommand = new TagSearchCommand(); + } else if (sub.equalsIgnoreCase("give") || sub.equalsIgnoreCase("g")) { + subCommand = new GiveCommand(); + } else if (sub.equalsIgnoreCase("update") || sub.equalsIgnoreCase("u")) { + subCommand = new UpdateCommand(); + } + + if (subCommand != null) { + subCommand.handle(sender, args); + return true; + } + + Utils.sendMessage(sender, " "); + Utils.sendMessage(sender, "&c&lHeadDB &c- &5Commands"); + Utils.sendMessage(sender, "&7&oParameters:&c command &9(aliases)&c arguments... &7- Description"); + Utils.sendMessage(sender, "&7 > &c/hdb &7- Opens the database"); + Utils.sendMessage(sender, "&7 > &c/hdb info &9(i) &7- Plugin Information"); + Utils.sendMessage(sender, "&7 > &c/hdb reload &9(r) &7- Reloads the messages file"); + Utils.sendMessage(sender, "&7 > &c/hdb search &9(s) &c &7- Search for heads matching a name"); + Utils.sendMessage(sender, "&7 > &c/hdb tagsearch &9(ts) &c &7- Search for heads matching a tag"); + Utils.sendMessage(sender, "&7 > &c/hdb update &9(u) &7- Forcefully update the database"); + Utils.sendMessage(sender, "&7 > &c/hdb give &9(g) &c &6[amount] &7- Give player a head"); + Utils.sendMessage(sender, " "); + return true; + } + +} \ No newline at end of file diff --git a/src/main/java/tsp/headdb/command/HeadSubCommand.java b/src/main/java/tsp/headdb/command/HeadSubCommand.java new file mode 100644 index 0000000..020f84f --- /dev/null +++ b/src/main/java/tsp/headdb/command/HeadSubCommand.java @@ -0,0 +1,21 @@ +package tsp.headdb.command; + +import org.bukkit.command.CommandSender; +import tsp.headdb.HeadDB; +import tsp.headdb.util.Localization; + +/** + * An interface for a HeadDB sub-command + * + * @author TheSilentPro + * @since 4.0.0 + */ +public interface HeadSubCommand { + + void handle(CommandSender sender, String[] args); + + default Localization getLocalization() { + return HeadDB.getInstance().getLocalization(); + } + +} diff --git a/src/main/java/tsp/headdb/command/InfoCommand.java b/src/main/java/tsp/headdb/command/InfoCommand.java new file mode 100644 index 0000000..18a5694 --- /dev/null +++ b/src/main/java/tsp/headdb/command/InfoCommand.java @@ -0,0 +1,18 @@ +package tsp.headdb.command; + +import org.bukkit.command.CommandSender; +import tsp.headdb.HeadDB; +import tsp.headdb.api.HeadAPI; +import tsp.headdb.util.Utils; + +public class InfoCommand implements HeadSubCommand { + + @Override + public void handle(CommandSender sender, String[] args) { + // These should not be customizable + Utils.sendMessage(sender, "&7Running &cHeadDB - " + HeadDB.getInstance().getDescription().getVersion()); + Utils.sendMessage(sender, "&7Created by &c" + HeadDB.getInstance().getDescription().getAuthors()); + Utils.sendMessage(sender, "&7There are currently &c" + HeadAPI.getHeads().size() + " &7heads in the database."); + } + +} diff --git a/src/main/java/tsp/headdb/command/ReloadCommand.java b/src/main/java/tsp/headdb/command/ReloadCommand.java new file mode 100644 index 0000000..0665d6a --- /dev/null +++ b/src/main/java/tsp/headdb/command/ReloadCommand.java @@ -0,0 +1,20 @@ +package tsp.headdb.command; + +import org.bukkit.command.CommandSender; +import tsp.headdb.HeadDB; +import tsp.headdb.util.Utils; + +public class ReloadCommand implements HeadSubCommand { + + @Override + public void handle(CommandSender sender, String[] args) { + if (!sender.hasPermission("headdb.reload")) { + Utils.sendMessage(sender, getLocalization().getMessage("noPermission")); + return; + } + + HeadDB.getInstance().getLocalization().load(); + Utils.sendMessage(sender, getLocalization().getMessage("reloadMessages")); + } + +} diff --git a/src/main/java/tsp/headdb/command/SearchCommand.java b/src/main/java/tsp/headdb/command/SearchCommand.java new file mode 100644 index 0000000..e709b93 --- /dev/null +++ b/src/main/java/tsp/headdb/command/SearchCommand.java @@ -0,0 +1,40 @@ +package tsp.headdb.command; + +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import tsp.headdb.api.HeadAPI; +import tsp.headdb.util.Utils; + +public class SearchCommand implements HeadSubCommand { + + @Override + public void handle(CommandSender sender, String[] args) { + if (!sender.hasPermission("headdb.search")) { + Utils.sendMessage(sender, getLocalization().getMessage("noPermission")); + return; + } + if (!(sender instanceof Player)) { + Utils.sendMessage(sender, getLocalization().getMessage("onlyPlayers")); + return; + } + Player player = (Player) sender; + + if (args.length < 2) { + Utils.sendMessage(player, "&c/hdb search "); + return; + } + + StringBuilder builder = new StringBuilder(); + for (int i = 1; i < args.length; i++) { + builder.append(args[i]); + if (i != args.length - 1) { + builder.append(" "); + } + } + String name = builder.toString(); + Utils.sendMessage(sender, "&7Searching for &e" + name); + HeadAPI.openSearchDatabase(player, name); + return; + } + +} diff --git a/src/main/java/tsp/headdb/command/TagSearchCommand.java b/src/main/java/tsp/headdb/command/TagSearchCommand.java new file mode 100644 index 0000000..664c409 --- /dev/null +++ b/src/main/java/tsp/headdb/command/TagSearchCommand.java @@ -0,0 +1,34 @@ +package tsp.headdb.command; + +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import tsp.headdb.api.HeadAPI; +import tsp.headdb.util.Utils; + +public class TagSearchCommand implements HeadSubCommand { + + @Override + public void handle(CommandSender sender, String[] args) { + if (!sender.hasPermission("headdb.tagsearch")) { + Utils.sendMessage(sender, getLocalization().getMessage("noPermission")); + return; + } + + if (args.length < 2) { + Utils.sendMessage(sender, "&c/hdb tagsearch "); + return; + } + + if (!(sender instanceof Player)) { + Utils.sendMessage(sender, getLocalization().getMessage("onlyPlayers")); + return; + } + + Player player = (Player) sender; + String tag = args[1]; + Utils.sendMessage(sender, "&7Searching for heads with tag &e" + tag); + HeadAPI.openTagSearchDatabase(player, tag); + return; + } + +} diff --git a/src/main/java/tsp/headdb/command/UpdateCommand.java b/src/main/java/tsp/headdb/command/UpdateCommand.java new file mode 100644 index 0000000..d3765ff --- /dev/null +++ b/src/main/java/tsp/headdb/command/UpdateCommand.java @@ -0,0 +1,26 @@ +package tsp.headdb.command; + +import org.bukkit.command.CommandSender; +import tsp.headdb.api.HeadAPI; +import tsp.headdb.util.Utils; + +import java.util.concurrent.TimeUnit; + +public class UpdateCommand implements HeadSubCommand { + + @Override + public void handle(CommandSender sender, String[] args) { + if (!sender.hasPermission("headdb.update")) { + Utils.sendMessage(sender, getLocalization().getMessage("noPermission")); + return; + } + + Utils.sendMessage(sender, "&7Updating..."); + long start = System.currentTimeMillis(); + HeadAPI.getDatabase().update(heads -> { + Utils.sendMessage(sender, "&7Done! Took: &a" + TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - start) + " &7seconds"); + Utils.sendMessage(sender, "&7There are &a" + HeadAPI.getHeads().size() + " &7heads in the database!"); + }); + } + +} diff --git a/src/main/java/tsp/headdb/economy/BasicEconomyProvider.java b/src/main/java/tsp/headdb/economy/BasicEconomyProvider.java new file mode 100644 index 0000000..f1b3e83 --- /dev/null +++ b/src/main/java/tsp/headdb/economy/BasicEconomyProvider.java @@ -0,0 +1,48 @@ +package tsp.headdb.economy; + +import org.bukkit.entity.Player; + +import java.math.BigDecimal; +import java.util.function.Consumer; + +/** + * An interface for generalizing Economy Provider's + * + * @author TheSilentPro + * @since 4.0.0 + * @see VaultProvider + * @see TreasuryProvider + */ +public interface BasicEconomyProvider { + + /** + * Retrieve if the player can purchase a head using this economy provider + * + * @param player The player + * @param cost The cost + * @param result If the player has enough to purchase + */ + default void canPurchase(Player player, BigDecimal cost, Consumer result) { + result.accept(true); + } + + /** + * Charge the player a specific amount using this economy provider + * + * @param player The player + * @param amount The amount + * @param result If the transaction was successful + */ + default void charge(Player player, BigDecimal amount, Consumer result) { + result.accept(true); + } + + /** + * Convenience method for initializing economy + * + * @see VaultProvider#initProvider() + * @see TreasuryProvider#initProvider() + */ + void initProvider(); + +} diff --git a/src/main/java/tsp/headdb/economy/HEconomyProvider.java b/src/main/java/tsp/headdb/economy/HEconomyProvider.java deleted file mode 100644 index aba0192..0000000 --- a/src/main/java/tsp/headdb/economy/HEconomyProvider.java +++ /dev/null @@ -1,21 +0,0 @@ -package tsp.headdb.economy; - -import org.bukkit.entity.Player; - -import java.math.BigDecimal; - -public interface HEconomyProvider { - - default boolean canPurchase(Player player, BigDecimal cost) { - return true; - } - - default void charge(Player player, BigDecimal amount) { - - } - - default void initProvider() { - - } - -} diff --git a/src/main/java/tsp/headdb/economy/TreasuryProvider.java b/src/main/java/tsp/headdb/economy/TreasuryProvider.java new file mode 100644 index 0000000..1db6b9e --- /dev/null +++ b/src/main/java/tsp/headdb/economy/TreasuryProvider.java @@ -0,0 +1,99 @@ +package tsp.headdb.economy; + +import me.lokka30.treasury.api.common.service.Service; +import me.lokka30.treasury.api.common.service.ServiceRegistry; +import me.lokka30.treasury.api.economy.EconomyProvider; +import me.lokka30.treasury.api.economy.account.PlayerAccount; +import me.lokka30.treasury.api.economy.currency.Currency; +import me.lokka30.treasury.api.economy.response.EconomyException; +import me.lokka30.treasury.api.economy.response.EconomySubscriber; +import me.lokka30.treasury.api.economy.transaction.EconomyTransactionInitiator; +import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import tsp.headdb.HeadDB; +import tsp.headdb.util.Log; + +import java.math.BigDecimal; +import java.util.Optional; +import java.util.function.Consumer; + +/** + * A {@link BasicEconomyProvider} for Treasury + * + * @author TheSilentPro + * @since 4.0.0 + */ +public class TreasuryProvider implements BasicEconomyProvider { + + private EconomyProvider provider; + private Currency currency; + + @Override + public void canPurchase(Player player, BigDecimal cost, Consumer result) { + EconomySubscriber + .asFuture(s -> provider.hasPlayerAccount(player.getUniqueId(), s)) + .thenCompose(val -> { + if (val) { + return EconomySubscriber.asFuture(s -> provider.retrievePlayerAccount(player.getUniqueId(), s)); + } else { + return EconomySubscriber.asFuture(s -> provider.createPlayerAccount(player.getUniqueId(), s)); + } + }) + .thenCompose(account -> EconomySubscriber.asFuture(s -> account.retrieveBalance(currency, s))) + .whenComplete((bal, ex) -> { + result.accept(bal.compareTo(cost) >= 0); + }); + } + + @Override + public void charge(Player player, BigDecimal amount, Consumer result) { + EconomySubscriber + .asFuture(s -> provider.hasPlayerAccount(player.getUniqueId(), s)) + .thenCompose(val -> { + if (val) { + return EconomySubscriber.asFuture(s -> provider.retrievePlayerAccount(player.getUniqueId(), s)); + } else { + return EconomySubscriber.asFuture(s -> provider.createPlayerAccount(player.getUniqueId(), s)); + } + }).whenComplete((account, ex) -> { + account.withdrawBalance( + amount, + EconomyTransactionInitiator.createInitiator(EconomyTransactionInitiator.Type.PLUGIN, "HeadDB"), + currency, + new EconomySubscriber() { + @Override + public void succeed(@NotNull BigDecimal bigDecimal) { + result.accept(true); + } + + @Override + public void fail(@NotNull EconomyException exception) { + result.accept(false); + exception.printStackTrace(); + } + }); + }); + } + + @Override + public void initProvider() { + Optional> service = ServiceRegistry.INSTANCE.serviceFor(EconomyProvider.class); + + if(!service.isPresent()) { + Log.error("Unable to find a supported economy plugin for Treasury!"); + return; + } + + provider = service.get().get(); + + String rawCurrency = HeadDB.getInstance().getConfig().getString("economy.currency"); + if (rawCurrency == null || rawCurrency.isEmpty()) { + currency = provider.getPrimaryCurrency(); + } else { + currency = provider.getCurrencies().stream() + .filter(currency -> currency.getIdentifier().equalsIgnoreCase(rawCurrency)) + .findFirst().get(); + } + } + +} diff --git a/src/main/java/tsp/headdb/economy/VaultProvider.java b/src/main/java/tsp/headdb/economy/VaultProvider.java index c5c19f1..1b7d6b6 100644 --- a/src/main/java/tsp/headdb/economy/VaultProvider.java +++ b/src/main/java/tsp/headdb/economy/VaultProvider.java @@ -5,21 +5,29 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.plugin.RegisteredServiceProvider; import tsp.headdb.util.Log; +import tsp.headdb.util.Utils; import java.math.BigDecimal; +import java.util.function.Consumer; -public class VaultProvider implements HEconomyProvider { +/** + * A {@link BasicEconomyProvider} for Vault + * + * @author TheSilentPro + * @since 4.0.0 + */ +public class VaultProvider implements BasicEconomyProvider { private Economy economy; @Override - public boolean canPurchase(Player player, BigDecimal cost) { - return economy.has(player, cost.doubleValue()); + public void canPurchase(Player player, BigDecimal cost, Consumer result) { + Utils.async(t -> result.accept(economy.has(player, cost.doubleValue()))); } @Override - public void charge(Player player, BigDecimal amount) { - economy.withdrawPlayer(player, amount.doubleValue()); + public void charge(Player player, BigDecimal amount, Consumer result) { + Utils.async(t -> result.accept(economy.withdrawPlayer(player, amount.doubleValue()).transactionSuccess())); } public void initProvider() { diff --git a/src/main/java/tsp/headdb/database/Category.java b/src/main/java/tsp/headdb/implementation/Category.java similarity index 75% rename from src/main/java/tsp/headdb/database/Category.java rename to src/main/java/tsp/headdb/implementation/Category.java index 0257c03..0a3f4a1 100644 --- a/src/main/java/tsp/headdb/database/Category.java +++ b/src/main/java/tsp/headdb/implementation/Category.java @@ -1,10 +1,10 @@ -package tsp.headdb.database; +package tsp.headdb.implementation; import org.bukkit.ChatColor; import org.bukkit.inventory.ItemStack; -import tsp.headdb.api.Head; import tsp.headdb.api.HeadAPI; +import javax.annotation.Nullable; import java.util.HashMap; import java.util.Map; @@ -45,15 +45,24 @@ public enum Category { return location; } + /** + * Retrieve the first valid head from a category + * + * @return First valid head + */ public ItemStack getItem() { - if (item.containsKey(this)) { - return item.get(this).getMenuItem(); - } - - item.put(this, HeadAPI.getHeads(this).get(0)); - return getItem(); + return HeadAPI.getHeads(this).stream() + .filter(head -> head != null) + .findFirst().get().getMenuItem(); } + /** + * Retrieve a {@link Category} by name + * + * @param name The name + * @return The category if it exists. Else it returns null + */ + @Nullable public static Category getByName(String name) { for (Category category : cache) { if (category.getName().equalsIgnoreCase(name)) { diff --git a/src/main/java/tsp/headdb/database/DatabaseUpdateTask.java b/src/main/java/tsp/headdb/implementation/DatabaseUpdateTask.java similarity index 62% rename from src/main/java/tsp/headdb/database/DatabaseUpdateTask.java rename to src/main/java/tsp/headdb/implementation/DatabaseUpdateTask.java index 44d75f5..21e00b4 100644 --- a/src/main/java/tsp/headdb/database/DatabaseUpdateTask.java +++ b/src/main/java/tsp/headdb/implementation/DatabaseUpdateTask.java @@ -1,4 +1,4 @@ -package tsp.headdb.database; +package tsp.headdb.implementation; import tsp.headdb.HeadDB; import tsp.headdb.api.HeadAPI; @@ -9,7 +9,7 @@ public class DatabaseUpdateTask implements Runnable { @Override public void run() { HeadDB.getInstance().getPlayerData().save(); - HeadAPI.getDatabase().updateAsync(heads -> Log.info("Fetched " + HeadAPI.getHeads().size() + " heads!")); + HeadAPI.getDatabase().update(heads -> Log.info("Fetched " + HeadAPI.getHeads().size() + " heads!")); } } diff --git a/src/main/java/tsp/headdb/api/Head.java b/src/main/java/tsp/headdb/implementation/Head.java similarity index 97% rename from src/main/java/tsp/headdb/api/Head.java rename to src/main/java/tsp/headdb/implementation/Head.java index c34eb48..19b0aa5 100644 --- a/src/main/java/tsp/headdb/api/Head.java +++ b/src/main/java/tsp/headdb/implementation/Head.java @@ -1,14 +1,11 @@ -package tsp.headdb.api; +package tsp.headdb.implementation; import com.mojang.authlib.GameProfile; import com.mojang.authlib.properties.Property; import org.apache.commons.lang.Validate; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.SkullMeta; -import tsp.headdb.HeadDB; -import tsp.headdb.database.Category; import tsp.headdb.util.Log; import tsp.headdb.util.Utils; diff --git a/src/main/java/tsp/headdb/database/HeadDatabase.java b/src/main/java/tsp/headdb/implementation/HeadDatabase.java similarity index 95% rename from src/main/java/tsp/headdb/database/HeadDatabase.java rename to src/main/java/tsp/headdb/implementation/HeadDatabase.java index 03b388f..630d0c1 100644 --- a/src/main/java/tsp/headdb/database/HeadDatabase.java +++ b/src/main/java/tsp/headdb/implementation/HeadDatabase.java @@ -1,4 +1,4 @@ -package tsp.headdb.database; +package tsp.headdb.implementation; import org.bukkit.Bukkit; import org.bukkit.ChatColor; @@ -7,8 +7,7 @@ import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; -import tsp.headdb.api.Head; -import tsp.headdb.event.DatabaseUpdateEvent; +import tsp.headdb.api.event.DatabaseUpdateEvent; import tsp.headdb.util.Log; import tsp.headdb.util.Utils; @@ -18,6 +17,7 @@ import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -117,7 +117,7 @@ public class HeadDatabase { } public List getHeads(Category category) { - return HEADS.get(category); + return Collections.unmodifiableList(HEADS.get(category)); } /** @@ -129,7 +129,7 @@ public class HeadDatabase { public List getHeads() { if (HEADS.isEmpty() || isLastUpdateOld()) { // Technically this should never be reached due to the update task in the main class. - updateAsync(result -> { + update(result -> { if (result != null) { for (Category category : result.keySet()) { HEADS.put(category, result.get(category)); @@ -145,7 +145,7 @@ public class HeadDatabase { return heads; } - public void getHeadsNoCacheAsync(Consumer>> resultSet) { + public void getHeadsNoCache(Consumer>> resultSet) { Bukkit.getScheduler().runTaskAsynchronously(plugin, task -> { Log.debug("[" + plugin.getName() + "] Updating database... "); Map> result = new HashMap<>(); @@ -207,8 +207,8 @@ public class HeadDatabase { }); } - public void updateAsync(Consumer>> result) { - Bukkit.getScheduler().runTaskAsynchronously(plugin, task -> getHeadsNoCacheAsync(heads -> { + public void update(Consumer>> result) { + Bukkit.getScheduler().runTaskAsynchronously(plugin, task -> getHeadsNoCache(heads -> { if (heads == null) { Log.error("[" + plugin.getName() + "] Failed to update database! Check above for any errors."); result.accept(null); diff --git a/src/main/java/tsp/headdb/api/LocalHead.java b/src/main/java/tsp/headdb/implementation/LocalHead.java similarity index 96% rename from src/main/java/tsp/headdb/api/LocalHead.java rename to src/main/java/tsp/headdb/implementation/LocalHead.java index e684903..0207b43 100644 --- a/src/main/java/tsp/headdb/api/LocalHead.java +++ b/src/main/java/tsp/headdb/implementation/LocalHead.java @@ -1,11 +1,10 @@ -package tsp.headdb.api; +package tsp.headdb.implementation; import org.apache.commons.lang.Validate; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.SkullMeta; -import tsp.headdb.database.Category; import tsp.headdb.util.Utils; import java.util.ArrayList; diff --git a/src/main/java/tsp/headdb/inventory/InventoryUtils.java b/src/main/java/tsp/headdb/inventory/InventoryUtils.java index 0a57437..93c042e 100644 --- a/src/main/java/tsp/headdb/inventory/InventoryUtils.java +++ b/src/main/java/tsp/headdb/inventory/InventoryUtils.java @@ -9,22 +9,21 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; import tsp.headdb.HeadDB; -import tsp.headdb.api.Head; +import tsp.headdb.implementation.Head; import tsp.headdb.api.HeadAPI; -import tsp.headdb.api.LocalHead; -import tsp.headdb.database.Category; -import tsp.headdb.economy.HEconomyProvider; -import tsp.headdb.event.PlayerHeadPurchaseEvent; +import tsp.headdb.implementation.LocalHead; +import tsp.headdb.implementation.Category; +import tsp.headdb.economy.BasicEconomyProvider; +import tsp.headdb.api.event.PlayerHeadPurchaseEvent; import tsp.headdb.util.Localization; import tsp.headdb.util.Utils; -import net.milkbowl.vault.economy.Economy; - import java.math.BigDecimal; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.Consumer; /** * Class for handling the "dirty" work @@ -308,49 +307,62 @@ public class InventoryUtils { return HeadDB.getInstance().getConfig().getDouble("economy.cost." + category); } - public static boolean processPayment(Player player, int amount, String category, String description) { - HEconomyProvider economyProvider = HeadDB.getInstance().getEconomyProvider(); + public static void processPayment(Player player, int amount, String category, String description, Consumer result) { + Utils.async(task -> { + BasicEconomyProvider economyProvider = HeadDB.getInstance().getEconomyProvider(); - // If economy is disabled or no plugin is present, the item is free. - // Don't mention receiving it for free in this case, since it is always free. - if (economyProvider == null) { - Utils.sendMessage(player, String.format(localization.getMessage("noEconomy"), amount, description)); - Utils.playSound(player, "noEconomy"); - return true; - } - - BigDecimal cost = BigDecimal.valueOf(getCategoryCost(player, category) * amount); - - // If the cost is higher than zero, attempt to charge for it. - if (cost.compareTo(BigDecimal.ZERO) > 0) { - if (economyProvider.canPurchase(player, cost)) { - economyProvider.charge(player, cost); - Utils.sendMessage(player, String.format(localization.getMessage("purchasedHead"), amount, description, cost)); - Utils.playSound(player, "paid"); - return true; + // If economy is disabled or no plugin is present, the item is free. + // Don't mention receiving it for free in this case, since it is always free. + if (economyProvider == null) { + Utils.sendMessage(player, String.format(localization.getMessage("noEconomy"), amount, description)); + Utils.playSound(player, "noEconomy"); + result.accept(true); + return; } - Utils.sendMessage(player, String.format(localization.getMessage("notEnoughMoney"), amount, description)); - Utils.playSound(player, "unavailable"); - return false; - } + BigDecimal cost = BigDecimal.valueOf(getCategoryCost(player, category) * amount); - // Otherwise, the item is free. - Utils.sendMessage(player, String.format(localization.getMessage("free"), amount, description)); - Utils.playSound(player, "free"); - return true; + // If the cost is higher than zero, attempt to charge for it. + if (cost.compareTo(BigDecimal.ZERO) > 0) { + economyProvider.canPurchase(player, cost, paymentResult -> { + if (paymentResult) { + economyProvider.charge(player, cost, chargeResult -> { + if (chargeResult) { + Utils.sendMessage(player, String.format(localization.getMessage("purchasedHead"), amount, description, cost)); + Utils.playSound(player, "paid"); + result.accept(true); + return; + } + }); + } + }); + + Utils.sendMessage(player, String.format(localization.getMessage("notEnoughMoney"), amount, description)); + Utils.playSound(player, "unavailable"); + result.accept(false); + return; + } + + // Otherwise, the item is free. + Utils.sendMessage(player, String.format(localization.getMessage("free"), amount, description)); + Utils.playSound(player, "free"); + result.accept(true); + }); } public static void purchaseHead(Player player, Head head, int amount, String category, String description) { - if (!processPayment(player, amount, category, description)) return; - - PlayerHeadPurchaseEvent event = new PlayerHeadPurchaseEvent(player, head, getCategoryCost(player, category)); - Bukkit.getPluginManager().callEvent(event); - if (!event.isCancelled()) { - ItemStack item = head.getMenuItem(); - item.setAmount(amount); - player.getInventory().addItem(item); - } + Utils.sendMessage(player, "&7Purchasing &e" + amount + "x " + head.getName() + "&7. Please wait..."); + processPayment(player, amount, category, description, result -> { + if (result) { + PlayerHeadPurchaseEvent event = new PlayerHeadPurchaseEvent(player, head, getCategoryCost(player, category)); + Bukkit.getPluginManager().callEvent(event); + if (!event.isCancelled()) { + ItemStack item = head.getMenuItem(); + item.setAmount(amount); + player.getInventory().addItem(item); + } + } + }); } private static String replace(String message, int size, String category, String search, Player player) { diff --git a/src/main/java/tsp/headdb/listener/MenuListener.java b/src/main/java/tsp/headdb/listener/MenuListener.java index d408dd8..13dc904 100644 --- a/src/main/java/tsp/headdb/listener/MenuListener.java +++ b/src/main/java/tsp/headdb/listener/MenuListener.java @@ -12,7 +12,7 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.plugin.java.JavaPlugin; import tsp.headdb.HeadDB; import tsp.headdb.api.HeadAPI; -import tsp.headdb.database.Category; +import tsp.headdb.implementation.Category; import tsp.headdb.inventory.InventoryUtils; import tsp.headdb.util.Utils; diff --git a/src/main/java/tsp/headdb/util/Utils.java b/src/main/java/tsp/headdb/util/Utils.java index 68b60b7..6607fa1 100644 --- a/src/main/java/tsp/headdb/util/Utils.java +++ b/src/main/java/tsp/headdb/util/Utils.java @@ -7,6 +7,7 @@ import org.bukkit.command.CommandSender; import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitTask; import tsp.headdb.HeadDB; import java.io.BufferedReader; @@ -22,6 +23,10 @@ public class Utils { private static final FileConfiguration config = HeadDB.getInstance().getConfig(); public static final Pattern UUID_PATTERN = Pattern.compile("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}"); + public static void async(Consumer task) { + Bukkit.getScheduler().runTaskAsynchronously(HeadDB.getInstance(), task); + } + /** * Retrieve the latest release from spigot * diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 9de9855..568a59a 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -11,8 +11,12 @@ requireCategoryPermission: false # Economy options economy: enable: false - # Supported: VAULT + # 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: '' cost: alphabet: 100 animals: 100