From 5bdca1ac678c6ca570253730d634a96cbb6af696 Mon Sep 17 00:00:00 2001 From: Silent Date: Tue, 1 Nov 2022 16:54:40 +0100 Subject: [PATCH 01/11] rewrite --- pom.xml | 56 +- src/main/java/tsp/headdb/HeadDB.java | 147 +-- src/main/java/tsp/headdb/Metrics.java | 861 +----------------- src/main/java/tsp/headdb/api/HeadAPI.java | 259 ------ .../headdb/api/event/DatabaseUpdateEvent.java | 54 -- .../api/event/PlayerHeadPurchaseEvent.java | 69 -- .../java/tsp/headdb/command/GiveCommand.java | 53 -- .../tsp/headdb/command/HeadDBCommand.java | 71 -- .../tsp/headdb/command/HeadSubCommand.java | 21 - .../java/tsp/headdb/command/InfoCommand.java | 18 - .../tsp/headdb/command/ReloadCommand.java | 20 - .../tsp/headdb/command/SearchCommand.java | 38 - .../tsp/headdb/command/TagSearchCommand.java | 32 - .../tsp/headdb/command/UpdateCommand.java | 26 - .../java/tsp/headdb/core/api/HeadAPI.java | 33 + .../tsp/headdb/core/command/CommandMain.java | 86 ++ .../headdb/core/command/CommandManager.java | 22 + .../headdb/core/command/HeadDBCommand.java | 38 + .../tsp/headdb/core/command/SubCommand.java | 9 + .../tsp/headdb/core/storage/PlayerData.java | 6 + .../headdb/core/storage/PlayerStorage.java | 38 + .../headdb/core/task/DatabaseUpdateTask.java | 25 + src/main/java/tsp/headdb/core/util/Utils.java | 16 + .../headdb/economy/BasicEconomyProvider.java | 48 - .../tsp/headdb/economy/TreasuryProvider.java | 98 -- .../tsp/headdb/economy/VaultProvider.java | 52 -- .../tsp/headdb/implementation/Category.java | 91 -- .../headdb/implementation/DataSaveTask.java | 11 - .../implementation/DatabaseUpdateTask.java | 16 - .../java/tsp/headdb/implementation/Head.java | 142 --- .../headdb/implementation/HeadDatabase.java | 298 ------ .../tsp/headdb/implementation/LocalHead.java | 87 -- .../implementation/category/Category.java | 55 ++ .../tsp/headdb/implementation/head/Head.java | 49 + .../implementation/head/HeadDatabase.java | 75 ++ .../implementation/requester/Requester.java | 77 ++ .../implementation/requester/Response.java | 3 + .../java/tsp/headdb/inventory/Button.java | 78 -- .../tsp/headdb/inventory/InventoryUtils.java | 422 --------- .../java/tsp/headdb/inventory/PagedPane.java | 386 -------- .../tsp/headdb/listener/JoinListener.java | 28 - .../tsp/headdb/listener/MenuListener.java | 104 --- .../headdb/listener/PagedPaneListener.java | 27 - .../tsp/headdb/storage/PlayerDataFile.java | 149 --- .../java/tsp/headdb/util/Localization.java | 49 - src/main/java/tsp/headdb/util/Log.java | 95 -- src/main/java/tsp/headdb/util/Utils.java | 89 -- src/main/resources/messages.yml | 31 - src/main/resources/messages/en.yml | 12 + src/main/resources/player_data.json | 1 - src/main/resources/plugin.yml | 6 +- 51 files changed, 619 insertions(+), 3958 deletions(-) delete mode 100644 src/main/java/tsp/headdb/api/HeadAPI.java delete mode 100644 src/main/java/tsp/headdb/api/event/DatabaseUpdateEvent.java delete mode 100644 src/main/java/tsp/headdb/api/event/PlayerHeadPurchaseEvent.java delete mode 100644 src/main/java/tsp/headdb/command/GiveCommand.java delete mode 100644 src/main/java/tsp/headdb/command/HeadDBCommand.java delete mode 100644 src/main/java/tsp/headdb/command/HeadSubCommand.java delete mode 100644 src/main/java/tsp/headdb/command/InfoCommand.java delete mode 100644 src/main/java/tsp/headdb/command/ReloadCommand.java delete mode 100644 src/main/java/tsp/headdb/command/SearchCommand.java delete mode 100644 src/main/java/tsp/headdb/command/TagSearchCommand.java delete mode 100644 src/main/java/tsp/headdb/command/UpdateCommand.java create mode 100644 src/main/java/tsp/headdb/core/api/HeadAPI.java create mode 100644 src/main/java/tsp/headdb/core/command/CommandMain.java create mode 100644 src/main/java/tsp/headdb/core/command/CommandManager.java create mode 100644 src/main/java/tsp/headdb/core/command/HeadDBCommand.java create mode 100644 src/main/java/tsp/headdb/core/command/SubCommand.java create mode 100644 src/main/java/tsp/headdb/core/storage/PlayerData.java create mode 100644 src/main/java/tsp/headdb/core/storage/PlayerStorage.java create mode 100644 src/main/java/tsp/headdb/core/task/DatabaseUpdateTask.java create mode 100644 src/main/java/tsp/headdb/core/util/Utils.java delete mode 100644 src/main/java/tsp/headdb/economy/BasicEconomyProvider.java delete mode 100644 src/main/java/tsp/headdb/economy/TreasuryProvider.java delete mode 100644 src/main/java/tsp/headdb/economy/VaultProvider.java delete mode 100644 src/main/java/tsp/headdb/implementation/Category.java delete mode 100644 src/main/java/tsp/headdb/implementation/DataSaveTask.java delete mode 100644 src/main/java/tsp/headdb/implementation/DatabaseUpdateTask.java delete mode 100644 src/main/java/tsp/headdb/implementation/Head.java delete mode 100644 src/main/java/tsp/headdb/implementation/HeadDatabase.java delete mode 100644 src/main/java/tsp/headdb/implementation/LocalHead.java create mode 100644 src/main/java/tsp/headdb/implementation/category/Category.java create mode 100644 src/main/java/tsp/headdb/implementation/head/Head.java create mode 100644 src/main/java/tsp/headdb/implementation/head/HeadDatabase.java create mode 100644 src/main/java/tsp/headdb/implementation/requester/Requester.java create mode 100644 src/main/java/tsp/headdb/implementation/requester/Response.java delete mode 100644 src/main/java/tsp/headdb/inventory/Button.java delete mode 100644 src/main/java/tsp/headdb/inventory/InventoryUtils.java delete mode 100644 src/main/java/tsp/headdb/inventory/PagedPane.java delete mode 100644 src/main/java/tsp/headdb/listener/JoinListener.java delete mode 100644 src/main/java/tsp/headdb/listener/MenuListener.java delete mode 100644 src/main/java/tsp/headdb/listener/PagedPaneListener.java delete mode 100644 src/main/java/tsp/headdb/storage/PlayerDataFile.java delete mode 100644 src/main/java/tsp/headdb/util/Localization.java delete mode 100644 src/main/java/tsp/headdb/util/Log.java delete mode 100644 src/main/java/tsp/headdb/util/Utils.java delete mode 100644 src/main/resources/messages.yml create mode 100644 src/main/resources/messages/en.yml delete mode 100644 src/main/resources/player_data.json diff --git a/pom.xml b/pom.xml index d42eee7..aa23910 100644 --- a/pom.xml +++ b/pom.xml @@ -6,38 +6,30 @@ tsp.headdb HeadDB - 4.4.4 + 5.0.0 jar - TheSilentPro_HeadDB - silent - https://sonarcloud.io - src/main/java/tsp/headdb/Metrics.java - * - 84967 + 17 + 17 HeadDB Database with thousands of heads - spigot-repo https://hub.spigotmc.org/nexus/content/repositories/snapshots/ - mojang-repo https://libraries.minecraft.net/ - jitpack.io https://jitpack.io - CodeMC https://repo.codemc.org/repository/maven-public @@ -49,34 +41,50 @@ - + org.spigotmc spigot-api - 1.19-R0.1-SNAPSHOT + 1.19.2-R0.1-SNAPSHOT provided - com.mojang authlib 1.5.21 provided - + + com.google.code.gson + gson + 2.10 + provided + + + net.wesjd anvilgui 1.5.3-SNAPSHOT - + + com.github.TheSilentPro + SmartPlugin + efc139dd46 + + + com.github.TheSilentPro + Warehouse + 2ae4ef1f97 + + + com.github.MilkBowl VaultAPI 1.7 provided - me.lokka30 treasury-api @@ -105,10 +113,6 @@ org.apache.maven.plugins maven-compiler-plugin 3.8.1 - - 16 - 16 - @@ -139,16 +143,6 @@ maven-javadoc-plugin 3.3.2 - - - - org.codehaus.mojo - sonar-maven-plugin - 3.9.1.2184 - - sonar - - diff --git a/src/main/java/tsp/headdb/HeadDB.java b/src/main/java/tsp/headdb/HeadDB.java index 3d81696..2fc5e6d 100644 --- a/src/main/java/tsp/headdb/HeadDB.java +++ b/src/main/java/tsp/headdb/HeadDB.java @@ -1,126 +1,69 @@ package tsp.headdb; -import org.bukkit.Bukkit; -import org.bukkit.plugin.java.JavaPlugin; -import tsp.headdb.api.HeadAPI; -import tsp.headdb.command.HeadDBCommand; -import tsp.headdb.economy.TreasuryProvider; -import tsp.headdb.implementation.DataSaveTask; -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; -import tsp.headdb.listener.PagedPaneListener; -import tsp.headdb.storage.PlayerDataFile; -import tsp.headdb.util.Localization; -import tsp.headdb.util.Log; -import tsp.headdb.util.Utils; +import tsp.headdb.core.command.CommandMain; +import tsp.headdb.core.command.CommandManager; +import tsp.headdb.core.task.DatabaseUpdateTask; +import tsp.smartplugin.SmartPlugin; +import tsp.smartplugin.inventory.PaneListener; +import tsp.smartplugin.localization.TranslatableLocalization; +import tsp.smartplugin.logger.AbstractLogger; +import tsp.smartplugin.logger.PluginLogger; -import javax.annotation.Nullable; -import java.io.File; +import java.io.IOException; +import java.net.URISyntaxException; -/** - * Main class of HeadDB - */ -public class HeadDB extends JavaPlugin { +public class HeadDB extends SmartPlugin { - private static HeadDB instance; - private BasicEconomyProvider economyProvider; - private PlayerDataFile playerData; - private Localization localization; + private static final HeadDB instance = new HeadDB(); + private AbstractLogger logger; + private TranslatableLocalization localization; + private CommandManager commandManager; @Override - public void onEnable() { - instance = this; - Log.info("Loading HeadDB - " + getDescription().getVersion()); - saveDefaultConfig(); - createLocalizationFile(); + public void onStart() { + instance.logger = new PluginLogger(this, getConfig().getBoolean("debug")); + instance.logger.info("Loading HeadDB - " + instance.getDescription().getVersion()); + instance.saveDefaultConfig(); - this.playerData = new PlayerDataFile("player_data.json"); - this.playerData.load(); + new PaneListener(this); - if (getConfig().getBoolean("economy.enable")) { - String rawProvider = getConfig().getString("economy.provider", "VAULT"); - Log.debug("Starting economy with provider: " + rawProvider); - if (rawProvider.equalsIgnoreCase("vault")) { - economyProvider = new VaultProvider(); - economyProvider.initProvider(); - } else if (rawProvider.equalsIgnoreCase("treasury")) { - economyProvider = new TreasuryProvider(); - economyProvider.initProvider(); - } + new DatabaseUpdateTask(getConfig().getLong("refresh")); + + instance.logger.info("Loaded " + loadLocalization() + " languages!"); + + //noinspection ConstantConditions + instance.getCommand("headdb").setExecutor(new CommandMain()); + + instance.logger.info("Done!"); + } + + private int loadLocalization() { + instance.localization = new TranslatableLocalization(this, "messages"); + try { + instance.localization.createDefaults(); + return instance.localization.load(); + } catch (URISyntaxException | IOException ex) { + instance.logger.error("Failed to load localization!"); + ex.printStackTrace(); + this.setEnabled(false); + return 0; } - - long refresh = getConfig().getLong("refresh") * 20; - HeadAPI.getDatabase().setRefresh(refresh); - Bukkit.getScheduler().runTaskTimerAsynchronously(this, new DatabaseUpdateTask(), 0, refresh); - Bukkit.getScheduler().runTaskTimerAsynchronously(this, new DataSaveTask(), refresh, refresh); - - new JoinListener(this); - new MenuListener(this); - new PagedPaneListener(this); - - getCommand("headdb").setExecutor(new HeadDBCommand()); - - Log.debug("Starting metrics..."); - initMetrics(); - - Utils.isLatestVersion(this, 84967, latest -> { - if (!Boolean.TRUE.equals(latest)) { - Log.warning("There is a new update available for HeadDB on spigot!"); - Log.warning("Download: https://www.spigotmc.org/resources/84967"); - } - }); - - Log.info("Done!"); } - @Override - public void onDisable() { - Bukkit.getScheduler().cancelTasks(this); - this.playerData.save(); + public CommandManager getCommandManager() { + return commandManager; } - public Localization getLocalization() { + public TranslatableLocalization getLocalization() { return localization; } - public PlayerDataFile getPlayerData() { - return playerData; - } - - @Nullable - public BasicEconomyProvider getEconomyProvider() { - return economyProvider; + public AbstractLogger getLog() { + return logger; } public static HeadDB getInstance() { return instance; } - private void createLocalizationFile() { - File messagesFile = new File(getDataFolder().getAbsolutePath() + "/messages.yml"); - if (!messagesFile.exists()) { - saveResource("messages.yml", false); - Log.debug("Localization loaded from jar file."); - } - - this.localization = new Localization(messagesFile); - this.localization.load(); - } - - private void initMetrics() { - Metrics metrics = new Metrics(this, 9152); - - metrics.addCustomChart(new Metrics.SimplePie("economy_provider", () -> { - if (this.getEconomyProvider() != null) { - return this.getConfig().getString("economy.provider"); - } - - return "None"; - })); - } - - } diff --git a/src/main/java/tsp/headdb/Metrics.java b/src/main/java/tsp/headdb/Metrics.java index 2016398..aae2780 100644 --- a/src/main/java/tsp/headdb/Metrics.java +++ b/src/main/java/tsp/headdb/Metrics.java @@ -1,863 +1,6 @@ -/* - * This Metrics class was auto-generated and can be copied into your project if you are - * not using a build tool like Gradle or Maven for dependency management. - * - * IMPORTANT: You are not allowed to modify this class, except changing the package. - * - * Unallowed modifications include but are not limited to: - * - Remove the option for users to opt-out - * - Change the frequency for data submission - * - Obfuscate the code (every obfucator should allow you to make an exception for specific files) - * - Reformat the code (if you use a linter, add an exception) - * - * Violations will result in a ban of your plugin and account from bStats. - */ package tsp.headdb; -import java.io.BufferedReader; -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.lang.reflect.Method; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Supplier; -import java.util.logging.Level; -import java.util.stream.Collectors; -import java.util.zip.GZIPOutputStream; -import javax.net.ssl.HttpsURLConnection; -import org.bukkit.Bukkit; -import org.bukkit.configuration.file.YamlConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.plugin.Plugin; -import org.bukkit.plugin.java.JavaPlugin; +class Metrics { + -public class Metrics { - - private final Plugin plugin; - - private final MetricsBase metricsBase; - - /** - * Creates a new Metrics instance. - * - * @param plugin Your plugin instance. - * @param serviceId The id of the service. It can be found at What is my plugin id? - */ - public Metrics(JavaPlugin plugin, int serviceId) { - 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); - if (!config.isSet("serverUuid")) { - config.addDefault("enabled", true); - config.addDefault("serverUuid", UUID.randomUUID().toString()); - config.addDefault("logFailedRequests", false); - config.addDefault("logSentData", false); - config.addDefault("logResponseStatusText", false); - // Inform the server owners about bStats - config - .options() - .header( - "bStats (https://bStats.org) collects some basic information for plugin authors, like how\n" - + "many people use their plugin and their total player count. It's recommended to keep bStats\n" - + "enabled, but if you're not comfortable with this, you can turn this setting off. There is no\n" - + "performance penalty associated with having metrics enabled, and data sent to bStats is fully\n" - + "anonymous.") - .copyDefaults(true); - try { - config.save(configFile); - } catch (IOException ignored) { - } - } - // Load the data - boolean enabled = config.getBoolean("enabled", true); - String serverUUID = config.getString("serverUuid"); - boolean logErrors = config.getBoolean("logFailedRequests", false); - boolean logSentData = config.getBoolean("logSentData", false); - boolean logResponseStatusText = config.getBoolean("logResponseStatusText", false); - metricsBase = - new MetricsBase( - "bukkit", - serverUUID, - serviceId, - enabled, - this::appendPlatformData, - this::appendServiceData, - submitDataTask -> Bukkit.getScheduler().runTask(plugin, submitDataTask), - plugin::isEnabled, - (message, error) -> this.plugin.getLogger().log(Level.WARNING, message, error), - (message) -> this.plugin.getLogger().log(Level.INFO, message), - logErrors, - logSentData, - logResponseStatusText); - } - - /** - * Adds a custom chart. - * - * @param chart The chart to add. - */ - public void addCustomChart(CustomChart chart) { - metricsBase.addCustomChart(chart); - } - - private void appendPlatformData(JsonObjectBuilder builder) { - builder.appendField("playerAmount", getPlayerAmount()); - builder.appendField("onlineMode", Bukkit.getOnlineMode() ? 1 : 0); - builder.appendField("bukkitVersion", Bukkit.getVersion()); - builder.appendField("bukkitName", Bukkit.getName()); - builder.appendField("javaVersion", System.getProperty("java.version")); - builder.appendField("osName", System.getProperty("os.name")); - builder.appendField("osArch", System.getProperty("os.arch")); - builder.appendField("osVersion", System.getProperty("os.version")); - builder.appendField("coreCount", Runtime.getRuntime().availableProcessors()); - } - - private void appendServiceData(JsonObjectBuilder builder) { - builder.appendField("pluginVersion", plugin.getDescription().getVersion()); - } - - private int getPlayerAmount() { - try { - // Around MC 1.8 the return type was changed from an array to a collection, - // This fixes java.lang.NoSuchMethodError: - // org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection; - Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers"); - return onlinePlayersMethod.getReturnType().equals(Collection.class) - ? ((Collection) onlinePlayersMethod.invoke(Bukkit.getServer())).size() - : ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length; - } catch (Exception e) { - // Just use the new method if the reflection failed - return Bukkit.getOnlinePlayers().size(); - } - } - - public static class MetricsBase { - - /** The version of the Metrics class. */ - public static final String METRICS_VERSION = "3.0.0"; - - private static final ScheduledExecutorService scheduler = - Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics")); - - private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s"; - - private final String platform; - - private final String serverUuid; - - private final int serviceId; - - private final Consumer appendPlatformDataConsumer; - - private final Consumer appendServiceDataConsumer; - - private final Consumer submitTaskConsumer; - - private final Supplier checkServiceEnabledSupplier; - - private final BiConsumer errorLogger; - - private final Consumer infoLogger; - - private final boolean logErrors; - - private final boolean logSentData; - - private final boolean logResponseStatusText; - - private final Set customCharts = new HashSet<>(); - - private final boolean enabled; - - /** - * Creates a new MetricsBase class instance. - * - * @param platform The platform of the service. - * @param serviceId The id of the service. - * @param serverUuid The server uuid. - * @param enabled Whether or not data sending is enabled. - * @param appendPlatformDataConsumer A consumer that receives a {@code JsonObjectBuilder} and - * appends all platform-specific data. - * @param appendServiceDataConsumer A consumer that receives a {@code JsonObjectBuilder} and - * appends all service-specific data. - * @param submitTaskConsumer A consumer that takes a runnable with the submit task. This can be - * used to delegate the data collection to a another thread to prevent errors caused by - * concurrency. Can be {@code null}. - * @param checkServiceEnabledSupplier A supplier to check if the service is still enabled. - * @param errorLogger A consumer that accepts log message and an error. - * @param infoLogger A consumer that accepts info log messages. - * @param logErrors Whether or not errors should be logged. - * @param logSentData Whether or not the sent data should be logged. - * @param logResponseStatusText Whether or not the response status text should be logged. - */ - public MetricsBase( - String platform, - String serverUuid, - int serviceId, - boolean enabled, - Consumer appendPlatformDataConsumer, - Consumer appendServiceDataConsumer, - Consumer submitTaskConsumer, - Supplier checkServiceEnabledSupplier, - BiConsumer errorLogger, - Consumer infoLogger, - boolean logErrors, - boolean logSentData, - boolean logResponseStatusText) { - this.platform = platform; - this.serverUuid = serverUuid; - this.serviceId = serviceId; - this.enabled = enabled; - this.appendPlatformDataConsumer = appendPlatformDataConsumer; - this.appendServiceDataConsumer = appendServiceDataConsumer; - this.submitTaskConsumer = submitTaskConsumer; - this.checkServiceEnabledSupplier = checkServiceEnabledSupplier; - this.errorLogger = errorLogger; - this.infoLogger = infoLogger; - this.logErrors = logErrors; - this.logSentData = logSentData; - this.logResponseStatusText = logResponseStatusText; - checkRelocation(); - if (enabled) { - // WARNING: Removing the option to opt-out will get your plugin banned from bStats - startSubmitting(); - } - } - - public void addCustomChart(CustomChart chart) { - this.customCharts.add(chart); - } - - private void startSubmitting() { - final Runnable submitTask = - () -> { - if (!enabled || !checkServiceEnabledSupplier.get()) { - // Submitting data or service is disabled - scheduler.shutdown(); - return; - } - if (submitTaskConsumer != null) { - submitTaskConsumer.accept(this::submitData); - } else { - this.submitData(); - } - }; - // Many servers tend to restart at a fixed time at xx:00 which causes an uneven distribution - // of requests on the - // bStats backend. To circumvent this problem, we introduce some randomness into the initial - // and second delay. - // WARNING: You must not modify and part of this Metrics class, including the submit delay or - // frequency! - // WARNING: Modifying this code will get your plugin banned on bStats. Just don't do it! - long initialDelay = (long) (1000 * 60 * (3 + Math.random() * 3)); - long secondDelay = (long) (1000 * 60 * (Math.random() * 30)); - scheduler.schedule(submitTask, initialDelay, TimeUnit.MILLISECONDS); - scheduler.scheduleAtFixedRate( - submitTask, initialDelay + secondDelay, 1000 * 60 * 30, TimeUnit.MILLISECONDS); - } - - private void submitData() { - final JsonObjectBuilder baseJsonBuilder = new JsonObjectBuilder(); - appendPlatformDataConsumer.accept(baseJsonBuilder); - final JsonObjectBuilder serviceJsonBuilder = new JsonObjectBuilder(); - appendServiceDataConsumer.accept(serviceJsonBuilder); - JsonObjectBuilder.JsonObject[] chartData = - customCharts.stream() - .map(customChart -> customChart.getRequestJsonObject(errorLogger, logErrors)) - .filter(Objects::nonNull) - .toArray(JsonObjectBuilder.JsonObject[]::new); - serviceJsonBuilder.appendField("id", serviceId); - serviceJsonBuilder.appendField("customCharts", chartData); - baseJsonBuilder.appendField("service", serviceJsonBuilder.build()); - baseJsonBuilder.appendField("serverUUID", serverUuid); - baseJsonBuilder.appendField("metricsVersion", METRICS_VERSION); - JsonObjectBuilder.JsonObject data = baseJsonBuilder.build(); - scheduler.execute( - () -> { - try { - // Send the data - sendData(data); - } catch (Exception e) { - // Something went wrong! :( - if (logErrors) { - errorLogger.accept("Could not submit bStats metrics data", e); - } - } - }); - } - - private void sendData(JsonObjectBuilder.JsonObject data) throws Exception { - if (logSentData) { - infoLogger.accept("Sent bStats metrics data: " + data.toString()); - } - String url = String.format(REPORT_URL, platform); - HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); - // Compress the data to save bandwidth - byte[] compressedData = compress(data.toString()); - connection.setRequestMethod("POST"); - connection.addRequestProperty("Accept", "application/json"); - connection.addRequestProperty("Connection", "close"); - connection.addRequestProperty("Content-Encoding", "gzip"); - connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length)); - connection.setRequestProperty("Content-Type", "application/json"); - connection.setRequestProperty("User-Agent", "Metrics-Service/1"); - connection.setDoOutput(true); - try (DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream())) { - outputStream.write(compressedData); - } - StringBuilder builder = new StringBuilder(); - try (BufferedReader bufferedReader = - new BufferedReader(new InputStreamReader(connection.getInputStream()))) { - String line; - while ((line = bufferedReader.readLine()) != null) { - builder.append(line); - } - } - if (logResponseStatusText) { - infoLogger.accept("Sent data to bStats and received response: " + builder); - } - } - - /** Checks that the class was properly relocated. */ - private void checkRelocation() { - // 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'}); - final String examplePackage = - new String(new byte[] {'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'}); - // We want to make sure no one just copy & pastes the example and uses the wrong package - // names - if (MetricsBase.class.getPackage().getName().startsWith(defaultPackage) - || MetricsBase.class.getPackage().getName().startsWith(examplePackage)) { - throw new IllegalStateException("bStats Metrics class has not been relocated correctly!"); - } - } - } - - /** - * Gzips the given string. - * - * @param str The string to gzip. - * @return The gzipped string. - */ - private static byte[] compress(final String str) throws IOException { - if (str == null) { - return null; - } - ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); - try (GZIPOutputStream gzip = new GZIPOutputStream(outputStream)) { - gzip.write(str.getBytes(StandardCharsets.UTF_8)); - } - return outputStream.toByteArray(); - } - } - - 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 JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map> map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - boolean reallyAllSkipped = true; - for (Map.Entry> entryValues : map.entrySet()) { - JsonObjectBuilder valueBuilder = new JsonObjectBuilder(); - boolean allSkipped = true; - for (Map.Entry valueEntry : map.get(entryValues.getKey()).entrySet()) { - valueBuilder.appendField(valueEntry.getKey(), valueEntry.getValue()); - allSkipped = false; - } - if (!allSkipped) { - reallyAllSkipped = false; - valuesBuilder.appendField(entryValues.getKey(), valueBuilder.build()); - } - } - if (reallyAllSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - 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 JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - 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) { - // Skip this invalid - continue; - } - allSkipped = false; - valuesBuilder.appendField(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - 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 JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - 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) { - // Skip this invalid - continue; - } - allSkipped = false; - valuesBuilder.appendField(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - 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 JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - Map map = callable.call(); - if (map == null || map.isEmpty()) { - // Null = skip the chart - return null; - } - for (Map.Entry entry : map.entrySet()) { - valuesBuilder.appendField(entry.getKey(), new int[] {entry.getValue()}); - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - public abstract static class CustomChart { - - private final String chartId; - - protected CustomChart(String chartId) { - if (chartId == null) { - throw new IllegalArgumentException("chartId must not be null"); - } - this.chartId = chartId; - } - - public JsonObjectBuilder.JsonObject getRequestJsonObject( - BiConsumer errorLogger, boolean logErrors) { - JsonObjectBuilder builder = new JsonObjectBuilder(); - builder.appendField("chartId", chartId); - try { - JsonObjectBuilder.JsonObject data = getChartData(); - if (data == null) { - // If the data is null we don't send the chart. - return null; - } - builder.appendField("data", data); - } catch (Throwable t) { - if (logErrors) { - errorLogger.accept("Failed to get data for custom chart with id " + chartId, t); - } - return null; - } - return builder.build(); - } - - protected abstract JsonObjectBuilder.JsonObject getChartData() throws Exception; - } - - 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 JsonObjectBuilder.JsonObject getChartData() throws Exception { - String value = callable.call(); - if (value == null || value.isEmpty()) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("value", value).build(); - } - } - - 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 JsonObjectBuilder.JsonObject getChartData() throws Exception { - JsonObjectBuilder valuesBuilder = new JsonObjectBuilder(); - 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) { - // Skip this invalid - continue; - } - allSkipped = false; - valuesBuilder.appendField(entry.getKey(), entry.getValue()); - } - if (allSkipped) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("values", valuesBuilder.build()).build(); - } - } - - 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 JsonObjectBuilder.JsonObject getChartData() throws Exception { - int value = callable.call(); - if (value == 0) { - // Null = skip the chart - return null; - } - return new JsonObjectBuilder().appendField("value", value).build(); - } - } - - /** - * An extremely simple JSON builder. - * - *

