From 996e17eb9667b5e7c2f9654f5d80583bc40c1d66 Mon Sep 17 00:00:00 2001 From: Brianna Date: Sun, 5 May 2019 20:21:51 -0400 Subject: [PATCH] The rest of the features. --- pom.xml | 6 + .../java/com/songoda/epicheads/EpicHeads.java | 45 ++ .../epicheads/command/CommandManager.java | 7 +- .../command/commands/CommandEpicHeads.java | 12 +- .../command/commands/CommandHelp.java | 53 ++ .../command/commands/CommandReload.java | 42 ++ .../command/commands/CommandSettings.java | 42 ++ .../com/songoda/epicheads/gui/GUIHeads.java | 57 +- .../songoda/epicheads/gui/GUIOverview.java | 85 ++- .../java/com/songoda/epicheads/head/Head.java | 15 +- .../songoda/epicheads/players/EPlayer.java | 5 + .../epicheads/players/PlayerManager.java | 13 +- .../epicheads/utils/ConfigWrapper.java | 67 ++ .../com/songoda/epicheads/utils/Metrics.java | 695 ++++++++++++++++++ .../epicheads/utils/SettingsManager.java | 8 + .../epicheads/utils/storage/Storage.java | 44 ++ .../epicheads/utils/storage/StorageItem.java | 62 ++ .../epicheads/utils/storage/StorageRow.java | 24 + .../utils/storage/types/StorageYaml.java | 147 ++++ src/main/resources/en_US.lang | 29 +- src/main/resources/plugin.yml | 3 +- 21 files changed, 1416 insertions(+), 45 deletions(-) create mode 100644 src/main/java/com/songoda/epicheads/command/commands/CommandHelp.java create mode 100644 src/main/java/com/songoda/epicheads/command/commands/CommandReload.java create mode 100644 src/main/java/com/songoda/epicheads/command/commands/CommandSettings.java create mode 100644 src/main/java/com/songoda/epicheads/utils/ConfigWrapper.java create mode 100644 src/main/java/com/songoda/epicheads/utils/Metrics.java create mode 100644 src/main/java/com/songoda/epicheads/utils/storage/Storage.java create mode 100644 src/main/java/com/songoda/epicheads/utils/storage/StorageItem.java create mode 100644 src/main/java/com/songoda/epicheads/utils/storage/StorageRow.java create mode 100644 src/main/java/com/songoda/epicheads/utils/storage/types/StorageYaml.java diff --git a/pom.xml b/pom.xml index 579cabb..4a057a0 100644 --- a/pom.xml +++ b/pom.xml @@ -68,5 +68,11 @@ songodaupdater 1 + + net.milkbowl + vault + 1.7.1 + provided + diff --git a/src/main/java/com/songoda/epicheads/EpicHeads.java b/src/main/java/com/songoda/epicheads/EpicHeads.java index b6458a7..b03277b 100644 --- a/src/main/java/com/songoda/epicheads/EpicHeads.java +++ b/src/main/java/com/songoda/epicheads/EpicHeads.java @@ -5,11 +5,16 @@ import com.songoda.epicheads.head.Head; import com.songoda.epicheads.head.HeadManager; import com.songoda.epicheads.head.Tag; import com.songoda.epicheads.listeners.ItemListeners; +import com.songoda.epicheads.players.EPlayer; import com.songoda.epicheads.players.PlayerManager; import com.songoda.epicheads.utils.Methods; +import com.songoda.epicheads.utils.Metrics; import com.songoda.epicheads.utils.ServerVersion; import com.songoda.epicheads.utils.SettingsManager; import com.songoda.epicheads.utils.gui.AbstractGUI; +import com.songoda.epicheads.utils.storage.Storage; +import com.songoda.epicheads.utils.storage.StorageRow; +import com.songoda.epicheads.utils.storage.types.StorageYaml; import com.songoda.epicheads.utils.updateModules.LocaleModule; import com.songoda.update.Plugin; import com.songoda.update.SongodaUpdate; @@ -26,6 +31,7 @@ import java.io.*; import java.net.URL; import java.nio.charset.Charset; import java.util.Optional; +import java.util.UUID; public class EpicHeads extends JavaPlugin { private static CommandSender console = Bukkit.getConsoleSender(); @@ -40,6 +46,7 @@ public class EpicHeads extends JavaPlugin { private CommandManager commandManager; private Locale locale; + private Storage storage; public static EpicHeads getInstance() { return INSTANCE; @@ -68,6 +75,7 @@ public class EpicHeads extends JavaPlugin { SongodaUpdate.load(plugin); this.references = new References(); + this.storage = new StorageYaml(this); // Setup Managers this.headManager = new HeadManager(); @@ -84,17 +92,51 @@ public class EpicHeads extends JavaPlugin { // Load Heads loadHeads(); + // Load Favorites + Bukkit.getScheduler().runTaskLater(this, this::loadData, 10); + + int timeout = SettingsManager.Setting.AUTOSAVE.getInt() * 60 * 20; + Bukkit.getScheduler().runTaskTimerAsynchronously(this, this::saveToFile, timeout, timeout); + + // Start Metrics + new Metrics(this); + console.sendMessage(Methods.formatText("&a=============================")); } @Override public void onDisable() { + this.storage.closeConnection(); + this.saveToFile(); console.sendMessage(Methods.formatText("&a=============================")); console.sendMessage(Methods.formatText("&7EpicHeads " + this.getDescription().getVersion() + " by &5Songoda <3!")); console.sendMessage(Methods.formatText("&7Action: &cDisabling&7...")); console.sendMessage(Methods.formatText("&a=============================")); } + private void saveToFile() { + storage.doSave(); + } + + private void loadData() { + // Adding in favorites. + if (storage.containsGroup("")) { + for (StorageRow row : storage.getRowsByGroup("players")) { + if (row.get("uuid").asObject() == null) + continue; + + EPlayer player = new EPlayer( + UUID.fromString(row.get("uuid").asString()), + row.get("favorites").asIntList()); + + this.playerManager.addPlayer(player); + } + } + + // Save data initially so that if the person reloads again fast they don't lose all their data. + this.saveToFile(); + } + private void downloadHeads() { try { @@ -213,4 +255,7 @@ public class EpicHeads extends JavaPlugin { return commandManager; } + public SettingsManager getSettingsManager() { + return settingsManager; + } } diff --git a/src/main/java/com/songoda/epicheads/command/CommandManager.java b/src/main/java/com/songoda/epicheads/command/CommandManager.java index 53f3137..9ed9e49 100644 --- a/src/main/java/com/songoda/epicheads/command/CommandManager.java +++ b/src/main/java/com/songoda/epicheads/command/CommandManager.java @@ -2,6 +2,9 @@ package com.songoda.epicheads.command; import com.songoda.epicheads.EpicHeads; import com.songoda.epicheads.command.commands.CommandEpicHeads; +import com.songoda.epicheads.command.commands.CommandHelp; +import com.songoda.epicheads.command.commands.CommandReload; +import com.songoda.epicheads.command.commands.CommandSettings; import com.songoda.epicheads.utils.Methods; import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; @@ -26,10 +29,10 @@ public class CommandManager implements CommandExecutor { instance.getCommand("EpicHeads").setExecutor(this); AbstractCommand commandEpicHeads = addCommand(new CommandEpicHeads()); -/* + addCommand(new CommandSettings(commandEpicHeads)); addCommand(new CommandHelp(commandEpicHeads)); - addCommand(new CommandReload(commandEpicHeads)); */ + addCommand(new CommandReload(commandEpicHeads)); for (AbstractCommand abstractCommand : commands) { if (abstractCommand.getParent() != null) continue; diff --git a/src/main/java/com/songoda/epicheads/command/commands/CommandEpicHeads.java b/src/main/java/com/songoda/epicheads/command/commands/CommandEpicHeads.java index abffb1b..4532b7c 100644 --- a/src/main/java/com/songoda/epicheads/command/commands/CommandEpicHeads.java +++ b/src/main/java/com/songoda/epicheads/command/commands/CommandEpicHeads.java @@ -27,16 +27,16 @@ public class CommandEpicHeads extends AbstractCommand { @Override public String getPermissionNode() { - return null; - } - - @Override - public String getSyntax() { return "epicheads.menu"; } + @Override + public String getSyntax() { + return "/epicheads"; + } + @Override public String getDescription() { - return "Displays this page."; + return "Displays heads overview."; } } diff --git a/src/main/java/com/songoda/epicheads/command/commands/CommandHelp.java b/src/main/java/com/songoda/epicheads/command/commands/CommandHelp.java new file mode 100644 index 0000000..db0fa0b --- /dev/null +++ b/src/main/java/com/songoda/epicheads/command/commands/CommandHelp.java @@ -0,0 +1,53 @@ +package com.songoda.epicheads.command.commands; + +import com.songoda.epicheads.EpicHeads; +import com.songoda.epicheads.command.AbstractCommand; +import com.songoda.epicheads.utils.Methods; +import org.bukkit.command.CommandSender; + +import java.util.List; + +public class CommandHelp extends AbstractCommand { + + public CommandHelp(AbstractCommand parent) { + super(parent, false, "help"); + } + + @Override + protected ReturnType runCommand(EpicHeads instance, CommandSender sender, String... args) { + sender.sendMessage(""); + sender.sendMessage(Methods.formatText(instance.getReferences().getPrefix() + "&7Version " + instance.getDescription().getVersion() + " Created with <3 by &5&l&oSongoda")); + sender.sendMessage(""); + sender.sendMessage(Methods.formatText("&7Welcome to EpicHeads! To get started try using the /epicheads command to access the heads panel.")); + sender.sendMessage(""); + sender.sendMessage(Methods.formatText("&6Commands:")); + for (AbstractCommand command : instance.getCommandManager().getCommands()) { + if (command.getPermissionNode() == null || sender.hasPermission(command.getPermissionNode())) { + sender.sendMessage(Methods.formatText("&8 - &a" + command.getSyntax() + "&7 - " + command.getDescription())); + } + } + sender.sendMessage(""); + + return ReturnType.SUCCESS; + } + + @Override + protected List onTab(EpicHeads instance, CommandSender sender, String... args) { + return null; + } + + @Override + public String getPermissionNode() { + return null; + } + + @Override + public String getSyntax() { + return "/epicheads help"; + } + + @Override + public String getDescription() { + return "Displays this page."; + } +} diff --git a/src/main/java/com/songoda/epicheads/command/commands/CommandReload.java b/src/main/java/com/songoda/epicheads/command/commands/CommandReload.java new file mode 100644 index 0000000..c7a3ef1 --- /dev/null +++ b/src/main/java/com/songoda/epicheads/command/commands/CommandReload.java @@ -0,0 +1,42 @@ +package com.songoda.epicheads.command.commands; + +import com.songoda.epicheads.EpicHeads; +import com.songoda.epicheads.command.AbstractCommand; +import com.songoda.epicheads.utils.Methods; +import org.bukkit.command.CommandSender; + +import java.util.List; + +public class CommandReload extends AbstractCommand { + + public CommandReload(AbstractCommand parent) { + super(parent, false, "reload"); + } + + @Override + protected ReturnType runCommand(EpicHeads instance, CommandSender sender, String... args) { + instance.reload(); + sender.sendMessage(Methods.formatText(instance.getReferences().getPrefix() + "&7Configuration and Language files reloaded.")); + return ReturnType.SUCCESS; + } + + @Override + protected List onTab(EpicHeads instance, CommandSender sender, String... args) { + return null; + } + + @Override + public String getPermissionNode() { + return "epicheads.admin"; + } + + @Override + public String getSyntax() { + return "/epicheads reload"; + } + + @Override + public String getDescription() { + return "Reload the Configuration and Language files."; + } +} diff --git a/src/main/java/com/songoda/epicheads/command/commands/CommandSettings.java b/src/main/java/com/songoda/epicheads/command/commands/CommandSettings.java new file mode 100644 index 0000000..4584af7 --- /dev/null +++ b/src/main/java/com/songoda/epicheads/command/commands/CommandSettings.java @@ -0,0 +1,42 @@ +package com.songoda.epicheads.command.commands; + +import com.songoda.epicheads.EpicHeads; +import com.songoda.epicheads.command.AbstractCommand; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.List; + +public class CommandSettings extends AbstractCommand { + + public CommandSettings(AbstractCommand parent) { + super(parent, true, "settings"); + } + + @Override + protected ReturnType runCommand(EpicHeads instance, CommandSender sender, String... args) { + Player player = (Player) sender; + instance.getSettingsManager().openSettingsManager(player); + return ReturnType.SUCCESS; + } + + @Override + protected List onTab(EpicHeads instance, CommandSender sender, String... args) { + return null; + } + + @Override + public String getPermissionNode() { + return "epicheads.admin"; + } + + @Override + public String getSyntax() { + return "/ehe settings"; + } + + @Override + public String getDescription() { + return "Edit EpicHeads Settings."; + } +} diff --git a/src/main/java/com/songoda/epicheads/gui/GUIHeads.java b/src/main/java/com/songoda/epicheads/gui/GUIHeads.java index 3756e5c..92e821d 100644 --- a/src/main/java/com/songoda/epicheads/gui/GUIHeads.java +++ b/src/main/java/com/songoda/epicheads/gui/GUIHeads.java @@ -5,13 +5,17 @@ import com.songoda.epicheads.head.Head; import com.songoda.epicheads.head.Tag; import com.songoda.epicheads.players.EPlayer; import com.songoda.epicheads.utils.AbstractChatConfirm; +import com.songoda.epicheads.utils.SettingsManager; import com.songoda.epicheads.utils.gui.AbstractGUI; +import net.milkbowl.vault.economy.Economy; import org.bukkit.Bukkit; +import org.bukkit.GameMode; import org.bukkit.Material; import org.bukkit.entity.Player; import org.bukkit.event.inventory.ClickType; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.plugin.RegisteredServiceProvider; import java.util.ArrayList; import java.util.Comparator; @@ -45,17 +49,16 @@ public class GUIHeads extends AbstractGUI { } private void updateTitle() { - - int numHeads = this.heads.size(); if (numHeads == 0) { - player.sendMessage("No heads found."); + player.sendMessage(plugin.getReferences().getPrefix() + plugin.getLocale().getMessage("general.search.nonefound")); return; } Tag tag = heads.get(0).getTag(); this.maxPage = (int) Math.floor(numHeads / 45.0); - init((query != null ? "Query: " + query : tag.getName()) + " (" + numHeads + ") Page " + (page + 1) + "/" + (maxPage + 1), 54); + init((query != null ? plugin.getLocale().getMessage("general.word.query") + ": " + query : tag.getName()) + + " (" + numHeads + ") " + plugin.getLocale().getMessage("general.word.page") + " " + (page + 1) + "/" + (maxPage + 1), 54); constructGUI(); } @@ -67,7 +70,7 @@ public class GUIHeads extends AbstractGUI { .collect(Collectors.toList()); if (page - 2 > 0) { - createButton(0, Material.ARROW, "Back"); + createButton(0, Material.ARROW, "&c" + plugin.getLocale().getMessage("general.word.page") + " " + (page - 2)); registerClickable(0, ((player1, inventory1, cursor, slot, type) -> { page -= 3; updateTitle(); @@ -76,7 +79,7 @@ public class GUIHeads extends AbstractGUI { } if (page - 1 > 0) { - createButton(1, Material.ARROW, "Back"); + createButton(1, Material.ARROW, "&c" + plugin.getLocale().getMessage("general.word.page") + " " + (page - 1)); registerClickable(1, ((player1, inventory1, cursor, slot, type) -> { page -= 2; updateTitle(); @@ -85,7 +88,7 @@ public class GUIHeads extends AbstractGUI { } if (page != 0) { - createButton(2, Material.ARROW, "Back"); + createButton(2, Material.ARROW, "&c" + plugin.getLocale().getMessage("general.word.page") + " " + page); registerClickable(2, ((player1, inventory1, cursor, slot, type) -> { page--; updateTitle(); @@ -93,16 +96,16 @@ public class GUIHeads extends AbstractGUI { inventory.getItem(2).setAmount(page); } - createButton(3, Material.COMPASS, "Create Search"); + createButton(3, Material.COMPASS, plugin.getLocale().getMessage("gui.heads.search")); - createButton(4, Material.MAP, "Back To Categories"); + createButton(4, Material.MAP, plugin.getLocale().getMessage("gui.heads.categories")); inventory.getItem(4).setAmount(page + 1); if (heads.size() > 1) - createButton(5, Material.COMPASS, "Refine Search"); + createButton(5, Material.COMPASS, plugin.getLocale().getMessage("gui.heads.refine")); if (page != maxPage) { - createButton(6, Material.ARROW, "Next"); + createButton(6, Material.ARROW, "&c" + plugin.getLocale().getMessage("general.word.page") + " " + (page + 2)); registerClickable(6, ((player1, inventory1, cursor, slot, type) -> { page++; updateTitle(); @@ -111,7 +114,7 @@ public class GUIHeads extends AbstractGUI { } if (page + 1 < maxPage) { - createButton(7, Material.ARROW, "Next"); + createButton(7, Material.ARROW, "&c" + plugin.getLocale().getMessage("general.word.page") + " " + (page + 3)); registerClickable(7, ((player1, inventory1, cursor, slot, type) -> { page += 2; updateTitle(); @@ -120,7 +123,7 @@ public class GUIHeads extends AbstractGUI { } if (page + 2 < maxPage) { - createButton(8, Material.ARROW, "Next"); + createButton(8, Material.ARROW, "&c" + plugin.getLocale().getMessage("general.word.page") + " " + (page + 4)); registerClickable(8, ((player1, inventory1, cursor, slot, type) -> { page += 3; updateTitle(); @@ -135,10 +138,15 @@ public class GUIHeads extends AbstractGUI { if (head.getName() == null) continue; - ItemStack item = head.asItemStack(favorites.contains(head.getId())); + boolean free = (!player.hasPermission("epicheads.freeheads") && + (SettingsManager.Setting.FREE_IN_CREATIVE.getBoolean() && player.getGameMode() == GameMode.CREATIVE)); + + ItemStack item = head.asItemStack(favorites.contains(head.getId()), free); inventory.setItem(i + 9, item); + double cost = SettingsManager.Setting.PRICE.getDouble(); + registerClickable(i + 9, ((player1, inventory1, cursor, slot, type) -> { if (type == ClickType.SHIFT_LEFT || type == ClickType.SHIFT_RIGHT) { EPlayer ePlayer = plugin.getPlayerManager().getPlayer(player); @@ -154,6 +162,23 @@ public class GUIHeads extends AbstractGUI { meta.setLore(new ArrayList<>()); item.setItemMeta(meta); + + if (!player.hasPermission("epicheads.bypasscost") && free) { + if (plugin.getServer().getPluginManager().getPlugin("Vault") != null) { + RegisteredServiceProvider rsp = plugin.getServer().getServicesManager().getRegistration(net.milkbowl.vault.economy.Economy.class); + net.milkbowl.vault.economy.Economy econ = rsp.getProvider(); + if (econ.has(player, cost)) { + econ.withdrawPlayer(player, cost); + } else { + player.sendMessage(plugin.getLocale().getMessage("event.buyhead.cannotafford")); + return; + } + } else { + player.sendMessage("Vault is not installed."); + return; + } + } + player.getInventory().addItem(item); })); } @@ -170,7 +195,7 @@ public class GUIHeads extends AbstractGUI { if (heads.size() > 1) { registerClickable(5, ((player1, inventory1, cursor, slot, type) -> { - player.sendMessage("Refine your search..."); + player.sendMessage(plugin.getReferences().getPrefix() + plugin.getLocale().getMessage("general.search.refine")); AbstractChatConfirm abstractChatConfirm = new AbstractChatConfirm(player, event -> { this.page = 0; this.heads = this.heads.stream().filter(head -> head.getName().toLowerCase() @@ -191,7 +216,7 @@ public class GUIHeads extends AbstractGUI { } public static void doSearch(Player player) { - player.sendMessage("Enter your query..."); + player.sendMessage(EpicHeads.getInstance().getReferences().getPrefix() + EpicHeads.getInstance().getLocale().getMessage("general.search.global")); new AbstractChatConfirm(player, event -> { List heads = EpicHeads.getInstance().getHeadManager().getHeads().stream() .filter(head -> head.getName().toLowerCase().contains(event.getMessage().toLowerCase())) diff --git a/src/main/java/com/songoda/epicheads/gui/GUIOverview.java b/src/main/java/com/songoda/epicheads/gui/GUIOverview.java index bd5da4d..26ab9fa 100644 --- a/src/main/java/com/songoda/epicheads/gui/GUIOverview.java +++ b/src/main/java/com/songoda/epicheads/gui/GUIOverview.java @@ -3,10 +3,13 @@ package com.songoda.epicheads.gui; import com.songoda.epicheads.EpicHeads; import com.songoda.epicheads.head.Tag; import com.songoda.epicheads.utils.Methods; +import com.songoda.epicheads.utils.SettingsManager; import com.songoda.epicheads.utils.gui.AbstractGUI; import org.bukkit.Material; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import java.util.ArrayList; import java.util.List; public class GUIOverview extends AbstractGUI { @@ -17,15 +20,19 @@ public class GUIOverview extends AbstractGUI { super(player); this.plugin = plugin; - init("EpicHeads (" + plugin.getHeadManager().getHeads().size() + " heads)", 45); + init(plugin.getLocale().getMessage("gui.overview.title", plugin.getHeadManager().getHeads().size()), 45); } @Override protected void constructGUI() { - createButton(5, Material.STONE, "&6&lView Favorites", - "Shift click any head", - "to save as a favorite."); + ArrayList lore = new ArrayList<>(); + String[] parts = plugin.getLocale().getMessage("gui.overview.favoriteslore").split("\\|"); + for (String line : parts) + lore.add(Methods.formatText(line)); + + createButton(4, Material.GOLDEN_APPLE, plugin.getLocale().getMessage("gui.overview.viewfavorites"), + lore); inventory.setItem(0, Methods.getBackgroundGlass(true)); inventory.setItem(1, Methods.getBackgroundGlass(true)); @@ -54,30 +61,84 @@ public class GUIOverview extends AbstractGUI { if (i + add == 7 || i + add == 16) add = add + 2; Tag tag = plugin.getHeadManager().getTags().get(i); - createButton(i + 10 + add, Material.STONE, "&c&l" + tag.getName(), "&e" + tag.getCount() + " heads"); - registerClickable(i + 10 + add, ((player1, inventory1, cursor, slot, type) -> new GUIHeads(plugin, player, null, plugin.getHeadManager().getHeadsByTag(tag)))); + if (!player.hasPermission("epicheads.category." + tag.getName())) return; + + TagInfo tagInfo = TagInfo.valueOf(tag.getName().toUpperCase()); + + createButton(i + 10 + add, Methods.addTexture(new ItemStack(Material.PLAYER_HEAD, 1, (byte) 3), + tagInfo.getUrl()), + plugin.getLocale().getMessage("gui.overview.headname", tagInfo.getName()), + plugin.getLocale().getMessage("gui.overview.headlore", tag.getCount())); + + registerClickable(i + 10 + add, ((player1, inventory1, cursor, slot, type) -> + new GUIHeads(plugin, player, null, plugin.getHeadManager().getHeadsByTag(tag)))); } - createButton(39, Material.COMPASS, "Search"); + createButton(39, Material.COMPASS, plugin.getLocale().getMessage("gui.overview.search")); - createButton(41, Material.STONE, - "Add or request new heads", - "in our discord server."); + + if (SettingsManager.Setting.DISCORD.getBoolean()) { + + ArrayList lore2 = new ArrayList<>(); + String[] parts2 = plugin.getLocale().getMessage("gui.overview.discordlore").split("\\|"); + for (String line : parts2) + lore2.add(Methods.formatText(line)); + + createButton(41, Methods.addTexture(new ItemStack(Material.PLAYER_HEAD, 1, (byte) 3), + "a3b183b148b9b4e2b158334aff3b5bb6c2c2dbbc4d67f76a7be856687a2b623"), + plugin.getLocale().getMessage("gui.overview.discord"), + lore2); + } } @Override protected void registerClickables() { - registerClickable(5, ((player1, inventory1, cursor, slot, type) -> - new GUIHeads(plugin, player, null, plugin.getPlayerManager().getPlayer(player).getFavoritesAsHeads()))); + registerClickable(4, ((player1, inventory1, cursor, slot, type) -> + new GUIHeads(plugin, player, plugin.getLocale().getMessage("general.word.favorites"), + plugin.getPlayerManager().getPlayer(player).getFavoritesAsHeads()))); registerClickable(39, ((player1, inventory1, cursor, slot, type) -> GUIHeads.doSearch(player1))); + + registerClickable(41, ((player1, inventory1, cursor, slot, type) -> { + player.sendMessage(Methods.formatText(plugin.getReferences().getPrefix() + "&9https://discord.gg/A9TRJQb")); + player.closeInventory(); + })); } @Override protected void registerOnCloses() { } + + public enum TagInfo { + ALPHABET("&9&lAlphabet", "9c60da2944a177dd08268fbec04e40812d1d929650be66529b1ee5e1e7eca"), + HUMANS("&a&lHumans", "b7c224f0453e745c85966025e7767d592feea3ad4c69d2366d94d5e8c9a8c2ce"), + FOOD("&b&lFood", "f7b9f08ada4e8ba586a04ed2e9e25fe8b9d568a665243f9c603799a7c896736"), + MISC("&8&lMisc", "f5612dc7b86d71afc1197301c15fd979e9f39e7b1f41d8f1ebdf8115576e2e"), + ANIMALS("&d&lAnimals", "d8cdd4f285632c25d762ece25f4193b966c2641b15d9bdbc0a113023de76ab"), + GAMES("&b&lGames", "dba8d8e53d8a5a75770b62cce73db6bab701cc3de4a9b654d213d54af9615"), + MONSTERS("&c&lMonsters", "68d2183640218ab330ac56d2aab7e29a9790a545f691619e38578ea4a69ae0b6"), + INTERIOR("&6&lInterior", "ec6d9024fc5412e8e2664123732d2291dfc6bb175f72cf894096f7f313641fd4"), + BLOCKS("&9&lBlocks", "7788f5ddaf52c5842287b9427a74dac8f0919eb2fdb1b51365ab25eb392c47"); + + String name; + String url; + + TagInfo(String name, String url) { + this.name = name; + this.url = url; + } + + public String getName() { + return name; + } + + public String getUrl() { + return url; + } + } + } diff --git a/src/main/java/com/songoda/epicheads/head/Head.java b/src/main/java/com/songoda/epicheads/head/Head.java index 37aefd6..8640ce3 100644 --- a/src/main/java/com/songoda/epicheads/head/Head.java +++ b/src/main/java/com/songoda/epicheads/head/Head.java @@ -1,7 +1,9 @@ package com.songoda.epicheads.head; +import com.songoda.epicheads.EpicHeads; import com.songoda.epicheads.utils.Methods; +import com.songoda.epicheads.utils.SettingsManager; import org.bukkit.Material; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; @@ -47,19 +49,24 @@ public class Head { } public ItemStack asItemStack() { - return asItemStack(false); + return asItemStack(false, false); } - public ItemStack asItemStack(boolean favorite) { + public ItemStack asItemStack(boolean favorite) { return asItemStack(favorite, false); } + + public ItemStack asItemStack(boolean favorite, boolean includeCost) { ItemStack item = Methods.addTexture(new ItemStack(Material.PLAYER_HEAD, 1, (byte) 3), this.URL); + double cost = SettingsManager.Setting.PRICE.getDouble(); ItemMeta meta = item.getItemMeta(); meta.setDisplayName(Methods.formatText((favorite ? "&6⭐ " : "") + "&9" + name)); List lore = new ArrayList<>(); if (this.staffPicked == 1) - lore.add(Methods.formatText("&8Staff Favorite")); - lore.add(Methods.formatText("&8ID: &7" + this.id)); + lore.add(Methods.formatText(EpicHeads.getInstance().getLocale().getMessage("general.head.staffpicked"))); + lore.add(Methods.formatText(EpicHeads.getInstance().getLocale().getMessage("general.head.id", this.id))); + if (includeCost) + lore.add(EpicHeads.getInstance().getLocale().getMessage("general.head.cost", cost)); meta.setLore(lore); item.setItemMeta(meta); diff --git a/src/main/java/com/songoda/epicheads/players/EPlayer.java b/src/main/java/com/songoda/epicheads/players/EPlayer.java index ed11f5a..9dbe32f 100644 --- a/src/main/java/com/songoda/epicheads/players/EPlayer.java +++ b/src/main/java/com/songoda/epicheads/players/EPlayer.java @@ -18,6 +18,11 @@ public class EPlayer { this.uuid = uuid; } + public EPlayer(UUID uuid, List favorites) { + this.uuid = uuid; + this.favorites = favorites; + } + public UUID getUuid() { return uuid; } diff --git a/src/main/java/com/songoda/epicheads/players/PlayerManager.java b/src/main/java/com/songoda/epicheads/players/PlayerManager.java index 66097c3..3859958 100644 --- a/src/main/java/com/songoda/epicheads/players/PlayerManager.java +++ b/src/main/java/com/songoda/epicheads/players/PlayerManager.java @@ -2,9 +2,7 @@ package com.songoda.epicheads.players; import org.bukkit.entity.Player; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; +import java.util.*; public class PlayerManager { @@ -18,4 +16,13 @@ public class PlayerManager { return getPlayer(player.getUniqueId()); } + public EPlayer addPlayer(EPlayer player) { + registeredHeads.put(player.getUuid(), player); + return player; + } + + public List getPlayers() { + return new ArrayList<>(registeredHeads.values()); + } + } diff --git a/src/main/java/com/songoda/epicheads/utils/ConfigWrapper.java b/src/main/java/com/songoda/epicheads/utils/ConfigWrapper.java new file mode 100644 index 0000000..97f37e3 --- /dev/null +++ b/src/main/java/com/songoda/epicheads/utils/ConfigWrapper.java @@ -0,0 +1,67 @@ +package com.songoda.epicheads.utils; + +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.io.IOException; +import java.util.logging.Level; + +/** + * ConfigWrapper made by @clip + */ +public class ConfigWrapper { + + private final JavaPlugin plugin; + private final String folderName, fileName; + private FileConfiguration config; + private File configFile; + + public ConfigWrapper(final JavaPlugin instance, final String folderName, final String fileName) { + this.plugin = instance; + this.folderName = folderName; + this.fileName = fileName; + } + + public void createNewFile(final String message, final String header) { + reloadConfig(); + saveConfig(); + loadConfig(header); + + if (message != null) { + plugin.getLogger().info(message); + } + } + + public FileConfiguration getConfig() { + if (config == null) { + reloadConfig(); + } + return config; + } + + public void loadConfig(final String header) { + config.options().header(header); + config.options().copyDefaults(true); + saveConfig(); + } + + public void reloadConfig() { + if (configFile == null) { + configFile = new File(plugin.getDataFolder() + folderName, fileName); + } + config = YamlConfiguration.loadConfiguration(configFile); + } + + public void saveConfig() { + if (config == null || configFile == null) { + return; + } + try { + getConfig().save(configFile); + } catch (final IOException ex) { + plugin.getLogger().log(Level.SEVERE, "Could not save config to " + configFile, ex); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/songoda/epicheads/utils/Metrics.java b/src/main/java/com/songoda/epicheads/utils/Metrics.java new file mode 100644 index 0000000..81f2315 --- /dev/null +++ b/src/main/java/com/songoda/epicheads/utils/Metrics.java @@ -0,0 +1,695 @@ +package com.songoda.epicheads.utils; + +import org.bukkit.Bukkit; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.bukkit.plugin.ServicePriority; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; + +import javax.net.ssl.HttpsURLConnection; +import java.io.*; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.Callable; +import java.util.logging.Level; +import java.util.zip.GZIPOutputStream; + +/** + * bStats collects some data for plugin authors. + *

+ * Check out https://bStats.org/ to learn more about bStats! + */ +@SuppressWarnings({"WeakerAccess", "unused"}) +public class Metrics { + + static { + // You can use the property to disable the check in your test environment + if (System.getProperty("bstats.relocatecheck") == null || !System.getProperty("bstats.relocatecheck").equals("false")) { + // Maven's Relocate is clever and changes strings, too. So we have to use this little "trick" ... :D + final String defaultPackage = new String( + new byte[]{'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's', '.', 'b', 'u', 'k', 'k', 'i', 't'}); + final String examplePackage = new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); + // We want to make sure nobody just copy & pastes the example and use the wrong package names + if (Metrics.class.getPackage().getName().equals(defaultPackage) || Metrics.class.getPackage().getName().equals(examplePackage)) { + throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); + } + } + } + + // The version of this bStats class + public static final int B_STATS_VERSION = 1; + + // The url to which the data is sent + private static final String URL = "https://bStats.org/submitData/bukkit"; + + // Is bStats enabled on this server? + private boolean enabled; + + // Should failed requests be logged? + private static boolean logFailedRequests; + + // Should the sent data be logged? + private static boolean logSentData; + + // Should the response text be logged? + private static boolean logResponseStatusText; + + // The uuid of the server + private static String serverUUID; + + // The plugin + private final Plugin plugin; + + // A list with all custom charts + private final List charts = new ArrayList<>(); + + /** + * Class constructor. + * + * @param plugin The plugin which stats should be submitted. + */ + public Metrics(Plugin plugin) { + if (plugin == null) { + throw new IllegalArgumentException("Plugin cannot be null!"); + } + this.plugin = plugin; + + // Get the config file + File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); + File configFile = new File(bStatsFolder, "config.yml"); + YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile); + + // Check if the config file exists + if (!config.isSet("serverUuid")) { + + // Add default values + config.addDefault("enabled", true); + // Every server gets it's unique random id. + config.addDefault("serverUuid", UUID.randomUUID().toString()); + // Should failed request be logged? + config.addDefault("logFailedRequests", false); + // Should the sent data be logged? + config.addDefault("logSentData", false); + // Should the response text be logged? + config.addDefault("logResponseStatusText", false); + + // Inform the server owners about bStats + config.options().header( + "bStats collects some data for plugin authors like how many servers are using their plugins.\n" + + "To honor their work, you should not disable it.\n" + + "This has nearly no effect on the server performance!\n" + + "Check out https://bStats.org/ to learn more :)" + ).copyDefaults(true); + try { + config.save(configFile); + } catch (IOException ignored) { } + } + + // Load the data + enabled = config.getBoolean("enabled", true); + serverUUID = config.getString("serverUuid"); + logFailedRequests = config.getBoolean("logFailedRequests", false); + logSentData = config.getBoolean("logSentData", false); + logResponseStatusText = config.getBoolean("logResponseStatusText", false); + + if (enabled) { + boolean found = false; + // Search for all other bStats Metrics classes to see if we are the first one + for (Class service : Bukkit.getServicesManager().getKnownServices()) { + try { + service.getField("B_STATS_VERSION"); // Our identifier :) + found = true; // We aren't the first + break; + } catch (NoSuchFieldException ignored) { } + } + // Register our service + Bukkit.getServicesManager().register(Metrics.class, this, plugin, ServicePriority.Normal); + if (!found) { + // We are the first! + startSubmitting(); + } + } + } + + /** + * Checks if bStats is enabled. + * + * @return Whether bStats is enabled or not. + */ + public boolean isEnabled() { + return enabled; + } + + /** + * Adds a custom chart. + * + * @param chart The chart to add. + */ + public void addCustomChart(CustomChart chart) { + if (chart == null) { + throw new IllegalArgumentException("Chart cannot be null!"); + } + charts.add(chart); + } + + /** + * Starts the Scheduler which submits our data every 30 minutes. + */ + private void startSubmitting() { + final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + if (!plugin.isEnabled()) { // Plugin was disabled + timer.cancel(); + return; + } + // Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler + // Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;) + Bukkit.getScheduler().runTask(plugin, () -> submitData()); + } + }, 1000 * 60 * 5, 1000 * 60 * 30); + // Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start + // WARNING: Changing the frequency has no effect but your plugin WILL be blocked/deleted! + // WARNING: Just don't do it! + } + + /** + * Gets the plugin specific data. + * This method is called using Reflection. + * + * @return The plugin specific data. + */ + public JSONObject getPluginData() { + JSONObject data = new JSONObject(); + + String pluginName = plugin.getDescription().getName(); + String pluginVersion = plugin.getDescription().getVersion(); + + data.put("pluginName", pluginName); // Append the name of the plugin + data.put("pluginVersion", pluginVersion); // Append the version of the plugin + JSONArray customCharts = new JSONArray(); + for (CustomChart customChart : charts) { + // Add the data of the custom charts + JSONObject chart = customChart.getRequestJsonObject(); + if (chart == null) { // If the chart is null, we skip it + continue; + } + customCharts.add(chart); + } + data.put("customCharts", customCharts); + + return data; + } + + /** + * Gets the server specific data. + * + * @return The server specific data. + */ + private JSONObject getServerData() { + // Minecraft specific data + int playerAmount; + try { + // Around MC 1.8 the return type was changed to a collection from an array, + // This fixes java.lang.NoSuchMethodError: org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; + Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); + playerAmount = onlinePlayersMethod.getReturnType().equals(Collection.class) + ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() + : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; + } catch (Exception e) { + playerAmount = Bukkit.getOnlinePlayers().size(); // Just use the new method if the Reflection failed + } + int onlineMode = Bukkit.getOnlineMode() ? 1 : 0; + String bukkitVersion = Bukkit.getVersion(); + + // OS/Java specific data + String javaVersion = System.getProperty("java.version"); + String osName = System.getProperty("os.name"); + String osArch = System.getProperty("os.arch"); + String osVersion = System.getProperty("os.version"); + int coreCount = Runtime.getRuntime().availableProcessors(); + + JSONObject data = new JSONObject(); + + data.put("serverUUID", serverUUID); + + data.put("playerAmount", playerAmount); + data.put("onlineMode", onlineMode); + data.put("bukkitVersion", bukkitVersion); + + data.put("javaVersion", javaVersion); + data.put("osName", osName); + data.put("osArch", osArch); + data.put("osVersion", osVersion); + data.put("coreCount", coreCount); + + return data; + } + + /** + * Collects the data and sends it afterwards. + */ + private void submitData() { + final JSONObject data = getServerData(); + + JSONArray pluginData = new JSONArray(); + // Search for all other bStats Metrics classes to get their plugin data + for (Class service : Bukkit.getServicesManager().getKnownServices()) { + try { + service.getField("B_STATS_VERSION"); // Our identifier :) + + for (RegisteredServiceProvider provider : Bukkit.getServicesManager().getRegistrations(service)) { + try { + pluginData.add(provider.getService().getMethod("getPluginData").invoke(provider.getProvider())); + } catch (NullPointerException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { } + } + } catch (NoSuchFieldException ignored) { } + } + + data.put("plugins", pluginData); + + // Create a new thread for the connection to the bStats server + new Thread(new Runnable() { + @Override + public void run() { + try { + // Send the data + sendData(plugin, data); + } catch (Exception e) { + // Something went wrong! :( + if (logFailedRequests) { + plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e); + } + } + } + }).start(); + } + + /** + * Sends the data to the bStats server. + * + * @param plugin Any plugin. It's just used to get a logger instance. + * @param data The data to send. + * @throws Exception If the request failed. + */ + private static void sendData(Plugin plugin, JSONObject data) throws Exception { + if (data == null) { + throw new IllegalArgumentException("Data cannot be null!"); + } + if (Bukkit.isPrimaryThread()) { + throw new IllegalAccessException("This method must not be called from the main thread!"); + } + if (logSentData) { + plugin.getLogger().info("Sending data to bStats: " + data.toString()); + } + HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection(); + + // Compress the data to save bandwidth + byte[] compressedData = compress(data.toString()); + + // Add headers + connection.setRequestMethod("POST"); + connection.addRequestProperty("Accept", "application/json"); + connection.addRequestProperty("Connection", "close"); + connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request + connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); + connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format + connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION); + + // Send data + connection.setDoOutput(true); + DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream()); + outputStream.write(compressedData); + outputStream.flush(); + outputStream.close(); + + InputStream inputStream = connection.getInputStream(); + BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); + + StringBuilder builder = new StringBuilder(); + String line; + while ((line = bufferedReader.readLine()) != null) { + builder.append(line); + } + bufferedReader.close(); + if (logResponseStatusText) { + plugin.getLogger().info("Sent data to bStats and received response: " + builder.toString()); + } + } + + /** + * Gzips the given String. + * + * @param str The string to gzip. + * @return The gzipped String. + * @throws IOException If the compression failed. + */ + private static byte[] compress(final String str) throws IOException { + if (str == null) { + return null; + } + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + GZIPOutputStream gzip = new GZIPOutputStream(outputStream); + gzip.write(str.getBytes(StandardCharsets.UTF_8)); + gzip.close(); + return outputStream.toByteArray(); + } + + /** + * Represents a custom chart. + */ + public static abstract class CustomChart { + + // The id of the chart + final String chartId; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + */ + CustomChart(String chartId) { + if (chartId == null || chartId.isEmpty()) { + throw new IllegalArgumentException("ChartId cannot be null or empty!"); + } + this.chartId = chartId; + } + + private JSONObject getRequestJsonObject() { + JSONObject chart = new JSONObject(); + chart.put("chartId", chartId); + try { + JSONObject data = getChartData(); + if (data == null) { + // If the data is null we don't send the chart. + return null; + } + chart.put("data", data); + } catch (Throwable t) { + if (logFailedRequests) { + Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t); + } + return null; + } + return chart; + } + + protected abstract JSONObject getChartData() throws Exception; + + } + + /** + * Represents a custom simple pie. + */ + public static class SimplePie extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimplePie(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + String value = callable.call(); + if (value == null || value.isEmpty()) { + // Null = skip the chart + return null; + } + data.put("value", value); + return data; + } + } + + /** + * Represents a custom advanced pie. + */ + public static class AdvancedPie extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedPie(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + continue; // Skip this invalid + } + allSkipped = false; + values.put(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + } + + /** + * Represents a custom drilldown pie. + */ + public static class DrilldownPie extends CustomChart { + + private final Callable>> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public DrilldownPie(String chartId, Callable>> callable) { + super(chartId); + this.callable = callable; + } + + @Override + public JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map> map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean reallyAllSkipped = true; + for (Map.Entry> entryValues : map.entrySet()) { + JSONObject value = new JSONObject(); + boolean allSkipped = true; + for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { + value.put(valueEntry.getKey(), valueEntry.getValue()); + allSkipped = false; + } + if (!allSkipped) { + reallyAllSkipped = false; + values.put(entryValues.getKey(), value); + } + } + if (reallyAllSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + } + + /** + * Represents a custom single line chart. + */ + public static class SingleLineChart extends CustomChart { + + private final Callable callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SingleLineChart(String chartId, Callable callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + int value = callable.call(); + if (value == 0) { + // Null = skip the chart + return null; + } + data.put("value", value); + return data; + } + + } + + /** + * Represents a custom multi line chart. + */ + public static class MultiLineChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public MultiLineChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue() == 0) { + continue; // Skip this invalid + } + allSkipped = false; + values.put(entry.getKey(), entry.getValue()); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + + } + + /** + * Represents a custom simple bar chart. + */ + public static class SimpleBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public SimpleBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + for (Map.Entry entry : map.entrySet()) { + JSONArray categoryValues = new JSONArray(); + categoryValues.add(entry.getValue()); + values.put(entry.getKey(), categoryValues); + } + data.put("values", values); + return data; + } + + } + + /** + * Represents a custom advanced bar chart. + */ + public static class AdvancedBarChart extends CustomChart { + + private final Callable> callable; + + /** + * Class constructor. + * + * @param chartId The id of the chart. + * @param callable The callable which is used to request the chart data. + */ + public AdvancedBarChart(String chartId, Callable> callable) { + super(chartId); + this.callable = callable; + } + + @Override + protected JSONObject getChartData() throws Exception { + JSONObject data = new JSONObject(); + JSONObject values = new JSONObject(); + Map map = callable.call(); + if (map == null || map.isEmpty()) { + // Null = skip the chart + return null; + } + boolean allSkipped = true; + for (Map.Entry entry : map.entrySet()) { + if (entry.getValue().length == 0) { + continue; // Skip this invalid + } + allSkipped = false; + JSONArray categoryValues = new JSONArray(); + for (int categoryValue : entry.getValue()) { + categoryValues.add(categoryValue); + } + values.put(entry.getKey(), categoryValues); + } + if (allSkipped) { + // Null = skip the chart + return null; + } + data.put("values", values); + return data; + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/songoda/epicheads/utils/SettingsManager.java b/src/main/java/com/songoda/epicheads/utils/SettingsManager.java index 076bdc5..2c72dd0 100644 --- a/src/main/java/com/songoda/epicheads/utils/SettingsManager.java +++ b/src/main/java/com/songoda/epicheads/utils/SettingsManager.java @@ -170,6 +170,11 @@ public class SettingsManager implements Listener { public enum Setting { + AUTOSAVE("Main.Auto Save Interval In Seconds", 15), + DISCORD("Main.Show Discord Button", true), + PRICE("Main.Head Cost", 24.99), + FREE_IN_CREATIVE("Main.Heads Free In Creative Mode", false), + GLASS_TYPE_1("Interfaces.Glass Type 1", 7), GLASS_TYPE_2("Interfaces.Glass Type 2", 11), GLASS_TYPE_3("Interfaces.Glass Type 3", 3), @@ -202,5 +207,8 @@ public class SettingsManager implements Listener { public char getChar() { return EpicHeads.getInstance().getConfig().getString(setting).charAt(0); } + public double getDouble() { + return EpicHeads.getInstance().getConfig().getDouble(setting); + } } } \ No newline at end of file diff --git a/src/main/java/com/songoda/epicheads/utils/storage/Storage.java b/src/main/java/com/songoda/epicheads/utils/storage/Storage.java new file mode 100644 index 0000000..7cc9207 --- /dev/null +++ b/src/main/java/com/songoda/epicheads/utils/storage/Storage.java @@ -0,0 +1,44 @@ +package com.songoda.epicheads.utils.storage; + +import com.songoda.epicheads.EpicHeads; +import com.songoda.epicheads.players.EPlayer; +import com.songoda.epicheads.utils.ConfigWrapper; + +import java.util.List; + +public abstract class Storage { + + protected final EpicHeads instance; + protected final ConfigWrapper dataFile; + + public Storage(EpicHeads instance) { + this.instance = instance; + this.dataFile = new ConfigWrapper(instance, "", "data.yml"); + this.dataFile.createNewFile(null, "EpicSpawners Data File"); + this.dataFile.getConfig().options().copyDefaults(true); + this.dataFile.saveConfig(); + } + + public abstract boolean containsGroup(String group); + + public abstract List getRowsByGroup(String group); + + public abstract void prepareSaveItem(String group, StorageItem... items); + + public void updateData(EpicHeads plugin) { + // Save game data + for (EPlayer player : instance.getPlayerManager().getPlayers()) { + prepareSaveItem("players", new StorageItem("uuid", player.getUuid().toString()), + new StorageItem("favorites", player.getFavorites())); + } + } + + public abstract void doSave(); + + public abstract void save(); + + public abstract void makeBackup(); + + public abstract void closeConnection(); + +} diff --git a/src/main/java/com/songoda/epicheads/utils/storage/StorageItem.java b/src/main/java/com/songoda/epicheads/utils/storage/StorageItem.java new file mode 100644 index 0000000..2b9ca86 --- /dev/null +++ b/src/main/java/com/songoda/epicheads/utils/storage/StorageItem.java @@ -0,0 +1,62 @@ +package com.songoda.epicheads.utils.storage; + +import java.util.ArrayList; +import java.util.List; + +public class StorageItem { + + private final Object object; + private String key = null; + + public StorageItem(Object object) { + this.object = object; + } + + public StorageItem(String key, Object object) { + this.key = key; + this.object = object; + } + + public StorageItem(String key, List integers) { + String object = ""; + for (Integer i : integers) { + object += i + ";"; + } + this.key = key; + this.object = object; + } + + public String getKey() { + return key; + } + + public String asString() { + if (object == null) return null; + return (String) object; + } + + public boolean asBoolean() { + if (object == null) return false; + return (boolean) object; + } + + public int asInt() { + if (object == null) return 0; + return (int) object; + } + + public Object asObject() { + return object; + } + + public List asIntList() { + List list = new ArrayList<>(); + if (object == null) return list; + String[] stack = ((String) object).split(";"); + for (String item : stack) { + if (item.equals("")) continue; + list.add(Integer.valueOf(item)); + } + return list; + } +} diff --git a/src/main/java/com/songoda/epicheads/utils/storage/StorageRow.java b/src/main/java/com/songoda/epicheads/utils/storage/StorageRow.java new file mode 100644 index 0000000..fbe9e9b --- /dev/null +++ b/src/main/java/com/songoda/epicheads/utils/storage/StorageRow.java @@ -0,0 +1,24 @@ +package com.songoda.epicheads.utils.storage; + +import java.util.Map; + +public class StorageRow { + + private final String key; + + private final Map items; + + public StorageRow(String key, Map items) { + this.key = key; + this.items = items; + } + + public String getKey() { + return key; + } + + public StorageItem get(String key) { + if (!items.containsKey(key) || items.get(key).asObject().toString().equals("")) return new StorageItem(null); + return items.get(key); + } +} diff --git a/src/main/java/com/songoda/epicheads/utils/storage/types/StorageYaml.java b/src/main/java/com/songoda/epicheads/utils/storage/types/StorageYaml.java new file mode 100644 index 0000000..176a6b5 --- /dev/null +++ b/src/main/java/com/songoda/epicheads/utils/storage/types/StorageYaml.java @@ -0,0 +1,147 @@ +package com.songoda.epicheads.utils.storage.types; + +import com.songoda.epicheads.EpicHeads; +import com.songoda.epicheads.utils.storage.Storage; +import com.songoda.epicheads.utils.storage.StorageItem; +import com.songoda.epicheads.utils.storage.StorageRow; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.MemorySection; + +import java.io.*; +import java.util.*; + +public class StorageYaml extends Storage { + + private static final Map toSave = new HashMap<>(); + private static Map lastSave = null; + + public StorageYaml(EpicHeads instance) { + super(instance); + } + + @Override + public boolean containsGroup(String group) { + return dataFile.getConfig().contains("data." + group); + } + + @Override + public List getRowsByGroup(String group) { + List rows = new ArrayList<>(); + ConfigurationSection currentSection = dataFile.getConfig().getConfigurationSection("data." + group); + for (String key : currentSection.getKeys(false)) { + + Map items = new HashMap<>(); + ConfigurationSection currentSection2 = dataFile.getConfig().getConfigurationSection("data." + group + "." + key); + for (String key2 : currentSection2.getKeys(false)) { + String path = "data." + group + "." + key + "." + key2; + items.put(key2, new StorageItem(dataFile.getConfig().get(path) instanceof MemorySection + ? convertToInLineList(path) : dataFile.getConfig().get(path))); + } + if (items.isEmpty()) continue; + StorageRow row = new StorageRow(key, items); + rows.add(row); + } + return rows; + } + + private String convertToInLineList(String path) { + StringBuilder converted = new StringBuilder(); + for (String key : dataFile.getConfig().getConfigurationSection(path).getKeys(false)) { + converted.append(key).append(":").append(dataFile.getConfig().getInt(path + "." + key)).append(";"); + } + return converted.toString(); + } + + @Override + public void prepareSaveItem(String group, StorageItem... items) { + for (StorageItem item : items) { + if (item == null || item.asObject() == null) continue; + toSave.put("data." + group + "." + items[0].asString() + "." + item.getKey(), item.asObject()); + } + } + + @Override + public void doSave() { + this.updateData(instance); + + if (lastSave == null) + lastSave = new HashMap<>(toSave); + + if (toSave.isEmpty()) return; + Map nextSave = new HashMap<>(toSave); + + this.makeBackup(); + this.save(); + + toSave.clear(); + lastSave.clear(); + lastSave.putAll(nextSave); + } + + @Override + public void save() { + try { + for (Map.Entry entry : lastSave.entrySet()) { + if (toSave.containsKey(entry.getKey())) { + Object newValue = toSave.get(entry.getKey()); + if (!entry.getValue().equals(newValue)) { + dataFile.getConfig().set(entry.getKey(), newValue); + } + toSave.remove(entry.getKey()); + } else { + dataFile.getConfig().set(entry.getKey(), null); + } + } + + for (Map.Entry entry : toSave.entrySet()) { + dataFile.getConfig().set(entry.getKey(), entry.getValue()); + } + + dataFile.saveConfig(); + } catch (NullPointerException e) { + e.printStackTrace(); + } + } + + @Override + public void makeBackup() { + File data = new File(instance.getDataFolder(), "data.yml"); + File dataClone = new File(instance.getDataFolder(), "data-backup-" + System.currentTimeMillis() + ".yml"); + try { + copyFile(data, dataClone); + } catch (IOException e) { + e.printStackTrace(); + } + Deque backups = new ArrayDeque<>(); + for (File file : Objects.requireNonNull(instance.getDataFolder().listFiles())) { + if (file.getName().toLowerCase().contains("data-backup")) { + backups.add(file); + } + } + if (backups.size() > 3) { + backups.getFirst().delete(); + } + } + + @Override + public void closeConnection() { + dataFile.saveConfig(); + } + + private static void copyFile(File source, File dest) throws IOException { + InputStream is = null; + OutputStream os = null; + try { + is = new FileInputStream(source); + os = new FileOutputStream(dest); + byte[] buffer = new byte[1024]; + int length; + while ((length = is.read(buffer)) > 0) { + os.write(buffer, 0, length); + } + } finally { + is.close(); + os.close(); + } + } +} diff --git a/src/main/resources/en_US.lang b/src/main/resources/en_US.lang index 6c4f95a..f17e475 100644 --- a/src/main/resources/en_US.lang +++ b/src/main/resources/en_US.lang @@ -1,3 +1,30 @@ #General Messages -general.nametag.prefix = "&7[&6EpicHeads&7]" \ No newline at end of file +general.nametag.prefix = "&7[&6EpicHeads&7]" +general.word.page = "Page" +general.word.query = "Query" +general.word.favorites = "Favorites" + +general.search.global = "&6Enter your search query." +general.search.refine = "&6Enter a search term to refine your search.") +general.search.nonefound = "&cNo heads founds.." + +general.head.staffpicked = "&8Staff Favorite"; +general.head.id = "&8ID: &7%id%" +general.head.cost = "&8Cost: &7$%cost%"; + +gui.heads.refine = "&9Refine Search" +gui.heads.search = "&9Create Search" +gui.heads.categories = "&cBack To Categories" + +gui.overview.title = "EpicHeads (%count% heads)" +gui.overview.search = "&a&lSearch" +gui.overview.headname = "&c&l%name%" +gui.overview.headlore = "&e%count% heads" +gui.overview.viewfavorites = "&6&lView Favorites" +gui.overview.favoriteslore = "&8Shift click any head|&8to save as a favorite." +gui.overview.discord = "&9&lDiscord" +gui.overview.discordlore = "&8Add or request new heads|&8in our discord server." + +event.general.nopermission = "&cYou do not have permission to use that command." +event.buyhead.cannotafford = "&cYou cannot afford this head." \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index f2f7407..dfedf0c 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -3,10 +3,11 @@ description: EpicHeads main: com.songoda.epicheads.EpicHeads version: maven-version-number author: Songoda +softdepend: [Vault] api-version: 1.13 commands: EpicHeads: description: View information on this plugin. default: true - aliases: [ehe, heads, head] + aliases: [ehe, heads] usage: /epicheads \ No newline at end of file