While this class is neither feature-rich nor the most performant one, it's sufficient enough - * for its use-case. - */ - public static class JsonObjectBuilder { - - private StringBuilder builder = new StringBuilder(); - - private boolean hasAtLeastOneField = false; - - public JsonObjectBuilder() { - builder.append("{"); - } - - /** - * Appends a null field to the JSON. - * - * @param key The key of the field. - * @return A reference to this object. - */ - public JsonObjectBuilder appendNull(String key) { - appendFieldUnescaped(key, "null"); - return this; - } - - /** - * Appends a string field to the JSON. - * - * @param key The key of the field. - * @param value The value of the field. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, String value) { - if (value == null) { - throw new IllegalArgumentException("JSON value must not be null"); - } - appendFieldUnescaped(key, "\"" + escape(value) + "\""); - return this; - } - - /** - * Appends an integer field to the JSON. - * - * @param key The key of the field. - * @param value The value of the field. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, int value) { - appendFieldUnescaped(key, String.valueOf(value)); - return this; - } - - /** - * Appends an object to the JSON. - * - * @param key The key of the field. - * @param object The object. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, JsonObject object) { - if (object == null) { - throw new IllegalArgumentException("JSON object must not be null"); - } - appendFieldUnescaped(key, object.toString()); - return this; - } - - /** - * Appends a string array to the JSON. - * - * @param key The key of the field. - * @param values The string array. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, String[] values) { - if (values == null) { - throw new IllegalArgumentException("JSON values must not be null"); - } - String escapedValues = - Arrays.stream(values) - .map(value -> "\"" + escape(value) + "\"") - .collect(Collectors.joining(",")); - appendFieldUnescaped(key, "[" + escapedValues + "]"); - return this; - } - - /** - * Appends an integer array to the JSON. - * - * @param key The key of the field. - * @param values The integer array. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, int[] values) { - if (values == null) { - throw new IllegalArgumentException("JSON values must not be null"); - } - String escapedValues = - Arrays.stream(values).mapToObj(String::valueOf).collect(Collectors.joining(",")); - appendFieldUnescaped(key, "[" + escapedValues + "]"); - return this; - } - - /** - * Appends an object array to the JSON. - * - * @param key The key of the field. - * @param values The integer array. - * @return A reference to this object. - */ - public JsonObjectBuilder appendField(String key, JsonObject[] values) { - if (values == null) { - throw new IllegalArgumentException("JSON values must not be null"); - } - String escapedValues = - Arrays.stream(values).map(JsonObject::toString).collect(Collectors.joining(",")); - appendFieldUnescaped(key, "[" + escapedValues + "]"); - return this; - } - - /** - * Appends a field to the object. - * - * @param key The key of the field. - * @param escapedValue The escaped value of the field. - */ - private void appendFieldUnescaped(String key, String escapedValue) { - if (builder == null) { - throw new IllegalStateException("JSON has already been built"); - } - if (key == null) { - throw new IllegalArgumentException("JSON key must not be null"); - } - if (hasAtLeastOneField) { - builder.append(","); - } - builder.append("\"").append(escape(key)).append("\":").append(escapedValue); - hasAtLeastOneField = true; - } - - /** - * Builds the JSON string and invalidates this builder. - * - * @return The built JSON string. - */ - public JsonObject build() { - if (builder == null) { - throw new IllegalStateException("JSON has already been built"); - } - JsonObject object = new JsonObject(builder.append("}").toString()); - builder = null; - return object; - } - - /** - * Escapes the given string like stated in https://www.ietf.org/rfc/rfc4627.txt. - * - *

This method escapes only the necessary characters '"', '\'. and '\u0000' - '\u001F'. - * Compact escapes are not used (e.g., '\n' is escaped as "\u000a" and not as "\n"). - * - * @param value The value to escape. - * @return The escaped value. - */ - private static String escape(String value) { - final StringBuilder builder = new StringBuilder(); - for (int i = 0; i < value.length(); i++) { - char c = value.charAt(i); - if (c == '"') { - builder.append("\\\""); - } else if (c == '\\') { - builder.append("\\\\"); - } else if (c <= '\u000F') { - builder.append("\\u000").append(Integer.toHexString(c)); - } else if (c <= '\u001F') { - builder.append("\\u00").append(Integer.toHexString(c)); - } else { - builder.append(c); - } - } - return builder.toString(); - } - - /** - * A super simple representation of a JSON object. - * - *

This class only exists to make methods of the {@link JsonObjectBuilder} type-safe and not - * allow a raw string inputs for methods like {@link JsonObjectBuilder#appendField(String, - * JsonObject)}. - */ - public static class JsonObject { - - private final String value; - - private JsonObject(String value) { - this.value = value; - } - - @Override - public String toString() { - return value; - } - } - } } \ No newline at end of file diff --git a/src/main/java/tsp/headdb/api/HeadAPI.java b/src/main/java/tsp/headdb/api/HeadAPI.java deleted file mode 100644 index a728f00..0000000 --- a/src/main/java/tsp/headdb/api/HeadAPI.java +++ /dev/null @@ -1,259 +0,0 @@ -package tsp.headdb.api; - -import org.apache.commons.lang.Validate; -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; -import tsp.headdb.HeadDB; -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; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.util.List; -import java.util.Objects; -import java.util.UUID; - -/** - * This class provides simple methods - * for interacting with the HeadDB plugin - * - * @author TheSilentPro - */ -// TODO: Optional instead of null. -// TODO: Remove stream, use loop. -public final class HeadAPI { - - private HeadAPI() {} - - private static final String VALIDATION_PLAYER_NULL = "Player can not be null!"; - private static final String VALIDATION_CATEGORY_NULL = "Category can not be null!"; - private static final String VALIDATION_UUID_NULL = "UUID can not be null!"; - private static final String VALIDATION_VALUE_NULL = "Value can not be null!"; - - /** - * Main {@link HeadDatabase} that he HeadDB plugin uses. - */ - private static final HeadDatabase database = new HeadDatabase(HeadDB.getInstance()); - - /** - * Retrieves the main {@link HeadDatabase} - * - * @return Head Database - */ - public static HeadDatabase getDatabase() { - return database; - } - - /** - * Opens the database for a player - * - * @param player Target player - */ - public static void openDatabase(@Nonnull Player player) { - Validate.notNull(player, VALIDATION_PLAYER_NULL); - - InventoryUtils.openDatabase(player); - } - - /** - * Opens a specific category of the database for a player - * - * @param player Target player - * @param category Category to open - */ - public static void openCategoryDatabase(@Nonnull Player player, @Nonnull Category category) { - Validate.notNull(player, VALIDATION_PLAYER_NULL); - Validate.notNull(category, VALIDATION_CATEGORY_NULL); - - InventoryUtils.openCategoryDatabase(player, category); - } - - /** - * Opens the database with results of a specific search term - * - * @param player Target player - * @param search Search term - */ - public static void openSearchDatabase(@Nonnull Player player, @Nonnull String search) { - Validate.notNull(player, VALIDATION_PLAYER_NULL); - Validate.notNull(search, "Search can not be null!"); - - InventoryUtils.openSearchDatabase(player, search); - } - - /** - * Opens the database with results of a specific tag search term - * - * @param player Target player - * @param tag Tag search term - */ - public static void openTagSearchDatabase(@Nonnull Player player, @Nonnull String tag) { - Validate.notNull(player, VALIDATION_PLAYER_NULL); - Validate.notNull(tag, "Tag can not be null!"); - - InventoryUtils.openTagSearchDatabase(player, tag); - } - - /** - * Retrieve a {@link Head} by it's ID - * - * @param id The ID of the head - * @return The head - */ - @Nullable - public static Head getHeadByID(int id) { - return database.getHeadByID(id); - } - - /** - * Retrieve a {@link Head} by it's UUID - * - * @param uuid The UUID of the head - * @return The head - */ - @Nullable - public static Head getHeadByUniqueId(@Nonnull UUID uuid) { - Validate.notNull(uuid, VALIDATION_UUID_NULL); - - return database.getHeadByUniqueId(uuid); - } - - /** - * Retrieve a {@link List} of {@link Head}'s by their tag - * - * @param tag The tag - * @return List of heads - */ - @Nonnull - public static List getHeadsByTag(@Nonnull String tag) { - Validate.notNull(tag, "Tag can not be null!"); - - return database.getHeadsByTag(tag); - } - - /** - * Retrieves a {@link List} of {@link Head}'s matching a name - * - * @param name The name to match for - * @return List of heads - */ - @Nonnull - public static List getHeadsByName(@Nonnull String name) { - Validate.notNull(name, "Name can not be null!"); - - return database.getHeadsByName(name); - } - - /** - * Retrieves a {@link List} of {@link Head}'s in a {@link Category} matching a name - * - * @param category The category to search in - * @param name The name to match for - * @return List of heads - */ - @Nonnull - public static List getHeadsByName(@Nonnull Category category, @Nonnull String name) { - Validate.notNull(category, VALIDATION_CATEGORY_NULL); - Validate.notNull(name, "Name can not be null!"); - - return database.getHeadsByName(category, name); - } - - /** - * Retrieve a {@link Head} by it's value - * - * @param value The texture value - * @return The head - */ - @Nullable - public static Head getHeadByValue(@Nonnull String value) { - Validate.notNull(value, VALIDATION_VALUE_NULL); - - return database.getHeadByValue(value); - } - - /** - * Retrieve a {@link List} of {@link Head}'s in a specific {@link Category} - * - * @param category The category to search in - * @return List of heads - */ - @Nonnull - public static List getHeads(@Nonnull Category category) { - Validate.notNull(category, VALIDATION_CATEGORY_NULL); - - return database.getHeads(category); - } - - /** - * Retrieve a {@link List} of all {@link Head}'s - * - * @return List of all heads - */ - @Nonnull - public static List getHeads() { - return database.getHeads(); - } - - /** - * Add a favorite {@link Head} to the player - * - * @param uuid The player's unique id - * @param textureValue The head's texture value - */ - public static void addFavoriteHead(@Nonnull UUID uuid, @Nonnull String textureValue) { - Validate.notNull(uuid, VALIDATION_UUID_NULL); - Validate.notNull(textureValue, VALIDATION_VALUE_NULL); - - HeadDB.getInstance().getPlayerData().modifyFavorite(uuid, textureValue, PlayerDataFile.ModificationType.SET); - } - - /** - * Remove a favorite {@link Head} from the player - * - * @param uuid The player's unique id - * @param textureValue The head's texture value - */ - public static void removeFavoriteHead(@Nonnull UUID uuid, @Nonnull String textureValue) { - Validate.notNull(uuid, VALIDATION_UUID_NULL); - Validate.notNull(textureValue, VALIDATION_VALUE_NULL); - - HeadDB.getInstance().getPlayerData().modifyFavorite(uuid, textureValue, PlayerDataFile.ModificationType.REMOVE); - } - - /** - * Retrieve a {@link List} of favorite {@link Head}'s for the player - * - * @param uuid The player's unique id - * @return List of favorite {@link Head}'s for the player - */ - @Nonnull - public static List getFavoriteHeads(@Nonnull UUID uuid) { - Validate.notNull(uuid, VALIDATION_UUID_NULL); - - return HeadDB.getInstance().getPlayerData().getFavoriteHeadsByTexture(uuid).stream() - .map(HeadAPI::getHeadByValue) - .filter(Objects::nonNull) - .toList(); - } - - /** - * Retrieve a list of {@link LocalHead}'s. - * These are heads from players that have joined the server at least once. - * Requires config option localHeads = true - * - * @return List of {@link LocalHead}'s - */ - @Nonnull - public static List getLocalHeads() { - return HeadDB.getInstance().getPlayerData().getEntries().stream() - .map(entry -> Bukkit.getOfflinePlayer(UUID.fromString(entry))) - .map(player -> new LocalHead(player.getUniqueId()).name(player.getName())) - .toList(); - } - -} diff --git a/src/main/java/tsp/headdb/api/event/DatabaseUpdateEvent.java b/src/main/java/tsp/headdb/api/event/DatabaseUpdateEvent.java deleted file mode 100644 index f46f54f..0000000 --- a/src/main/java/tsp/headdb/api/event/DatabaseUpdateEvent.java +++ /dev/null @@ -1,54 +0,0 @@ -package tsp.headdb.api.event; - -import org.bukkit.event.Event; -import org.bukkit.event.HandlerList; -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 HeadDatabase} updates. - * The event is called asynchronously and can not be cancelled. - * - * @author TheSilentPro - */ -public class DatabaseUpdateEvent extends Event { - - private final HandlerList handlerList = new HandlerList(); - private final HeadDatabase database; - private final Map> heads; - - public DatabaseUpdateEvent(HeadDatabase database, Map> heads) { - super(true); - - this.database = database; - this.heads = heads; - } - - /** - * Retrieve the {@link HeadDatabase} associated with this event - * - * @return The database - */ - public HeadDatabase getDatabase() { - return database; - } - - /** - * Retrieve the result of the update - * - * @return The heads fetched from the update - */ - public Map> getHeads() { - return heads; - } - - @Override - public HandlerList getHandlers() { - return handlerList; - } - -} \ No newline at end of file diff --git a/src/main/java/tsp/headdb/api/event/PlayerHeadPurchaseEvent.java b/src/main/java/tsp/headdb/api/event/PlayerHeadPurchaseEvent.java deleted file mode 100644 index a9faf87..0000000 --- a/src/main/java/tsp/headdb/api/event/PlayerHeadPurchaseEvent.java +++ /dev/null @@ -1,69 +0,0 @@ -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.implementation.Head; - -/** - * This event is called when a player purchases a {@link Head} - * - * @author TheSilentPro - * @see tsp.headdb.inventory.InventoryUtils#purchaseHead(Player, Head, int, String, String) - */ -public class PlayerHeadPurchaseEvent extends Event implements Cancellable { - - private final HandlerList handlerList = new HandlerList(); - private boolean cancelled; - private Player player; - private Head head; - private double cost; - - public PlayerHeadPurchaseEvent(Player player, Head head, double cost) { - super(true); - this.player = player; - this.head = head; - this.cost = cost; - } - - public Player getPlayer() { - return player; - } - - public Head getHead() { - return head; - } - - public double getCost() { - return cost; - } - - public void setPlayer(Player player) { - this.player = player; - } - - public void setHead(Head head) { - this.head = head; - } - - public void setCost(double cost) { - this.cost = cost; - } - - @Override - public boolean isCancelled() { - return cancelled; - } - - @Override - public void setCancelled(boolean b) { - this.cancelled = b; - } - - @Override - public HandlerList getHandlers() { - return handlerList; - } - -} diff --git a/src/main/java/tsp/headdb/command/GiveCommand.java b/src/main/java/tsp/headdb/command/GiveCommand.java deleted file mode 100644 index 99cd4f8..0000000 --- a/src/main/java/tsp/headdb/command/GiveCommand.java +++ /dev/null @@ -1,53 +0,0 @@ -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 deleted file mode 100644 index da0f779..0000000 --- a/src/main/java/tsp/headdb/command/HeadDBCommand.java +++ /dev/null @@ -1,71 +0,0 @@ -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 { - - private void handle(CommandSender sender, String[] args) { - Localization localization = HeadDB.getInstance().getLocalization(); - if (args.length == 0) { - if (!sender.hasPermission("headdb.open")) { - Utils.sendMessage(sender, localization.getMessage("noPermission")); - return; - } - if (!(sender instanceof Player player)) { - Utils.sendMessage(sender, localization.getMessage("onlyPlayers")); - return; - } - - Utils.sendMessage(player, localization.getMessage("databaseOpen")); - HeadAPI.openDatabase(player); - return; - } - - 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; - } - - 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, " "); - } - - @Override - public boolean onCommand(CommandSender sender, Command command, String s, String[] args) { - handle(sender, args); - 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 deleted file mode 100644 index 020f84f..0000000 --- a/src/main/java/tsp/headdb/command/HeadSubCommand.java +++ /dev/null @@ -1,21 +0,0 @@ -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 deleted file mode 100644 index 18a5694..0000000 --- a/src/main/java/tsp/headdb/command/InfoCommand.java +++ /dev/null @@ -1,18 +0,0 @@ -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 deleted file mode 100644 index 0665d6a..0000000 --- a/src/main/java/tsp/headdb/command/ReloadCommand.java +++ /dev/null @@ -1,20 +0,0 @@ -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 deleted file mode 100644 index 792e276..0000000 --- a/src/main/java/tsp/headdb/command/SearchCommand.java +++ /dev/null @@ -1,38 +0,0 @@ -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 player)) { - Utils.sendMessage(sender, getLocalization().getMessage("onlyPlayers")); - return; - } - - 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); - } - -} diff --git a/src/main/java/tsp/headdb/command/TagSearchCommand.java b/src/main/java/tsp/headdb/command/TagSearchCommand.java deleted file mode 100644 index bf601e2..0000000 --- a/src/main/java/tsp/headdb/command/TagSearchCommand.java +++ /dev/null @@ -1,32 +0,0 @@ -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 player)) { - Utils.sendMessage(sender, getLocalization().getMessage("onlyPlayers")); - return; - } - - String tag = args[1]; - Utils.sendMessage(sender, "&7Searching for heads with tag &e" + tag); - HeadAPI.openTagSearchDatabase(player, tag); - } - -} diff --git a/src/main/java/tsp/headdb/command/UpdateCommand.java b/src/main/java/tsp/headdb/command/UpdateCommand.java deleted file mode 100644 index d3765ff..0000000 --- a/src/main/java/tsp/headdb/command/UpdateCommand.java +++ /dev/null @@ -1,26 +0,0 @@ -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/core/api/HeadAPI.java b/src/main/java/tsp/headdb/core/api/HeadAPI.java new file mode 100644 index 0000000..0de608c --- /dev/null +++ b/src/main/java/tsp/headdb/core/api/HeadAPI.java @@ -0,0 +1,33 @@ +package tsp.headdb.core.api; + +import tsp.headdb.HeadDB; +import tsp.headdb.implementation.category.Category; +import tsp.headdb.implementation.head.Head; +import tsp.headdb.implementation.head.HeadDatabase; + +import java.util.List; +import java.util.Map; + +public final class HeadAPI { + + private HeadAPI() {} + + private static final HeadDatabase database = new HeadDatabase(HeadDB.getInstance()); + + public static List getHeads(Category category) { + return database.getHeads().get(category); + } + + public static Map> getHeads() { + return database.getHeads(); + } + + public static int getTotalHeads() { + return database.getSize(); + } + + public static HeadDatabase getDatabase() { + return database; + } + +} diff --git a/src/main/java/tsp/headdb/core/command/CommandMain.java b/src/main/java/tsp/headdb/core/command/CommandMain.java new file mode 100644 index 0000000..913bcd1 --- /dev/null +++ b/src/main/java/tsp/headdb/core/command/CommandMain.java @@ -0,0 +1,86 @@ +package tsp.headdb.core.command; + +import org.bukkit.Material; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.command.RemoteConsoleCommandSender; +import org.bukkit.entity.Player; + +import tsp.headdb.HeadDB; +import tsp.headdb.core.api.HeadAPI; +import tsp.headdb.implementation.category.Category; +import tsp.smartplugin.builder.item.ItemBuilder; +import tsp.smartplugin.inventory.Button; +import tsp.smartplugin.inventory.single.Pane; +import tsp.smartplugin.localization.TranslatableLocalization; +import tsp.smartplugin.utils.InventoryUtils; +import tsp.smartplugin.utils.StringUtils; +import tsp.smartplugin.utils.Validate; + +import javax.annotation.ParametersAreNonnullByDefault; + +public class CommandMain extends HeadDBCommand implements CommandExecutor { + + private final TranslatableLocalization localization = HeadDB.getInstance().getLocalization(); + + public CommandMain() { + super("headdb", "headdb.command.open"); + } + + @Override + @ParametersAreNonnullByDefault + public void handle(CommandSender sender, String[] args) { + if (args.length == 0) { + if (!(sender instanceof Player player)) { + localization.sendConsoleMessage("noConsole"); + return; + } + + if (!player.hasPermission(getPermission())) { + sendMessage(sender, "noPermission"); + return; + } + localization.sendMessage(player.getUniqueId(), "openDatabase"); + + Pane pane = new Pane(6, StringUtils.colorize(localization.getMessage(player.getUniqueId(), "menu.main.title").orElse("&cHeadDB &7(" + HeadAPI.getTotalHeads() + ")")).replace("%size%", HeadAPI.getTotalHeads() + "")); + InventoryUtils.fillBorder(pane.getInventory(), new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE).name(" ").build()); + for (Category category : Category.VALUES) { + pane.addButton(new Button(category.getItem(player.getUniqueId()), e -> { + if (e.getClick().isLeftClick()) { + // open category + e.getWhoClicked().sendMessage("clicked category: " + category.getName()); + } + + e.setCancelled(true); + })); + } + + pane.open(player); + return; + } + + HeadDB.getInstance().getCommandManager().getCommand(args[0]).ifPresentOrElse(command -> command.handle(sender, args), () -> sendMessage(sender, "invalidSubCommand")); + } + + @ParametersAreNonnullByDefault + private void sendMessage(CommandSender sender, String key) { + Validate.notNull(sender, "Message sender can not be null!"); + Validate.notNull(key, "Key can not be null!"); + + if (sender instanceof Player player) { + localization.sendMessage(player.getUniqueId(), key); + } else if (sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender){ + localization.sendConsoleMessage(key); + } + } + + @Override + @ParametersAreNonnullByDefault + public boolean onCommand(CommandSender sender, Command command, String s, String[] args) { + handle(sender, args); + return true; + } + +} diff --git a/src/main/java/tsp/headdb/core/command/CommandManager.java b/src/main/java/tsp/headdb/core/command/CommandManager.java new file mode 100644 index 0000000..bbf5407 --- /dev/null +++ b/src/main/java/tsp/headdb/core/command/CommandManager.java @@ -0,0 +1,22 @@ +package tsp.headdb.core.command; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class CommandManager { + + private final Map commands = new HashMap<>(); + + public void register(SubCommand command) { + this.commands.put(command.getName(), command); + } + + public Optional getCommand(String name) { + return Optional.ofNullable(commands.get(name)); + } + + public Map getCommandsMap() { + return commands; + } +} diff --git a/src/main/java/tsp/headdb/core/command/HeadDBCommand.java b/src/main/java/tsp/headdb/core/command/HeadDBCommand.java new file mode 100644 index 0000000..3380091 --- /dev/null +++ b/src/main/java/tsp/headdb/core/command/HeadDBCommand.java @@ -0,0 +1,38 @@ +package tsp.headdb.core.command; + +import org.bukkit.command.CommandSender; + +import javax.annotation.Nullable; +import java.util.Optional; + +public abstract class HeadDBCommand { + + private final String name; + private final String permission; + private final String[] aliases; + + public HeadDBCommand(String name, String permission, @Nullable String[] aliases) { + this.name = name; + this.permission = permission; + this.aliases = aliases; + } + + public HeadDBCommand(String name, String permission) { + this(name, permission, null); + } + + public abstract void handle(CommandSender sender, String[] args); + + public String getName() { + return name; + } + + public String getPermission() { + return permission; + } + + public Optional getAliases() { + return Optional.ofNullable(aliases); + } + +} \ No newline at end of file diff --git a/src/main/java/tsp/headdb/core/command/SubCommand.java b/src/main/java/tsp/headdb/core/command/SubCommand.java new file mode 100644 index 0000000..d9a149f --- /dev/null +++ b/src/main/java/tsp/headdb/core/command/SubCommand.java @@ -0,0 +1,9 @@ +package tsp.headdb.core.command; + +public abstract class SubCommand extends HeadDBCommand { + + public SubCommand(String name) { + super(name, "headdb.command." + name); + } + +} diff --git a/src/main/java/tsp/headdb/core/storage/PlayerData.java b/src/main/java/tsp/headdb/core/storage/PlayerData.java new file mode 100644 index 0000000..bb0d62a --- /dev/null +++ b/src/main/java/tsp/headdb/core/storage/PlayerData.java @@ -0,0 +1,6 @@ +package tsp.headdb.core.storage; + +import java.util.Collection; +import java.util.UUID; + +public record PlayerData(UUID uniqueId, String name, Collection favorites) {} \ No newline at end of file diff --git a/src/main/java/tsp/headdb/core/storage/PlayerStorage.java b/src/main/java/tsp/headdb/core/storage/PlayerStorage.java new file mode 100644 index 0000000..f74ce67 --- /dev/null +++ b/src/main/java/tsp/headdb/core/storage/PlayerStorage.java @@ -0,0 +1,38 @@ +package tsp.headdb.core.storage; + +import org.bukkit.Bukkit; +import org.jetbrains.annotations.NotNull; +import tsp.headdb.HeadDB; +import tsp.warehouse.storage.DataManager; +import tsp.warehouse.storage.sql.SQLiteDataManager; + +import javax.annotation.Nullable; +import java.io.File; +import java.util.Collection; +import java.util.concurrent.CompletableFuture; + +public class PlayerStorage extends SQLiteDataManager { + + public PlayerStorage() { + super(new File(HeadDB.getInstance().getDataFolder(), "player_data.db"), null); + sendPreparedUpdate("CREATE TABLE IF NOT EXISTS data(uuid NOT NULL PRIMARY KEY VARCHAR(36), name TEXT, favorites TEXT)"); + } + + @Override + public CompletableFuture> load() { + return null; + } + + @Override + public CompletableFuture save(Collection data) { + Bukkit.getScheduler().runTaskAsynchronously(HeadDB.getInstance(), () -> { + StringBuilder builder = new StringBuilder(); + for (PlayerData entry : data) { + builder.append(String.format("(%s, %s, %s),", entry.uniqueId().toString(), entry.name(), entry.favorites().toString())); + } + sendPreparedUpdate("INSERT OR REPLACE INTO data VALUES" + builder.substring(0, builder.length() - 1) + ";").join(); + }); + return CompletableFuture.completedFuture(true); + } + +} diff --git a/src/main/java/tsp/headdb/core/task/DatabaseUpdateTask.java b/src/main/java/tsp/headdb/core/task/DatabaseUpdateTask.java new file mode 100644 index 0000000..6b68113 --- /dev/null +++ b/src/main/java/tsp/headdb/core/task/DatabaseUpdateTask.java @@ -0,0 +1,25 @@ +package tsp.headdb.core.task; + +import tsp.headdb.core.api.HeadAPI; +import tsp.smartplugin.tasker.Task; + +@SuppressWarnings("ClassCanBeRecord") +public class DatabaseUpdateTask implements Task { + + private final long interval; + + public DatabaseUpdateTask(long interval) { + this.interval = interval; + } + + @Override + public void run() { + HeadAPI.getDatabase().update(); + } + + @Override + public long getRepeatInterval() { + return interval; + } + +} diff --git a/src/main/java/tsp/headdb/core/util/Utils.java b/src/main/java/tsp/headdb/core/util/Utils.java new file mode 100644 index 0000000..1df47f0 --- /dev/null +++ b/src/main/java/tsp/headdb/core/util/Utils.java @@ -0,0 +1,16 @@ +package tsp.headdb.core.util; + +import java.util.Optional; +import java.util.UUID; + +public class Utils { + + public static Optional validateUniqueId(String raw) { + try { + return Optional.of(UUID.fromString(raw)); + } catch (IllegalArgumentException ignored) { + return Optional.empty(); + } + } + +} diff --git a/src/main/java/tsp/headdb/economy/BasicEconomyProvider.java b/src/main/java/tsp/headdb/economy/BasicEconomyProvider.java deleted file mode 100644 index f1b3e83..0000000 --- a/src/main/java/tsp/headdb/economy/BasicEconomyProvider.java +++ /dev/null @@ -1,48 +0,0 @@ -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/TreasuryProvider.java b/src/main/java/tsp/headdb/economy/TreasuryProvider.java deleted file mode 100644 index 9d905cf..0000000 --- a/src/main/java/tsp/headdb/economy/TreasuryProvider.java +++ /dev/null @@ -1,98 +0,0 @@ -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 EconomyTransactionInitiator transactionInitiator; - private Currency currency; - - @Override - public void canPurchase(Player player, BigDecimal cost, Consumer result) { - EconomySubscriber - .asFuture(s -> provider.hasPlayerAccount(player.getUniqueId(), s)) - .thenCompose(val -> { - if (Boolean.TRUE.equals(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 (Boolean.TRUE.equals(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, - transactionInitiator, - currency, - new EconomySubscriber() { - @Override - public void succeed(@NotNull BigDecimal bigDecimal) { - result.accept(true); - } - - @Override - public void fail(@NotNull EconomyException exception) { - result.accept(false); - Log.error(ex); - } - })); - } - - @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(); - transactionInitiator = EconomyTransactionInitiator.createInitiator(EconomyTransactionInitiator.Type.PLUGIN, "HeadDB"); - - String rawCurrency = HeadDB.getInstance().getConfig().getString("economy.currency"); - if (rawCurrency == null || rawCurrency.isEmpty()) { - currency = provider.getPrimaryCurrency(); - } else { - provider.getCurrencies().stream() - .filter(c -> c.getIdentifier().equalsIgnoreCase(rawCurrency)) - .findFirst() - .ifPresentOrElse(c -> currency = c, () -> Log.error("Could not find currency: " + rawCurrency)); - } - } - -} diff --git a/src/main/java/tsp/headdb/economy/VaultProvider.java b/src/main/java/tsp/headdb/economy/VaultProvider.java deleted file mode 100644 index 1b7d6b6..0000000 --- a/src/main/java/tsp/headdb/economy/VaultProvider.java +++ /dev/null @@ -1,52 +0,0 @@ -package tsp.headdb.economy; - -import net.milkbowl.vault.economy.Economy; -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; - -/** - * A {@link BasicEconomyProvider} for Vault - * - * @author TheSilentPro - * @since 4.0.0 - */ -public class VaultProvider implements BasicEconomyProvider { - - private Economy economy; - - @Override - 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, Consumer result) { - Utils.async(t -> result.accept(economy.withdrawPlayer(player, amount.doubleValue()).transactionSuccess())); - } - - public void initProvider() { - if (!Bukkit.getServer().getPluginManager().isPluginEnabled("Vault")) { - Log.error("Vault is not installed!"); - return; - } - - RegisteredServiceProvider economyProvider = Bukkit.getServer().getServicesManager().getRegistration(Economy.class); - if (economyProvider == null) { - Log.error("Could not find vault economy provider!"); - return; - } - - economy = economyProvider.getProvider(); - } - - public Economy getProvider() { - return economy; - } - -} diff --git a/src/main/java/tsp/headdb/implementation/Category.java b/src/main/java/tsp/headdb/implementation/Category.java deleted file mode 100644 index a5d1d19..0000000 --- a/src/main/java/tsp/headdb/implementation/Category.java +++ /dev/null @@ -1,91 +0,0 @@ -package tsp.headdb.implementation; - -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; -import tsp.headdb.api.HeadAPI; - -import javax.annotation.Nullable; -import java.util.Objects; -import java.util.Optional; - -/** - * Represents a category for heads - * - * @author TheSilentPro - */ -public enum Category { - - ALPHABET("alphabet", ChatColor.YELLOW, 20), - ANIMALS("animals", ChatColor.DARK_AQUA, 21), - BLOCKS("blocks", ChatColor.DARK_GRAY, 22), - DECORATION("decoration", ChatColor.LIGHT_PURPLE, 23), - FOOD_DRINKS("food-drinks", ChatColor.GOLD, 24), - HUMANS("humans", ChatColor.DARK_BLUE, 29), - HUMANOID("humanoid", ChatColor.AQUA, 30), - MISCELLANEOUS("miscellaneous", ChatColor.DARK_GREEN, 31), - MONSTERS("monsters", ChatColor.RED, 32), - PLANTS("plants", ChatColor.GREEN, 33); - - private final String name; - private final ChatColor color; - private final int location; - private static final Category[] cache = values(); - - Category(String name, ChatColor color, int location) { - this.name = name; - this.color = color; - this.location = location; - } - - public String getName() { - return name; - } - - public ChatColor getColor() { - return color; - } - - public int getLocation() { - return location; - } - - /** - * Retrieve the first valid head from a category - * - * @return First valid head - */ - public ItemStack getItem() { - Optional result = HeadAPI.getHeads(this).stream() - .filter(Objects::nonNull) - .findFirst(); - - if (result.isPresent()) { - return result.get().getMenuItem(); - } else { - return new ItemStack(Material.PLAYER_HEAD); - } - } - - /** - * 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)) { - return category; - } - } - - return null; - } - - public static Category[] getCache() { - return cache; - } - -} diff --git a/src/main/java/tsp/headdb/implementation/DataSaveTask.java b/src/main/java/tsp/headdb/implementation/DataSaveTask.java deleted file mode 100644 index 00ac241..0000000 --- a/src/main/java/tsp/headdb/implementation/DataSaveTask.java +++ /dev/null @@ -1,11 +0,0 @@ -package tsp.headdb.implementation; - -import tsp.headdb.HeadDB; - -public class DataSaveTask implements Runnable { - - @Override - public void run() { - HeadDB.getInstance().getPlayerData().save(); - } -} diff --git a/src/main/java/tsp/headdb/implementation/DatabaseUpdateTask.java b/src/main/java/tsp/headdb/implementation/DatabaseUpdateTask.java deleted file mode 100644 index 413711f..0000000 --- a/src/main/java/tsp/headdb/implementation/DatabaseUpdateTask.java +++ /dev/null @@ -1,16 +0,0 @@ -package tsp.headdb.implementation; - -import tsp.headdb.api.HeadAPI; -import tsp.headdb.util.Log; - -/** - * Task that updates the database on an interval - */ -public class DatabaseUpdateTask implements Runnable { - - @Override - public void run() { - HeadAPI.getDatabase().update(heads -> Log.info("Fetched " + HeadAPI.getHeads().size() + " heads!")); - } - -} diff --git a/src/main/java/tsp/headdb/implementation/Head.java b/src/main/java/tsp/headdb/implementation/Head.java deleted file mode 100644 index 6a0315e..0000000 --- a/src/main/java/tsp/headdb/implementation/Head.java +++ /dev/null @@ -1,142 +0,0 @@ -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.SkullMeta; -import tsp.headdb.util.Log; -import tsp.headdb.util.Utils; - -import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.List; -import java.util.UUID; -import java.util.regex.Pattern; - -/** - * Represents a Head that a player can obtain via the database - * - * @author TheSilentPro - */ -public class Head { - - public static final Pattern SPLIT = Pattern.compile(","); - private String name; - private UUID uuid; - private String value; - private Category category; - private int id; - private List tags; - private ItemStack menuItem; - - public Head() {} - - public Head(int id) { - this.id = id; - } - - public ItemStack getMenuItem() { - if (menuItem == null) { - Validate.notNull(name, "name must not be null!"); - Validate.notNull(uuid, "uuid must not be null!"); - Validate.notNull(value, "value must not be null!"); - - ItemStack item = new ItemStack(Material.PLAYER_HEAD); - SkullMeta meta = (SkullMeta) item.getItemMeta(); - meta.setDisplayName(Utils.colorize(category != null ? category.getColor() + name : "&8" + name)); - // set skull owner - GameProfile profile = new GameProfile(uuid, name); - profile.getProperties().put("textures", new Property("textures", value)); - Field profileField; - try { - profileField = meta.getClass().getDeclaredField("profile"); - profileField.setAccessible(true); - profileField.set(meta, profile); - } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) { - Log.error("Could not set skull owner for " + uuid.toString() + " | Stack Trace:"); - Log.error(ex); - } - - meta.setLore(Arrays.asList( - Utils.colorize("&cID: " + id), - Utils.colorize("&e" + buildTagLore(tags)), - "", - Utils.colorize("&8Right-Click to add/remove from favorites.") - )); - - item.setItemMeta(meta); - menuItem = item; - } - - return menuItem; - } - - public String getName() { - return name; - } - - public UUID getUniqueId() { - return uuid; - } - - public String getValue() { - return value; - } - - public Category getCategory() { - return category; - } - - public int getId() { - return id; - } - - public List getTags() { - return tags; - } - - public Head name(String name) { - this.name = name; - return this; - } - - public Head uniqueId(UUID uuid) { - this.uuid = uuid; - return this; - } - - public Head value(String value) { - this.value = value; - return this; - } - - public Head category(Category category) { - this.category = category; - return this; - } - - public Head id(int id) { - this.id = id; - return this; - } - - public Head tags(String tags) { - this.tags = Arrays.asList(SPLIT.split(tags)); - return this; - } - - private String buildTagLore(List tags) { - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < tags.size(); i++) { - builder.append(tags.get(i)); - if (i != tags.size() - 1) { - builder.append(","); - } - } - - return builder.toString(); - } - -} diff --git a/src/main/java/tsp/headdb/implementation/HeadDatabase.java b/src/main/java/tsp/headdb/implementation/HeadDatabase.java deleted file mode 100644 index 8a72208..0000000 --- a/src/main/java/tsp/headdb/implementation/HeadDatabase.java +++ /dev/null @@ -1,298 +0,0 @@ -package tsp.headdb.implementation; - -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.plugin.java.JavaPlugin; -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.HeadDB; -import tsp.headdb.api.event.DatabaseUpdateEvent; -import tsp.headdb.util.Log; -import tsp.headdb.util.Utils; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.URL; -import java.net.URLConnection; -import java.util.ArrayList; -import java.util.Collections; -import java.util.EnumMap; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; -import javax.annotation.Nonnull; - -/** - * This is the Database that holds all heads - * - * @author TheSilentPro - */ -// TODO: Optionals instead of null. -public class HeadDatabase { - - private final JavaPlugin plugin; - private final EnumMap> heads = new EnumMap<>(Category.class); - private long refresh; - private int timeout; - private long updated; - private int nextId; // Internal only - - public HeadDatabase(JavaPlugin plugin) { - this.plugin = plugin; - this.refresh = 3600; - this.timeout = 5000; - } - - public Head getHeadByValue(String value) { - List fetched = getHeads(); - for (Head head : fetched) { - if (head.getValue().equals(value)) { - return head; - } - } - - return null; - } - - public Head getHeadByID(int id) { - List fetched = getHeads(); - for (Head head : fetched) { - if (head.getId() == id) { - return head; - } - } - - return null; - } - - public Head getHeadByUniqueId(UUID uuid) { - List fetched = getHeads(); - for (Head head : fetched) { - if (head.getUniqueId().equals(uuid)) { - return head; - } - } - - return null; - } - - public List getHeadsByTag(String tag) { - List result = new ArrayList<>(); - List fetched = getHeads(); - tag = tag.toLowerCase(Locale.ROOT); - for (Head head : fetched) { - for (String t : head.getTags()) { - if (t.toLowerCase(Locale.ROOT).contains(tag)) { - result.add(head); - } - } - } - - return result; - } - - public List getHeadsByName(Category category, String name) { - List result = new ArrayList<>(); - List fetched = getHeads(category); - for (Head head : fetched) { - String hName = ChatColor.stripColor(head.getName().toLowerCase(Locale.ROOT)); - if (hName.contains(ChatColor.stripColor(name.toLowerCase(Locale.ROOT)))) { - result.add(head); - } - } - - return result; - } - - public List getHeadsByName(String name) { - List result = new ArrayList<>(); - for (Category category : Category.getCache()) { - result.addAll(getHeadsByName(category, name)); - } - - return result; - } - - @Nonnull - public List getHeads(Category category) { - List result = heads.get(category); - return result != null ? Collections.unmodifiableList(result) : Collections.emptyList(); - } - - /** - * Gets all heads from the cache if available. - * - * @return List containing each head in its category. - */ - @Nonnull - public List getHeads() { - List result = new ArrayList<>(); - for (Category category : heads.keySet()) { - result.addAll(getHeads(category)); - } - return result; - } - - public void getHeadsNoCache(Consumer>> resultSet) { - Bukkit.getScheduler().runTaskAsynchronously(plugin, task -> { - Log.debug("[" + plugin.getName() + "] Updating database... "); - EnumMap> result = new EnumMap<>(Category.class); - Category[] categories = Category.getCache(); - - for (Category category : categories) { - Log.debug("Caching heads from: " + category.getName()); - List results = new ArrayList<>(); - try { - // First the original api is fetched - results = gather("https://minecraft-heads.com/scripts/api.php?cat=" + category.getName() + "&tags=true", category); - } catch (ParseException | IOException e) { - Log.debug("[" + plugin.getName() + "] Failed to fetch heads (no-cache) from category " + category.getName() + " | Stack Trace:"); - Log.debug(e); - Log.warning("Failed to fetch heads for " + category.getName()); - if (HeadDB.getInstance().getConfig().getBoolean("fallback", true)) { - Log.info("Attempting fallback provider for: " + category.getName()); - try { - // If the original fails and fallback is enabled, fetch from static archive - results = gather("https://heads.pages.dev/archive/" + category.getName() + ".json", category); - } catch (IOException | ParseException ex) { - Log.error("Failed to fetch heads for " + category.getName() + "! (OF)"); // OF = Original-Fallback, both failed - Log.error(ex); - } - } - } - - updated = System.nanoTime(); - result.put(category, results); - } - - resultSet.accept(result); - }); - } - - /** - * Fetches and gathers the heads from the url. - * For internal use only! - * - * @param url The url - * @param category The category of the heads - * @return List of heads for that category - * @throws IOException error - * @throws ParseException error - */ - protected List gather(String url, Category category) throws IOException, ParseException { - long start = System.currentTimeMillis(); - List headList = new ArrayList<>(); - // TODO: gson - JSONParser parser = new JSONParser(); - JSONArray array = (JSONArray) parser.parse(fetch(url)); - for (Object o : array) { - JSONObject obj = (JSONObject) o; - String rawUUID = obj.get("uuid").toString(); - - UUID uuid; - if (Utils.validateUniqueId(rawUUID)) { - uuid = UUID.fromString(rawUUID); - } else { - uuid = UUID.randomUUID(); - } - - Head head = new Head(nextId++) - .name(obj.get("name").toString()) - .uniqueId(uuid) - .value(obj.get("value").toString()) - .tags(obj.get("tags") != null ? obj.get("tags").toString() : "None") - .category(category); - - headList.add(head); - } - - long elapsed = (System.currentTimeMillis() - start); - Log.debug(category.getName() + " -> Done! Time: " + elapsed + "ms (" + TimeUnit.MILLISECONDS.toSeconds(elapsed) + "s)"); - return headList; - } - - /** - * Fetches heads from the url. - * For internal use only! - * - * @param url The url - * @return JSON-string response - * @throws IOException error - */ - protected String fetch(String url) throws IOException { - String line; - StringBuilder response = new StringBuilder(); - - URLConnection connection = new URL(url).openConnection(); - connection.setConnectTimeout(timeout); - connection.setRequestProperty("User-Agent", plugin.getName() + "-DatabaseUpdater"); - BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); - while ((line = in.readLine()) != null) { - response.append(line); - } - - return response.toString(); - } - - public void update(Consumer>> result) { - Bukkit.getScheduler().runTaskAsynchronously(plugin, task -> getHeadsNoCache(headsList -> { - if (headsList == null) { - Log.error("[" + plugin.getName() + "] Failed to update database! Check above for any errors."); - result.accept(null); - return; - } - - heads.clear(); - heads.putAll(headsList); - result.accept(heads); - Bukkit.getPluginManager().callEvent(new DatabaseUpdateEvent(this, heads)); - })); - } - - /** - * Get the last time the database was updated. - * - * @return Last update in seconds - */ - public long getLastUpdate() { - long now = System.nanoTime(); - long elapsed = now - updated; - return TimeUnit.NANOSECONDS.toSeconds(elapsed); - } - - /** - * Checks if the update is past the refresh time - * - * @return Whether the update is old - */ - public boolean isLastUpdateOld() { - return getLastUpdate() >= refresh; - } - - public void setTimeout(int timeout) { - this.timeout = timeout; - } - - public int getTimeout() { - return timeout; - } - - public long getRefresh() { - return refresh; - } - - public void setRefresh(long refresh) { - this.refresh = refresh; - } - - public JavaPlugin getPlugin() { - return plugin; - } - -} diff --git a/src/main/java/tsp/headdb/implementation/LocalHead.java b/src/main/java/tsp/headdb/implementation/LocalHead.java deleted file mode 100644 index dfaffd0..0000000 --- a/src/main/java/tsp/headdb/implementation/LocalHead.java +++ /dev/null @@ -1,87 +0,0 @@ -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.util.Utils; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.UUID; - -/** - * Represents a local player head that can be obtained via the LocalHeads option - * - * @author TheSilentPro - */ -public class LocalHead extends Head { - - private UUID uuid; - private String name; - - public LocalHead(UUID uuid) { - this.uuid = uuid; - } - - @Override - public ItemStack getMenuItem() { - Validate.notNull(uuid, "uuid must not be null!"); - - ItemStack item = new ItemStack(Material.PLAYER_HEAD); - SkullMeta meta = (SkullMeta) item.getItemMeta(); - meta.setOwningPlayer(Bukkit.getOfflinePlayer(uuid)); - meta.setDisplayName(Utils.colorize("&e" + name)); - List lore = new ArrayList<>(); - lore.add(Utils.colorize("&7UUID: " + uuid.toString())); - meta.setLore(lore); - item.setItemMeta(meta); - - return item; - } - - @Override - public UUID getUniqueId() { - return uuid; - } - - @Override - public String getName() { - return name; - } - - @Override - public String getValue() { - return null; - } - - @Override - public Category getCategory() { - return null; - } - - @Override - public int getId() { - return -1; - } - - @Override - public List getTags() { - return Collections.emptyList(); - } - - @Override - public LocalHead uniqueId(UUID uuid) { - this.uuid = uuid; - return this; - } - - @Override - public LocalHead name(String name) { - this.name = name; - return this; - } - -} diff --git a/src/main/java/tsp/headdb/implementation/category/Category.java b/src/main/java/tsp/headdb/implementation/category/Category.java new file mode 100644 index 0000000..4529feb --- /dev/null +++ b/src/main/java/tsp/headdb/implementation/category/Category.java @@ -0,0 +1,55 @@ +package tsp.headdb.implementation.category; + +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import tsp.headdb.HeadDB; +import tsp.headdb.core.api.HeadAPI; +import tsp.headdb.implementation.head.Head; +import tsp.smartplugin.builder.item.ItemBuilder; + +import javax.annotation.Nonnull; +import java.util.List; +import java.util.Locale; +import java.util.UUID; + +public enum Category { + + //alphabet, animals, blocks, decoration, food-drinks, humans, humanoid, miscellaneous, monsters, plants + ALPHABET("alphabet"), + ANIMALS("animals"), + BLOCKS("blocks"), + DECORATION("decoration"), + FOOD_DRINKS("food-drinks"), + HUMANS("humans"), + HUMANOID("humanoid"), + MISCELLANEOUS("miscellaneous"), + MONSTERS("monsters"), + PLANTS("plants"); + + private final String name; + private final String url; + + public static final Category[] VALUES = values(); + + Category(String name) { + this.name = name; + this.url = String.format("https://minecraft-heads.com/scripts/api.php?cat=%s&tags=true", name); + } + + public String getName() { + return name; + } + + public String getUrl() { + return url; + } + + @Nonnull + public ItemStack getItem(UUID receiver) { + List heads = HeadAPI.getHeads(this); + return heads.size() > 1 + ? new ItemBuilder(heads.get(0).getItem(receiver)).name(HeadDB.getInstance().getLocalization().getMessage(receiver, "menu.main.category.name").orElse("&e" + getName())).build() + : new ItemBuilder(Material.BARRIER).name(getName().toUpperCase(Locale.ROOT)).build(); + } + +} diff --git a/src/main/java/tsp/headdb/implementation/head/Head.java b/src/main/java/tsp/headdb/implementation/head/Head.java new file mode 100644 index 0000000..226da79 --- /dev/null +++ b/src/main/java/tsp/headdb/implementation/head/Head.java @@ -0,0 +1,49 @@ +package tsp.headdb.implementation.head; + +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; +import tsp.headdb.HeadDB; +import tsp.smartplugin.builder.item.ItemBuilder; +import tsp.smartplugin.localization.TranslatableLocalization; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.lang.reflect.Field; +import java.util.Locale; +import java.util.UUID; + +@ParametersAreNonnullByDefault +public record Head(UUID uniqueId, String name, String value, String tags, String updated) { + + private static ItemStack item; + + public ItemStack getItem(UUID receiver) { + if (item == null) { + TranslatableLocalization localization = HeadDB.getInstance().getLocalization(); + item = new ItemBuilder(Material.PLAYER_HEAD) + .name(localization.getMessage(receiver, "menu.head.name").orElse("&e" + name.toUpperCase(Locale.ROOT)).replace("%name%", name)) + .setLore("&7Tags: &e" + tags) + .build(); + + ItemMeta meta = item.getItemMeta(); + GameProfile profile = new GameProfile(uniqueId, name); + profile.getProperties().put("textures", new Property("textures", value)); + Field profileField; + try { + //noinspection ConstantConditions + profileField = meta.getClass().getDeclaredField("profile"); + profileField.setAccessible(true); + profileField.set(meta, profile); + } catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) { + //Log.error("Could not set skull owner for " + uuid.toString() + " | Stack Trace:"); + ex.printStackTrace(); + } + + item.setItemMeta(meta); + } + return item; + } + +} \ No newline at end of file diff --git a/src/main/java/tsp/headdb/implementation/head/HeadDatabase.java b/src/main/java/tsp/headdb/implementation/head/HeadDatabase.java new file mode 100644 index 0000000..d3dc561 --- /dev/null +++ b/src/main/java/tsp/headdb/implementation/head/HeadDatabase.java @@ -0,0 +1,75 @@ +package tsp.headdb.implementation.head; + +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitScheduler; +import tsp.headdb.implementation.category.Category; +import tsp.headdb.implementation.requester.Requester; + +import java.io.IOException; +import java.util.Collections; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +public class HeadDatabase { + + private final JavaPlugin plugin; + private final BukkitScheduler scheduler; + private final Requester requester; + private final Map> heads; + private long timestamp; + private int size; + + public HeadDatabase(JavaPlugin plugin) { + this.plugin = plugin; + this.scheduler = plugin.getServer().getScheduler(); + this.requester = new Requester(plugin); + this.heads = Collections.synchronizedMap(new EnumMap<>(Category.class)); + } + + public Map> getHeads() { + return heads; + } + + public void getHeadsNoCache(Consumer>> heads) { + getScheduler().runTaskAsynchronously(plugin, () -> { + Map> result = new HashMap<>(); + for (Category category : Category.VALUES) { + try { + requester.fetchAndResolve(category, response -> result.put(category, response)); + } catch (IOException e) { + e.printStackTrace(); + } + } + + heads.accept(result); + }); + } + + public void update() { + getHeadsNoCache(result -> { + heads.putAll(result); + timestamp = System.currentTimeMillis(); + size = heads.values().size(); + }); + } + + public int getSize() { + return size; + } + + public JavaPlugin getPlugin() { + return plugin; + } + + public BukkitScheduler getScheduler() { + return scheduler; + } + + public Requester getRequester() { + return requester; + } + +} diff --git a/src/main/java/tsp/headdb/implementation/requester/Requester.java b/src/main/java/tsp/headdb/implementation/requester/Requester.java new file mode 100644 index 0000000..c127174 --- /dev/null +++ b/src/main/java/tsp/headdb/implementation/requester/Requester.java @@ -0,0 +1,77 @@ +package tsp.headdb.implementation.requester; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.bukkit.plugin.java.JavaPlugin; +import tsp.headdb.core.util.Utils; +import tsp.headdb.implementation.category.Category; +import tsp.headdb.implementation.head.Head; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.function.Consumer; + +@SuppressWarnings("ClassCanBeRecord") +public class Requester { + + private final JavaPlugin plugin; + + public Requester(JavaPlugin plugin) { + this.plugin = plugin; + } + + public void fetchAndResolve(Category category, Consumer> heads) throws IOException { + fetch(category, response -> { + List result = new ArrayList<>(); + if (response.code() != 200) { + heads.accept(result); + return; + } + + JsonArray main = JsonParser.parseString(response.response()).getAsJsonArray(); + for (JsonElement entry : main) { + JsonObject obj = entry.getAsJsonObject(); + result.add(new Head( + Utils.validateUniqueId(obj.get("uuid").getAsString()).orElse(UUID.randomUUID()), + obj.get("name").getAsString(), obj.get("value").getAsString(), obj.get("tags").getAsString(), + response.date() + )); + } + + heads.accept(result); + }); + } + + public void fetch(Category category, Consumer response) throws IOException { + HttpURLConnection connection = (HttpURLConnection) new URL(category.getUrl()).openConnection(); + connection.setConnectTimeout(5000); + connection.setRequestMethod("GET"); + connection.setRequestProperty("User-Agent", plugin.getName() + "/" + plugin.getDescription().getVersion()); + connection.setRequestProperty("Accept", "application/json"); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) { + StringBuilder builder = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + builder.append(line); + } + + response.accept(new Response(builder.toString(), connection.getResponseCode(), connection.getRequestProperty("date"))); + } + + connection.disconnect(); + } + + public JavaPlugin getPlugin() { + return plugin; + } + +} diff --git a/src/main/java/tsp/headdb/implementation/requester/Response.java b/src/main/java/tsp/headdb/implementation/requester/Response.java new file mode 100644 index 0000000..9d21755 --- /dev/null +++ b/src/main/java/tsp/headdb/implementation/requester/Response.java @@ -0,0 +1,3 @@ +package tsp.headdb.implementation.requester; + +public record Response(String response, int code, String date) {} \ No newline at end of file diff --git a/src/main/java/tsp/headdb/inventory/Button.java b/src/main/java/tsp/headdb/inventory/Button.java deleted file mode 100644 index 2fabb66..0000000 --- a/src/main/java/tsp/headdb/inventory/Button.java +++ /dev/null @@ -1,78 +0,0 @@ -package tsp.headdb.inventory; - -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.inventory.ItemStack; - -import java.util.Objects; -import java.util.function.Consumer; - -/** - * A button - */ -public class Button { - - private static int counter = 0; - private static final int ID = counter++; - - private final ItemStack itemStack; - private Consumer action; - - /** - * @param itemStack The Item - */ - @SuppressWarnings("unused") - public Button(ItemStack itemStack) { - this(itemStack, event -> { - }); - } - - /** - * @param itemStack The Item - * @param action The action - */ - public Button(ItemStack itemStack, Consumer action) { - this.itemStack = itemStack; - this.action = action; - } - - - - /** - * @return The icon - */ - @SuppressWarnings("WeakerAccess") - public ItemStack getItemStack() { - return itemStack; - } - - /** - * @param action The new action - */ - @SuppressWarnings("unused") - public void setAction(Consumer action) { - this.action = action; - } - - /** - * @param event The event that triggered it - */ - @SuppressWarnings("WeakerAccess") - public void onClick(InventoryClickEvent event) { - action.accept(event); - } - - // We do not want equals collisions. The default hashcode would not fulfil this contract. - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - - return o instanceof Button; - } - - @Override - public int hashCode() { - return Objects.hash(ID); - } -} diff --git a/src/main/java/tsp/headdb/inventory/InventoryUtils.java b/src/main/java/tsp/headdb/inventory/InventoryUtils.java deleted file mode 100644 index 7d086df..0000000 --- a/src/main/java/tsp/headdb/inventory/InventoryUtils.java +++ /dev/null @@ -1,422 +0,0 @@ -package tsp.headdb.inventory; - -import me.clip.placeholderapi.PlaceholderAPI; -import org.bukkit.Bukkit; -import org.bukkit.Material; -import org.bukkit.configuration.file.FileConfiguration; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; - -import tsp.headdb.HeadDB; -import tsp.headdb.implementation.Head; -import tsp.headdb.api.HeadAPI; -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 java.math.BigDecimal; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Consumer; -import java.util.stream.Collectors; - -/** - * Class for handling the "dirty" work - * such as inventories and economy. - */ -// TODO: Rewrite -public class InventoryUtils { - - private InventoryUtils() {} - private static final FileConfiguration config = HeadDB.getInstance().getConfig(); - - private static final Localization localization = HeadDB.getInstance().getLocalization(); - private static final Map uiLocation = new HashMap<>(); - private static final Map uiItem = new HashMap<>(); - - public static void openLocalMenu(Player player) { - List heads = HeadAPI.getLocalHeads(); - - PagedPane pane = new PagedPane(4, 6, - replace(localization.getMessage("menu.local"), heads.size(), "Local", "None", player)); - for (LocalHead localHead : heads) { - pane.addButton(new Button(localHead.getMenuItem(), e -> { - if (e.getClick() == ClickType.SHIFT_LEFT) { - purchaseHead(player, localHead, 64, "local", localHead.getName()); - return; - } - if (e.getClick() == ClickType.LEFT) { - purchaseHead(player, localHead, 1, "local", localHead.getName()); - return; - } - if (e.getClick() == ClickType.RIGHT) { - Utils.sendMessage(player, localization.getMessage("localFavorites")); - } - })); - } - - pane.open(player); - } - - public static void openFavoritesMenu(Player player) { - List heads = config.getBoolean("hidden.hideFavorites") ? filter(HeadAPI.getFavoriteHeads(player.getUniqueId())) : HeadAPI.getFavoriteHeads(player.getUniqueId()); - - PagedPane pane = new PagedPane(4, 6, - replace(localization.getMessage("menu.favorites"), heads.size(), "Favorites", "None", player)); - for (Head head : heads) { - pane.addButton(new Button(head.getMenuItem(), e -> { - if (e.getClick() == ClickType.SHIFT_LEFT) { - purchaseHead(player, head, 64, head.getCategory().getName(), head.getName()); - } - if (e.getClick() == ClickType.LEFT) { - purchaseHead(player, head, 1, head.getCategory().getName(), head.getName()); - } - if (e.getClick() == ClickType.RIGHT) { - HeadAPI.removeFavoriteHead(player.getUniqueId(), head.getValue()); - openFavoritesMenu(player); - Utils.sendMessage(player, "&7Removed &e" + head.getName() + " &7from favorites."); - Utils.playSound(player, "removeFavorite"); - } - })); - } - - pane.open(player); - } - - public static PagedPane openSearchDatabase(Player player, String search) { - List heads = filter(HeadAPI.getHeadsByName(search)); - - PagedPane pane = new PagedPane(4, 6, - replace(localization.getMessage("menu.search"), heads.size(), "None", search, player)); - for (Head head : heads) { - pane.addButton(new Button(head.getMenuItem(), e -> { - if (e.getClick() == ClickType.SHIFT_LEFT) { - purchaseHead(player, head, 64, head.getCategory().getName(), head.getName()); - } - if (e.getClick() == ClickType.LEFT) { - purchaseHead(player, head, 1, head.getCategory().getName(), head.getName()); - } - if (e.getClick() == ClickType.RIGHT) { - if (!player.hasPermission("headdb.favorites")) { - Utils.sendMessage(player, localization.getMessage("noPermission")); - Utils.playSound(player, "noPermission"); - return; - } - - HeadAPI.addFavoriteHead(player.getUniqueId(), head.getValue()); - Utils.sendMessage(player, "&7Added &e" + head.getName() + " &7to favorites."); - Utils.playSound(player, "addFavorite"); - } - })); - } - - pane.open(player); - return pane; - } - - public static void openTagSearchDatabase(Player player, String tag) { - List heads = filter(HeadAPI.getHeadsByTag(tag)); - - PagedPane pane = new PagedPane(4, 6, - replace(localization.getMessage("menu.tagSearch"), heads.size(), "None", tag, player)); - for (Head head : heads) { - pane.addButton(new Button(head.getMenuItem(), e -> { - if (e.getClick() == ClickType.SHIFT_LEFT) { - purchaseHead(player, head, 64, head.getCategory().getName(), head.getName()); - } - if (e.getClick() == ClickType.LEFT) { - purchaseHead(player, head, 1, head.getCategory().getName(), head.getName()); - } - if (e.getClick() == ClickType.RIGHT) { - if (!player.hasPermission("headdb.favorites")) { - Utils.sendMessage(player, localization.getMessage("noPermission")); - Utils.playSound(player, "noPermission"); - return; - } - - HeadAPI.addFavoriteHead(player.getUniqueId(), head.getValue()); - Utils.sendMessage(player, "&7Added &e" + head.getName() + " &7to favorites."); - Utils.playSound(player, "addFavorite"); - } - })); - } - - pane.open(player); - } - - public static void openCategoryDatabase(Player player, Category category) { - List heads = filter(HeadAPI.getHeads(category)); - - PagedPane pane = new PagedPane(4, 6, - replace(localization.getMessage("menu.category"), heads.size(), category.getName(), "None", player)); - for (Head head : heads) { - pane.addButton(new Button(head.getMenuItem(), e -> { - if (e.getClick() == ClickType.SHIFT_LEFT) { - purchaseHead(player, head, 64, head.getCategory().getName(), head.getName()); - } - if (e.getClick() == ClickType.LEFT) { - purchaseHead(player, head, 1, head.getCategory().getName(), head.getName()); - } - if (e.getClick() == ClickType.RIGHT) { - if (!player.hasPermission("headdb.favorites")) { - Utils.sendMessage(player, localization.getMessage("noPermission")); - Utils.playSound(player, "noPermission"); - return; - } - - HeadAPI.addFavoriteHead(player.getUniqueId(), head.getValue()); - Utils.sendMessage(player, "&7Added &e" + head.getName() + " &7to favorites."); - Utils.playSound(player, "addFavorite"); - } - })); - } - - pane.open(player); - } - - @SuppressWarnings("ConstantConditions") - public static void openDatabase(Player player) { - Inventory inventory = Bukkit.createInventory(null, 54, - replace(localization.getMessage("menu.main"), HeadAPI.getHeads().size(), "Main", "None", player)); - - for (Category category : Category.getCache()) { - ItemStack item = getUIItem(category.getName(), category.getItem()); - ItemMeta meta = item.getItemMeta(); - meta.setDisplayName(Utils.colorize(localization.getMessage("menu.heads." + category.getName()))); - List lore = new ArrayList<>(); - lore.add(Utils.colorize(replace(localization.getMessage("menu.lore"), HeadAPI.getHeads(category).size(), "Main", "None", player))); - meta.setLore(lore); - item.setItemMeta(meta); - inventory.setItem(getUILocation(category.getName(), category.getLocation()), item); - } - - if (player.hasPermission("headdb.favorites")) { - inventory.setItem(getUILocation("favorites", 39), buildButton( - getUIItem("favorites", new ItemStack(Material.BOOK)), - "&eFavorites", - "", - "&8Click to view your favorites") - ); - } - - if (player.hasPermission("headdb.search")) { - inventory.setItem(getUILocation("search", 40), buildButton( - getUIItem("search", new ItemStack(Material.DARK_OAK_SIGN)), - "&9Search", - "", - "&8Click to open search menu" - )); - } - - if (player.hasPermission("headdb.local")) { - inventory.setItem(getUILocation("local", 41), buildButton( - getUIItem("local", new ItemStack(Material.COMPASS)), - "&aLocal", - "", - "&8Heads from any players that have logged on the server" - )); - } - - fill(inventory); - player.openInventory(inventory); - } - - private static void fill(Inventory inv) { - ItemStack item = getUIItem("fill", new ItemStack(Material.BLACK_STAINED_GLASS_PANE)); - // Do not bother filling the inventory if item to fill it with is AIR. - if (item == null || item.getType() == Material.AIR) return; - - // Fill any non-empty inventory slots with the given item. - int size = inv.getSize(); - for (int i = 0; i < size; i++) { - ItemStack slotItem = inv.getItem(i); - if (slotItem == null || slotItem.getType() == Material.AIR) { - inv.setItem(i, item); - } - } - } - - private static int getUILocation(String category, int slot) { - // Try to use the cached value first. - if (uiLocation.containsKey(category)) return uiLocation.get(category); - - // Try to get the value from the config file. - if (HeadDB.getInstance().getConfig().contains("ui.category." + category + ".location")) { - uiLocation.put(category, HeadDB.getInstance().getConfig().getInt("ui.category." + category + ".location")); - return uiLocation.get(category); - } - - // No valid value in the config file, return the given default. - uiLocation.put(category, slot); - return slot; - } - - @SuppressWarnings("ConstantConditions") - private static ItemStack getUIItem(String category, ItemStack item) { - // Try to use the cached item first. - if (uiItem.containsKey(category)) return uiItem.get(category); - - // Try to get a head from the config file. - if (HeadDB.getInstance().getConfig().contains("ui.category." + category + ".head")) { - int id = HeadDB.getInstance().getConfig().getInt("ui.category." + category + ".head"); - Head head = HeadAPI.getHeadByID(id); - if (head != null) { - uiItem.put(category, head.getMenuItem()); - return uiItem.get(category); - } - } - - // Try to get an item from the config file. - if (HeadDB.getInstance().getConfig().contains("ui.category." + category + ".item")) { - String cfg = HeadDB.getInstance().getConfig().getString("ui.category." + category + ".item"); - Material mat = Material.matchMaterial(cfg); - - // AIR is allowed as the fill material for the menu, but not as a category item. - if (mat != null && (category.equals("fill") || mat != Material.AIR)) { - uiItem.put(category, new ItemStack(mat)); - return uiItem.get(category); - } - } - - // No valid head or item in the config file, return the given default. - uiItem.put(category, item); - return item; - } - - @SuppressWarnings("ConstantConditions") - private static ItemStack buildButton(ItemStack item, String name, String... lore) { - ItemMeta meta = item.getItemMeta(); - meta.setDisplayName(Utils.colorize(name)); - List list = meta.getLore(); - if (list == null) { - list = new ArrayList<>(); - } - - for (String line : lore) { - list.add(Utils.colorize(line)); - } - - item.setItemMeta(meta); - return item; - } - - public static double getCategoryCost(Player player, String category) { - // If the player has the permission headdb.economy.free or headdb.economy.CATEGORY.free, the item is free. - if (player.hasPermission("headdb.economy.free") || player.hasPermission("headdb.economy." + category + ".free")) return 0; - - // Otherwise, get the price for this category from the config file. - return HeadDB.getInstance().getConfig().getDouble("economy.cost." + category); - } - - 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"); - result.accept(true); - return; - } - - 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) { - economyProvider.canPurchase(player, cost, paymentResult -> { - if (Boolean.TRUE.equals(paymentResult)) { - economyProvider.charge(player, cost, chargeResult -> { - if (Boolean.TRUE.equals(chargeResult)) { - Utils.sendMessage(player, String.format(localization.getMessage("purchasedHead"), amount, description, cost)); - Utils.playSound(player, "paid"); - result.accept(true); - } - }); - } else { - 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 (config.getBoolean("closeOnPurchase", false)) { - player.closeInventory(); - } - - Utils.sendMessage(player, String.format(localization.getMessage("processPayment"), amount, head.getName())); - processPayment(player, amount, category, description, result -> { - if (Boolean.TRUE.equals(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); - - // Commands can only be ran on main thread - Bukkit.getScheduler().runTask(HeadDB.getInstance(), () -> runPurchaseCommands(player)); - } - } - }); - } - - private static void runPurchaseCommands(Player player) { - for (String cmd : config.getStringList("commands.purchase")) { - if (cmd.isEmpty()) continue; - if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) PlaceholderAPI.setPlaceholders(player, cmd); - Bukkit.dispatchCommand(Bukkit.getConsoleSender(), cmd.replace("%player%", player.getName())); - } - } - - private static String replace(String message, int size, String category, String search, Player player) { - return message - .replace("%size%", String.valueOf(size)) - .replace("%category%", category) - .replace("%search%", search) - .replace("%player%", player.getName()); - } - - private static List filter(List source) { - if (!config.getBoolean("hidden.enabled")) { - return source; - } - - List result = new ArrayList<>(); - List tags = config.getStringList("hidden.tags"); - List names = config.getStringList("hidden.names"); - - for (Head head : source) { - if (!names.contains(head.getName()) && !contains(head.getTags(), tags)) { - result.add(head); - } - } - - - return result; - } - - private static boolean contains(List list1, List list2) { - return list1.stream().anyMatch(list2.stream().collect(Collectors.toSet())::contains); - } - -} diff --git a/src/main/java/tsp/headdb/inventory/PagedPane.java b/src/main/java/tsp/headdb/inventory/PagedPane.java deleted file mode 100644 index 5f58768..0000000 --- a/src/main/java/tsp/headdb/inventory/PagedPane.java +++ /dev/null @@ -1,386 +0,0 @@ -package tsp.headdb.inventory; - -import net.wesjd.anvilgui.AnvilGUI; -import org.bukkit.Bukkit; -import org.bukkit.ChatColor; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.event.inventory.ClickType; -import org.bukkit.event.inventory.InventoryClickEvent; -import org.bukkit.event.inventory.InventoryType; -import org.bukkit.inventory.Inventory; -import org.bukkit.inventory.InventoryHolder; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.ItemMeta; -import tsp.headdb.HeadDB; -import tsp.headdb.api.HeadAPI; -import tsp.headdb.util.Utils; - -import java.util.*; -import java.util.Map.Entry; - -/** - * A paged pane. Credits @ I Al Ianstaan - */ -public class PagedPane implements InventoryHolder { - - private Inventory inventory; - - private SortedMap pages = new TreeMap<>(); - private int currentIndex; - private int pageSize; - - @SuppressWarnings("WeakerAccess") - protected Button controlBack; - @SuppressWarnings("WeakerAccess") - protected Button controlNext; - @SuppressWarnings("WeakerAccess") - protected Button controlMain; - - /** - * @param pageSize The page size. inventory rows - 2 - */ - public PagedPane(int pageSize, int rows, String title) { - Objects.requireNonNull(title, "title can not be null!"); - if (rows > 6) { - throw new IllegalArgumentException("Rows must be <= 6, got " + rows); - } - if (pageSize > 6) { - throw new IllegalArgumentException("Page size must be <= 6, got" + pageSize); - } - - this.pageSize = pageSize; - inventory = Bukkit.createInventory(this, rows * 9, color(title)); - - pages.put(0, new Page(pageSize)); - } - - /** - * @param button The button to add - */ - public void addButton(Button button) { - for (Entry entry : pages.entrySet()) { - if (entry.getValue().addButton(button)) { - if (entry.getKey() == currentIndex) { - reRender(); - } - return; - } - } - Page page = new Page(pageSize); - page.addButton(button); - pages.put(pages.lastKey() + 1, page); - - reRender(); - } - - /** - * @param button The Button to remove - */ - @SuppressWarnings("unused") - public void removeButton(Button button) { - for (Iterator> iterator = pages.entrySet().iterator(); iterator.hasNext(); ) { - Entry entry = iterator.next(); - if (entry.getValue().removeButton(button)) { - - // we may need to delete the page - if (entry.getValue().isEmpty()) { - // we have more than one page, so delete it - if (pages.size() > 1) { - iterator.remove(); - } - // the currentIndex now points to a page that does not exist. Correct it. - if (currentIndex >= pages.size()) { - currentIndex--; - } - } - // if we modified the current one, re-render - // if we deleted the current page, re-render too - if (entry.getKey() >= currentIndex) { - reRender(); - } - return; - } - } - } - - /** - * @return The amount of pages - */ - @SuppressWarnings("WeakerAccess") - public int getPageAmount() { - return pages.size(); - } - - /** - * @return The number of the current page (1 based) - */ - @SuppressWarnings("WeakerAccess") - public int getCurrentPage() { - return currentIndex + 1; - } - - /** - * @param index The index of the new page - */ - @SuppressWarnings("WeakerAccess") - public void selectPage(int index) { - if (index < 0 || index >= getPageAmount()) { - throw new IllegalArgumentException( - "Index out of bounds s: " + index + " [" + 0 + " " + getPageAmount() + ")" - ); - } - if (index == currentIndex) { - return; - } - - currentIndex = index; - reRender(); - } - - /** - * Renders the inventory again - */ - @SuppressWarnings("WeakerAccess") - public void reRender() { - inventory.clear(); - pages.get(currentIndex).render(inventory); - - controlBack = null; - controlNext = null; - controlMain = null; - createControls(inventory); - } - - /** - * @param event The {@link InventoryClickEvent} - */ - @SuppressWarnings("WeakerAccess") - public void onClick(InventoryClickEvent event) { - event.setCancelled(true); - - // back item - if (event.getSlot() == inventory.getSize() - 8) { - if (controlBack != null) { - controlBack.onClick(event); - } - return; - } - // next item - else if (event.getSlot() == inventory.getSize() - 2) { - if (controlNext != null) { - controlNext.onClick(event); - } - return; - } - else if (event.getSlot() == inventory.getSize()- 5) { - if (controlMain != null){ - controlMain.onClick(event); - } - return; - } - - pages.get(currentIndex).handleClick(event); - - } - - /** - * Get the object's inventory. - * - * @return The inventory. - */ - @Override - public Inventory getInventory() { - return inventory; - } - - /** - * Creates the controls - * - * @param inventory The inventory - */ - @SuppressWarnings("WeakerAccess") - protected void createControls(Inventory inventory) { - // create separator - fillRow( - inventory.getSize() / 9 - 2, - new ItemStack(Material.BLACK_STAINED_GLASS_PANE), - inventory - ); - - if (getCurrentPage() > 1) { - String name = String.format( - Locale.ROOT, - "&3&lPage &a&l%d &7/ &c&l%d", - getCurrentPage() - 1, getPageAmount() - ); - String lore = String.format( - Locale.ROOT, - "&7Previous: &c%d", - getCurrentPage() - 1 - ); - ItemStack itemStack = setMeta(HeadAPI.getHeadByValue("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODY1MmUyYjkzNmNhODAyNmJkMjg2NTFkN2M5ZjI4MTlkMmU5MjM2OTc3MzRkMThkZmRiMTM1NTBmOGZkYWQ1ZiJ9fX0=").getMenuItem(), name, lore); - controlBack = new Button(itemStack, event -> selectPage(currentIndex - 1)); - inventory.setItem(inventory.getSize() - 8, itemStack); - } - - if (getCurrentPage() < getPageAmount()) { - String name = String.format( - Locale.ROOT, - "&3&lPage &a&l%d &7/ &c&l%d", - getCurrentPage() + 1, getPageAmount() - ); - String lore = String.format( - Locale.ROOT, - "&7Next: &c%d", - getCurrentPage() + 1 - ); - ItemStack itemStack = setMeta(HeadAPI.getHeadByValue("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmEzYjhmNjgxZGFhZDhiZjQzNmNhZThkYTNmZTgxMzFmNjJhMTYyYWI4MWFmNjM5YzNlMDY0NGFhNmFiYWMyZiJ9fX0=").getMenuItem(), name, lore); - controlNext = new Button(itemStack, event -> selectPage(getCurrentPage())); - inventory.setItem(inventory.getSize() - 2, itemStack); - } - - String name = String.format( - Locale.ROOT, - "&3&lPage &a&l%d &7/ &c&l%d", - getCurrentPage(), getPageAmount() - ); - ItemStack itemStack = setMeta(HeadAPI.getHeadByValue("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2Q5MWY1MTI2NmVkZGM2MjA3ZjEyYWU4ZDdhNDljNWRiMDQxNWFkYTA0ZGFiOTJiYjc2ODZhZmRiMTdmNGQ0ZSJ9fX0=").getMenuItem(), - name, - "&7Left-Click to go to the &cMain Menu", - "&7Right-Click to go to a &6Specific Page"); - controlMain = new Button(itemStack, event -> { - if (event.getClick() == ClickType.RIGHT) { - new AnvilGUI.Builder() - .onComplete((player, text) -> { - try { - int i = Integer.parseInt(text); - if (i > getPageAmount()) { - Utils.sendMessage(player, "&cPage number is out of bounds! Max: &e" + getPageAmount()); - return AnvilGUI.Response.text("&cOut of bounds!"); - } - selectPage(i - 1); - return AnvilGUI.Response.openInventory(this.getInventory()); - } catch (NumberFormatException nfe) { - Utils.sendMessage(player, "&cValue must be a number!"); - return AnvilGUI.Response.text(Utils.colorize("&cValue must be a number!")); - } - }) - .title("Select Page") - .text("Page number...") - .plugin(HeadDB.getInstance()) - .open((Player) event.getWhoClicked()); - } else { - InventoryUtils.openDatabase((Player) event.getWhoClicked()); - } - }); - inventory.setItem(inventory.getSize() - 5, itemStack); - } - - private void fillRow(int rowIndex, ItemStack itemStack, Inventory inventory) { - int yMod = rowIndex * 9; - for (int i = 0; i < 9; i++) { - int slot = yMod + i; - inventory.setItem(slot, setMeta(itemStack, "")); - } - } - - protected ItemStack setMeta(ItemStack itemStack, String name, String... lore) { - ItemMeta meta = itemStack.getItemMeta(); - meta.setDisplayName(Utils.colorize(name)); - meta.setLore(Arrays.stream(lore).map(this::color).toList()); - itemStack.setItemMeta(meta); - return itemStack; - } - - @SuppressWarnings("WeakerAccess") - protected String color(String input) { - return ChatColor.translateAlternateColorCodes('&', input); - } - - /** - * @param player The {@link Player} to open it for - */ - public void open(Player player) { - reRender(); - player.openInventory(getInventory()); - } - - - private static class Page { - private List