Mirror von
https://github.com/TheSilentPro/HeadDB.git
synchronisiert 2024-12-26 19:02:39 +01:00
rewrite
Dieser Commit ist enthalten in:
Ursprung
0440d2be17
Commit
5bdca1ac67
56
pom.xml
56
pom.xml
@ -6,38 +6,30 @@
|
||||
|
||||
<groupId>tsp.headdb</groupId>
|
||||
<artifactId>HeadDB</artifactId>
|
||||
<version>4.4.4</version>
|
||||
<version>5.0.0</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<sonar.projectKey>TheSilentPro_HeadDB</sonar.projectKey>
|
||||
<sonar.organization>silent</sonar.organization>
|
||||
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
|
||||
<sonar.exclusions>src/main/java/tsp/headdb/Metrics.java</sonar.exclusions>
|
||||
<sonar.coverage.exclusions>*</sonar.coverage.exclusions>
|
||||
<spigot.id>84967</spigot.id>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.target.source>17</maven.target.source>
|
||||
</properties>
|
||||
|
||||
<name>HeadDB</name>
|
||||
<description>Database with thousands of heads</description>
|
||||
|
||||
<repositories>
|
||||
<!-- Spigot Repo -->
|
||||
<repository>
|
||||
<id>spigot-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
|
||||
</repository>
|
||||
<!-- Mojang Repo -->
|
||||
<repository>
|
||||
<id>mojang-repo</id>
|
||||
<url>https://libraries.minecraft.net/</url>
|
||||
</repository>
|
||||
<!-- JitPack -->
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
<!-- CodeMC -->
|
||||
<repository>
|
||||
<id>CodeMC</id>
|
||||
<url>https://repo.codemc.org/repository/maven-public</url>
|
||||
@ -49,34 +41,50 @@
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spigot API -->
|
||||
<!-- Hard Dependencies (Provided) -->
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.19-R0.1-SNAPSHOT</version>
|
||||
<version>1.19.2-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- Mojang Auth Lib -->
|
||||
<dependency>
|
||||
<groupId>com.mojang</groupId>
|
||||
<artifactId>authlib</artifactId>
|
||||
<version>1.5.21</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- AnvilGUI -->
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.10</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Hard Dependencies (Shaded) -->
|
||||
<dependency>
|
||||
<groupId>net.wesjd</groupId>
|
||||
<artifactId>anvilgui</artifactId>
|
||||
<version>1.5.3-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<!-- Vault -->
|
||||
<dependency>
|
||||
<groupId>com.github.TheSilentPro</groupId>
|
||||
<artifactId>SmartPlugin</artifactId>
|
||||
<version>efc139dd46</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.TheSilentPro</groupId>
|
||||
<artifactId>Warehouse</artifactId>
|
||||
<version>2ae4ef1f97</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Soft Dependencies -->
|
||||
<dependency>
|
||||
<groupId>com.github.MilkBowl</groupId>
|
||||
<artifactId>VaultAPI</artifactId>
|
||||
<version>1.7</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- Treasury -->
|
||||
<dependency>
|
||||
<groupId>me.lokka30</groupId>
|
||||
<artifactId>treasury-api</artifactId>
|
||||
@ -105,10 +113,6 @@
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.8.1</version>
|
||||
<configuration>
|
||||
<source>16</source>
|
||||
<target>16</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Shade -->
|
||||
@ -139,16 +143,6 @@
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>3.3.2</version>
|
||||
</plugin>
|
||||
|
||||
<!-- Sonarcloud -->
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>sonar-maven-plugin</artifactId>
|
||||
<version>3.9.1.2184</version>
|
||||
<configuration>
|
||||
<goalPrefix>sonar</goalPrefix>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
@ -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";
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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 <a
|
||||
* href="https://bstats.org/what-is-my-plugin-id">What is my plugin id?</a>
|
||||
*/
|
||||
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<JsonObjectBuilder> appendPlatformDataConsumer;
|
||||
|
||||
private final Consumer<JsonObjectBuilder> appendServiceDataConsumer;
|
||||
|
||||
private final Consumer<Runnable> submitTaskConsumer;
|
||||
|
||||
private final Supplier<Boolean> checkServiceEnabledSupplier;
|
||||
|
||||
private final BiConsumer<String, Throwable> errorLogger;
|
||||
|
||||
private final Consumer<String> infoLogger;
|
||||
|
||||
private final boolean logErrors;
|
||||
|
||||
private final boolean logSentData;
|
||||
|
||||
private final boolean logResponseStatusText;
|
||||
|
||||
private final Set<CustomChart> 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<JsonObjectBuilder> appendPlatformDataConsumer,
|
||||
Consumer<JsonObjectBuilder> appendServiceDataConsumer,
|
||||
Consumer<Runnable> submitTaskConsumer,
|
||||
Supplier<Boolean> checkServiceEnabledSupplier,
|
||||
BiConsumer<String, Throwable> errorLogger,
|
||||
Consumer<String> 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<Map<String, Map<String, Integer>>> 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<Map<String, Map<String, Integer>>> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
|
||||
Map<String, Map<String, Integer>> map = callable.call();
|
||||
if (map == null || map.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
boolean reallyAllSkipped = true;
|
||||
for (Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) {
|
||||
JsonObjectBuilder valueBuilder = new JsonObjectBuilder();
|
||||
boolean allSkipped = true;
|
||||
for (Map.Entry<String, Integer> 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<Map<String, Integer>> 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<Map<String, Integer>> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
|
||||
Map<String, Integer> map = callable.call();
|
||||
if (map == null || map.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
boolean allSkipped = true;
|
||||
for (Map.Entry<String, Integer> 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<Map<String, Integer>> 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<Map<String, Integer>> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
|
||||
Map<String, Integer> map = callable.call();
|
||||
if (map == null || map.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
boolean allSkipped = true;
|
||||
for (Map.Entry<String, Integer> 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<Map<String, Integer>> 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<Map<String, Integer>> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
|
||||
Map<String, Integer> map = callable.call();
|
||||
if (map == null || map.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
for (Map.Entry<String, Integer> 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<String, Throwable> 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<String> 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<String> 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<Map<String, int[]>> 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<Map<String, int[]>> callable) {
|
||||
super(chartId);
|
||||
this.callable = callable;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JsonObjectBuilder.JsonObject getChartData() throws Exception {
|
||||
JsonObjectBuilder valuesBuilder = new JsonObjectBuilder();
|
||||
Map<String, int[]> map = callable.call();
|
||||
if (map == null || map.isEmpty()) {
|
||||
// Null = skip the chart
|
||||
return null;
|
||||
}
|
||||
boolean allSkipped = true;
|
||||
for (Map.Entry<String, int[]> 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<Integer> 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<Integer> 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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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.
|
||||
*
|
||||
* <p>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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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<Head> 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<Head> 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<Head> 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<Head> 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<Head> 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<Head> 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<LocalHead> getLocalHeads() {
|
||||
return HeadDB.getInstance().getPlayerData().getEntries().stream()
|
||||
.map(entry -> Bukkit.getOfflinePlayer(UUID.fromString(entry)))
|
||||
.map(player -> new LocalHead(player.getUniqueId()).name(player.getName()))
|
||||
.toList();
|
||||
}
|
||||
|
||||
}
|
@ -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<Category, List<Head>> heads;
|
||||
|
||||
public DatabaseUpdateEvent(HeadDatabase database, Map<Category, List<Head>> 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<Category, List<Head>> getHeads() {
|
||||
return heads;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerList getHandlers() {
|
||||
return handlerList;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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 <id> <player> &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!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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<name> &7- Search for heads matching a name");
|
||||
Utils.sendMessage(sender, "&7 > &c/hdb tagsearch &9(ts) &c<tag> &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<id> <player> &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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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.");
|
||||
}
|
||||
|
||||
}
|
@ -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"));
|
||||
}
|
||||
|
||||
}
|
@ -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 <name>");
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@ -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 <tag>");
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
@ -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!");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
33
src/main/java/tsp/headdb/core/api/HeadAPI.java
Normale Datei
33
src/main/java/tsp/headdb/core/api/HeadAPI.java
Normale Datei
@ -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<Head> getHeads(Category category) {
|
||||
return database.getHeads().get(category);
|
||||
}
|
||||
|
||||
public static Map<Category, List<Head>> getHeads() {
|
||||
return database.getHeads();
|
||||
}
|
||||
|
||||
public static int getTotalHeads() {
|
||||
return database.getSize();
|
||||
}
|
||||
|
||||
public static HeadDatabase getDatabase() {
|
||||
return database;
|
||||
}
|
||||
|
||||
}
|
86
src/main/java/tsp/headdb/core/command/CommandMain.java
Normale Datei
86
src/main/java/tsp/headdb/core/command/CommandMain.java
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
22
src/main/java/tsp/headdb/core/command/CommandManager.java
Normale Datei
22
src/main/java/tsp/headdb/core/command/CommandManager.java
Normale Datei
@ -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<String, SubCommand> commands = new HashMap<>();
|
||||
|
||||
public void register(SubCommand command) {
|
||||
this.commands.put(command.getName(), command);
|
||||
}
|
||||
|
||||
public Optional<SubCommand> getCommand(String name) {
|
||||
return Optional.ofNullable(commands.get(name));
|
||||
}
|
||||
|
||||
public Map<String, SubCommand> getCommandsMap() {
|
||||
return commands;
|
||||
}
|
||||
}
|
38
src/main/java/tsp/headdb/core/command/HeadDBCommand.java
Normale Datei
38
src/main/java/tsp/headdb/core/command/HeadDBCommand.java
Normale Datei
@ -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<String[]> getAliases() {
|
||||
return Optional.ofNullable(aliases);
|
||||
}
|
||||
|
||||
}
|
9
src/main/java/tsp/headdb/core/command/SubCommand.java
Normale Datei
9
src/main/java/tsp/headdb/core/command/SubCommand.java
Normale Datei
@ -0,0 +1,9 @@
|
||||
package tsp.headdb.core.command;
|
||||
|
||||
public abstract class SubCommand extends HeadDBCommand {
|
||||
|
||||
public SubCommand(String name) {
|
||||
super(name, "headdb.command." + name);
|
||||
}
|
||||
|
||||
}
|
6
src/main/java/tsp/headdb/core/storage/PlayerData.java
Normale Datei
6
src/main/java/tsp/headdb/core/storage/PlayerData.java
Normale Datei
@ -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<String> favorites) {}
|
38
src/main/java/tsp/headdb/core/storage/PlayerStorage.java
Normale Datei
38
src/main/java/tsp/headdb/core/storage/PlayerStorage.java
Normale Datei
@ -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<PlayerData> {
|
||||
|
||||
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<Collection<PlayerData>> load() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> save(Collection<PlayerData> 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);
|
||||
}
|
||||
|
||||
}
|
25
src/main/java/tsp/headdb/core/task/DatabaseUpdateTask.java
Normale Datei
25
src/main/java/tsp/headdb/core/task/DatabaseUpdateTask.java
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
16
src/main/java/tsp/headdb/core/util/Utils.java
Normale Datei
16
src/main/java/tsp/headdb/core/util/Utils.java
Normale Datei
@ -0,0 +1,16 @@
|
||||
package tsp.headdb.core.util;
|
||||
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static Optional<UUID> validateUniqueId(String raw) {
|
||||
try {
|
||||
return Optional.of(UUID.fromString(raw));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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<Boolean> 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<Boolean> result) {
|
||||
result.accept(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience method for initializing economy
|
||||
*
|
||||
* @see VaultProvider#initProvider()
|
||||
* @see TreasuryProvider#initProvider()
|
||||
*/
|
||||
void initProvider();
|
||||
|
||||
}
|
@ -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<Boolean> result) {
|
||||
EconomySubscriber
|
||||
.<Boolean>asFuture(s -> provider.hasPlayerAccount(player.getUniqueId(), s))
|
||||
.thenCompose(val -> {
|
||||
if (Boolean.TRUE.equals(val)) {
|
||||
return EconomySubscriber.<PlayerAccount>asFuture(s -> provider.retrievePlayerAccount(player.getUniqueId(), s));
|
||||
} else {
|
||||
return EconomySubscriber.<PlayerAccount>asFuture(s -> provider.createPlayerAccount(player.getUniqueId(), s));
|
||||
}
|
||||
})
|
||||
.thenCompose(account -> EconomySubscriber.<BigDecimal>asFuture(s -> account.retrieveBalance(currency, s)))
|
||||
.whenComplete((bal, ex) -> result.accept(bal.compareTo(cost) >= 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void charge(Player player, BigDecimal amount, Consumer<Boolean> result) {
|
||||
EconomySubscriber
|
||||
.<Boolean>asFuture(s -> provider.hasPlayerAccount(player.getUniqueId(), s))
|
||||
.thenCompose(val -> {
|
||||
if (Boolean.TRUE.equals(val)) {
|
||||
return EconomySubscriber.<PlayerAccount>asFuture(s -> provider.retrievePlayerAccount(player.getUniqueId(), s));
|
||||
} else {
|
||||
return EconomySubscriber.<PlayerAccount>asFuture(s -> provider.createPlayerAccount(player.getUniqueId(), s));
|
||||
}
|
||||
}).whenComplete((account, ex) -> account.withdrawBalance(
|
||||
amount,
|
||||
transactionInitiator,
|
||||
currency,
|
||||
new EconomySubscriber<BigDecimal>() {
|
||||
@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<EconomyProvider>> 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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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<Boolean> result) {
|
||||
Utils.async(t -> result.accept(economy.has(player, cost.doubleValue())));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void charge(Player player, BigDecimal amount, Consumer<Boolean> 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<Economy> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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<Head> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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!"));
|
||||
}
|
||||
|
||||
}
|
@ -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<String> 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<String> 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<String> 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();
|
||||
}
|
||||
|
||||
}
|
@ -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<Category, List<Head>> 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<Head> fetched = getHeads();
|
||||
for (Head head : fetched) {
|
||||
if (head.getValue().equals(value)) {
|
||||
return head;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Head getHeadByID(int id) {
|
||||
List<Head> fetched = getHeads();
|
||||
for (Head head : fetched) {
|
||||
if (head.getId() == id) {
|
||||
return head;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Head getHeadByUniqueId(UUID uuid) {
|
||||
List<Head> fetched = getHeads();
|
||||
for (Head head : fetched) {
|
||||
if (head.getUniqueId().equals(uuid)) {
|
||||
return head;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public List<Head> getHeadsByTag(String tag) {
|
||||
List<Head> result = new ArrayList<>();
|
||||
List<Head> 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<Head> getHeadsByName(Category category, String name) {
|
||||
List<Head> result = new ArrayList<>();
|
||||
List<Head> 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<Head> getHeadsByName(String name) {
|
||||
List<Head> result = new ArrayList<>();
|
||||
for (Category category : Category.getCache()) {
|
||||
result.addAll(getHeadsByName(category, name));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<Head> getHeads(Category category) {
|
||||
List<Head> 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<Head> getHeads() {
|
||||
List<Head> result = new ArrayList<>();
|
||||
for (Category category : heads.keySet()) {
|
||||
result.addAll(getHeads(category));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public void getHeadsNoCache(Consumer<Map<Category, List<Head>>> resultSet) {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, task -> {
|
||||
Log.debug("[" + plugin.getName() + "] Updating database... ");
|
||||
EnumMap<Category, List<Head>> result = new EnumMap<>(Category.class);
|
||||
Category[] categories = Category.getCache();
|
||||
|
||||
for (Category category : categories) {
|
||||
Log.debug("Caching heads from: " + category.getName());
|
||||
List<Head> 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<Head> gather(String url, Category category) throws IOException, ParseException {
|
||||
long start = System.currentTimeMillis();
|
||||
List<Head> 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<Map<Category, List<Head>>> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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<String> 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<String> 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;
|
||||
}
|
||||
|
||||
}
|
55
src/main/java/tsp/headdb/implementation/category/Category.java
Normale Datei
55
src/main/java/tsp/headdb/implementation/category/Category.java
Normale Datei
@ -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<Head> 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();
|
||||
}
|
||||
|
||||
}
|
49
src/main/java/tsp/headdb/implementation/head/Head.java
Normale Datei
49
src/main/java/tsp/headdb/implementation/head/Head.java
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
75
src/main/java/tsp/headdb/implementation/head/HeadDatabase.java
Normale Datei
75
src/main/java/tsp/headdb/implementation/head/HeadDatabase.java
Normale Datei
@ -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<Category, List<Head>> 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<Category, List<Head>> getHeads() {
|
||||
return heads;
|
||||
}
|
||||
|
||||
public void getHeadsNoCache(Consumer<Map<Category, List<Head>>> heads) {
|
||||
getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
Map<Category, List<Head>> 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;
|
||||
}
|
||||
|
||||
}
|
77
src/main/java/tsp/headdb/implementation/requester/Requester.java
Normale Datei
77
src/main/java/tsp/headdb/implementation/requester/Requester.java
Normale Datei
@ -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<List<Head>> heads) throws IOException {
|
||||
fetch(category, response -> {
|
||||
List<Head> 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> 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;
|
||||
}
|
||||
|
||||
}
|
3
src/main/java/tsp/headdb/implementation/requester/Response.java
Normale Datei
3
src/main/java/tsp/headdb/implementation/requester/Response.java
Normale Datei
@ -0,0 +1,3 @@
|
||||
package tsp.headdb.implementation.requester;
|
||||
|
||||
public record Response(String response, int code, String date) {}
|
@ -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<InventoryClickEvent> 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<InventoryClickEvent> 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<InventoryClickEvent> 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);
|
||||
}
|
||||
}
|
@ -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<String, Integer> uiLocation = new HashMap<>();
|
||||
private static final Map<String, ItemStack> uiItem = new HashMap<>();
|
||||
|
||||
public static void openLocalMenu(Player player) {
|
||||
List<LocalHead> 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<Head> 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<Head> 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<Head> 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<Head> 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<String> 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<String> 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<Boolean> 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<Head> filter(List<Head> source) {
|
||||
if (!config.getBoolean("hidden.enabled")) {
|
||||
return source;
|
||||
}
|
||||
|
||||
List<Head> result = new ArrayList<>();
|
||||
List<String> tags = config.getStringList("hidden.tags");
|
||||
List<String> 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<String> list1, List<String> list2) {
|
||||
return list1.stream().anyMatch(list2.stream().collect(Collectors.toSet())::contains);
|
||||
}
|
||||
|
||||
}
|
@ -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<Integer, Page> 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<Integer, Page> 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<Entry<Integer, Page>> iterator = pages.entrySet().iterator(); iterator.hasNext(); ) {
|
||||
Entry<Integer, Page> 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<Button> buttons = new ArrayList<>();
|
||||
private int maxSize;
|
||||
|
||||
Page(int maxSize) {
|
||||
this.maxSize = maxSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param event The click event
|
||||
*/
|
||||
void handleClick(InventoryClickEvent event) {
|
||||
// user clicked in his own inventory. Silently drop it
|
||||
if (event.getRawSlot() > event.getInventory().getSize()) {
|
||||
return;
|
||||
}
|
||||
// user clicked outside of the inventory
|
||||
if (event.getSlotType() == InventoryType.SlotType.OUTSIDE) {
|
||||
return;
|
||||
}
|
||||
if (event.getSlot() >= buttons.size()) {
|
||||
return;
|
||||
}
|
||||
Button button = buttons.get(event.getSlot());
|
||||
button.onClick(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if there is space left
|
||||
*/
|
||||
boolean hasSpace() {
|
||||
return buttons.size() < maxSize * 9;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param button The {@link Button} to add
|
||||
*
|
||||
* @return True if the button was added, false if there was no space
|
||||
*/
|
||||
boolean addButton(Button button) {
|
||||
if (!hasSpace()) {
|
||||
return false;
|
||||
}
|
||||
buttons.add(button);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param button The {@link Button} to remove
|
||||
*
|
||||
* @return True if the button was removed
|
||||
*/
|
||||
boolean removeButton(Button button) {
|
||||
return buttons.remove(button);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param inventory The inventory to render in
|
||||
*/
|
||||
void render(Inventory inventory) {
|
||||
for (int i = 0; i < buttons.size(); i++) {
|
||||
Button button = buttons.get(i);
|
||||
|
||||
inventory.setItem(i, button.getItemStack());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if this page is empty
|
||||
*/
|
||||
boolean isEmpty() {
|
||||
return buttons.isEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
package tsp.headdb.listener;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import tsp.headdb.HeadDB;
|
||||
import tsp.headdb.storage.PlayerDataFile;
|
||||
|
||||
/**
|
||||
* This saves heads from players that join
|
||||
* Used for local heads option
|
||||
*/
|
||||
public class JoinListener implements Listener {
|
||||
|
||||
public JoinListener(HeadDB plugin) {
|
||||
// If local heads are disabled, there is no need for this listener.
|
||||
if (HeadDB.getInstance().getConfig().getBoolean("localHeads")) {
|
||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onJoin(PlayerJoinEvent e) {
|
||||
HeadDB.getInstance().getPlayerData().modifyUsername(e.getPlayer().getUniqueId(), e.getPlayer().getName(), PlayerDataFile.ModificationType.SET);
|
||||
HeadDB.getInstance().getPlayerData().save();
|
||||
}
|
||||
|
||||
}
|
@ -1,104 +0,0 @@
|
||||
package tsp.headdb.listener;
|
||||
|
||||
import net.wesjd.anvilgui.AnvilGUI;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.inventory.Inventory;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import tsp.headdb.HeadDB;
|
||||
import tsp.headdb.api.HeadAPI;
|
||||
import tsp.headdb.implementation.Category;
|
||||
import tsp.headdb.inventory.InventoryUtils;
|
||||
import tsp.headdb.util.Utils;
|
||||
|
||||
/**
|
||||
* This handles all clicks on the main inventory
|
||||
*/
|
||||
public class MenuListener implements Listener {
|
||||
|
||||
public MenuListener(JavaPlugin plugin) {
|
||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void click(InventoryClickEvent e) {
|
||||
if (!(e.getWhoClicked() instanceof Player)) {
|
||||
return;
|
||||
}
|
||||
Player player = (Player) e.getWhoClicked();
|
||||
if (e.getView().getTitle().startsWith(Utils.colorize("&c&lHeadDB"))) {
|
||||
e.setCancelled(true);
|
||||
Inventory inventory = e.getClickedInventory();
|
||||
|
||||
if (inventory != null) {
|
||||
int slot = e.getSlot();
|
||||
ItemStack item = inventory.getItem(slot);
|
||||
|
||||
if (item != null && item.getType() != Material.AIR) {
|
||||
String name = ChatColor.stripColor(item.getItemMeta().getDisplayName().toLowerCase());
|
||||
if (name.equalsIgnoreCase("favorites")) {
|
||||
if (!player.hasPermission("headdb.favorites")) {
|
||||
player.closeInventory();
|
||||
Utils.sendMessage(player, "&cYou do not have permission for favorites!");
|
||||
Utils.playSound(player, "noPermission");
|
||||
return;
|
||||
}
|
||||
InventoryUtils.openFavoritesMenu(player);
|
||||
Utils.playSound(player, "open");
|
||||
return;
|
||||
}
|
||||
if (name.equalsIgnoreCase("local")) {
|
||||
if (!player.hasPermission("headdb.local")) {
|
||||
player.closeInventory();
|
||||
Utils.sendMessage(player, "&cYou do not have permission to view local heads!");
|
||||
Utils.playSound(player, "noPermission");
|
||||
return;
|
||||
}
|
||||
InventoryUtils.openLocalMenu(player);
|
||||
Utils.playSound(player, "open");
|
||||
return;
|
||||
}
|
||||
if (name.equalsIgnoreCase("search")) {
|
||||
if (!player.hasPermission("headdb.search")) {
|
||||
player.closeInventory();
|
||||
Utils.sendMessage(player, "&cYou do not have permission for the search function!");
|
||||
Utils.playSound(player, "noPermission");
|
||||
return;
|
||||
}
|
||||
|
||||
new AnvilGUI.Builder()
|
||||
.onComplete((p, text) -> {
|
||||
InventoryUtils.openSearchDatabase(p, text);
|
||||
return AnvilGUI.Response.openInventory(InventoryUtils.openSearchDatabase(p, text).getInventory());
|
||||
})
|
||||
.title("Search Heads")
|
||||
.text("Name...")
|
||||
.plugin(HeadDB.getInstance())
|
||||
.open(player);
|
||||
Utils.playSound(player, "open");
|
||||
return;
|
||||
}
|
||||
|
||||
Category category = Category.getByName(name);
|
||||
|
||||
if (category != null) {
|
||||
if (HeadDB.getInstance().getConfig().getBoolean("requireCategoryPermission") && !player.hasPermission("headdb.category." + category)) {
|
||||
Utils.sendMessage(player, "&cYou do not have permission for this category! (&e" + category.getName() + "&c)");
|
||||
Utils.playSound(player, "noPermission");
|
||||
return;
|
||||
}
|
||||
|
||||
Utils.playSound(player, "open");
|
||||
HeadAPI.openCategoryDatabase(player, category);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,27 +0,0 @@
|
||||
package tsp.headdb.listener;
|
||||
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.inventory.InventoryClickEvent;
|
||||
import org.bukkit.inventory.InventoryHolder;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import tsp.headdb.inventory.PagedPane;
|
||||
|
||||
/**
|
||||
* Listens for click events for the {@link PagedPane}
|
||||
*/
|
||||
public class PagedPaneListener implements Listener {
|
||||
|
||||
public PagedPaneListener(JavaPlugin plugin) {
|
||||
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onClick(InventoryClickEvent event) {
|
||||
InventoryHolder holder = event.getInventory().getHolder();
|
||||
|
||||
if (holder instanceof PagedPane pane) {
|
||||
pane.onClick(event);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,149 +0,0 @@
|
||||
package tsp.headdb.storage;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonPrimitive;
|
||||
import tsp.headdb.HeadDB;
|
||||
import tsp.headdb.util.Log;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Manages the data file that stores information
|
||||
*/
|
||||
public class PlayerDataFile {
|
||||
|
||||
private final File file;
|
||||
private JsonObject main;
|
||||
|
||||
public PlayerDataFile(String name) {
|
||||
HeadDB plugin = HeadDB.getInstance();
|
||||
// This check avoids warning in console
|
||||
if (plugin.getResource(name) != null && !new File(plugin.getDataFolder(), name).exists()) {
|
||||
plugin.saveResource(name, false);
|
||||
}
|
||||
|
||||
this.file = new File(plugin.getDataFolder(), name);
|
||||
}
|
||||
|
||||
@Nonnull
|
||||
public List<String> getFavoriteHeadsByTexture(UUID uuid) {
|
||||
List<String> result = new ArrayList<>();
|
||||
|
||||
if (main.has(uuid.toString()) && main.get(uuid.toString()).getAsJsonObject().has("favorites")) {
|
||||
JsonArray favorites = main.get(uuid.toString()).getAsJsonObject().get("favorites").getAsJsonArray();
|
||||
for (int i = 0; i < favorites.size(); i++) {
|
||||
String str = favorites.get(i).toString();
|
||||
result.add(str.substring(1, str.length() - 1));
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void modifyFavorite(UUID uuid, String textureValue, ModificationType modificationType) {
|
||||
JsonObject userObject;
|
||||
if (main.has(uuid.toString())) {
|
||||
userObject = main.get(uuid.toString()).getAsJsonObject();
|
||||
} else {
|
||||
userObject = new JsonObject();
|
||||
}
|
||||
|
||||
JsonArray favorites;
|
||||
if (userObject.has("favorites")) {
|
||||
favorites = userObject.get("favorites").getAsJsonArray();
|
||||
} else {
|
||||
favorites = new JsonArray();
|
||||
}
|
||||
|
||||
JsonPrimitive value = new JsonPrimitive(textureValue);
|
||||
if (modificationType == ModificationType.SET) {
|
||||
if (favorites.contains(value)) {
|
||||
// Head is already in the list, no need to modify it
|
||||
return;
|
||||
}
|
||||
|
||||
favorites.add(value);
|
||||
} else if (modificationType == ModificationType.REMOVE) {
|
||||
if (!favorites.contains(value)) {
|
||||
// Head is not in the list, no need to modify it
|
||||
return;
|
||||
}
|
||||
|
||||
favorites.remove(value);
|
||||
}
|
||||
|
||||
userObject.add("favorites", favorites);
|
||||
main.add(uuid.toString(), userObject);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getUsername(UUID uuid) {
|
||||
return main.get(uuid.toString()).getAsJsonObject().get("username").toString();
|
||||
}
|
||||
|
||||
public Set<String> getEntries() {
|
||||
return main.keySet();
|
||||
}
|
||||
|
||||
public void modifyUsername(UUID uuid, String username, ModificationType modificationType) {
|
||||
JsonObject userObject;
|
||||
if (main.has(uuid.toString())) {
|
||||
userObject = main.get(uuid.toString()).getAsJsonObject();
|
||||
} else {
|
||||
userObject = new JsonObject();
|
||||
}
|
||||
|
||||
if (modificationType == ModificationType.SET) {
|
||||
userObject.addProperty("username", username);
|
||||
} else {
|
||||
userObject.remove("username");
|
||||
}
|
||||
main.add(uuid.toString(), userObject);
|
||||
}
|
||||
|
||||
public void load() {
|
||||
try {
|
||||
main = new JsonParser().parse(new FileReader(file)).getAsJsonObject();
|
||||
} catch (FileNotFoundException ex) {
|
||||
Log.error("Failed to load player_data.json!");
|
||||
Log.error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public void save() {
|
||||
if (main == null) {
|
||||
Log.debug("No data to save! Skipping...");
|
||||
return;
|
||||
}
|
||||
|
||||
try (FileWriter writer = new FileWriter(file)) {
|
||||
writer.write(main.toString());
|
||||
Log.debug("Saved data to " + file.getName());
|
||||
} catch (IOException ex) {
|
||||
Log.error("Failed to save player_data.json!");
|
||||
Log.error(ex);
|
||||
}
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
public enum ModificationType {
|
||||
SET,
|
||||
REMOVE;
|
||||
}
|
||||
|
||||
}
|
@ -1,49 +0,0 @@
|
||||
package tsp.headdb.util;
|
||||
|
||||
import org.apache.commons.lang.Validate;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Localization class for a messages file
|
||||
*
|
||||
* @author TheSilentPro
|
||||
*/
|
||||
public class Localization {
|
||||
|
||||
private final File file;
|
||||
@Nullable
|
||||
private FileConfiguration data;
|
||||
|
||||
public Localization(@Nonnull File file) {
|
||||
Validate.notNull(file, "File can not be null.");
|
||||
|
||||
this.file = file;
|
||||
this.data = null;
|
||||
}
|
||||
|
||||
public String getMessage(@Nonnull String key) {
|
||||
Validate.notNull(data, "File data is null.");
|
||||
Validate.notNull(key, "Message key can not be null.");
|
||||
|
||||
return Utils.colorize(data.getString(key, "null"));
|
||||
}
|
||||
|
||||
public void load() {
|
||||
this.data = YamlConfiguration.loadConfiguration(file);
|
||||
}
|
||||
|
||||
public File getFile() {
|
||||
return file;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public FileConfiguration getData() {
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
package tsp.headdb.util;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import tsp.headdb.HeadDB;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
/**
|
||||
* @author TheSilentPro
|
||||
*/
|
||||
public class Log {
|
||||
|
||||
private static String name = "&cHeadDB";
|
||||
|
||||
public static void info(String message) {
|
||||
log(LogLevel.INFO, message);
|
||||
}
|
||||
|
||||
public static void warning(String message) {
|
||||
log(LogLevel.WARNING, message);
|
||||
}
|
||||
|
||||
public static void error(String message) {
|
||||
log(LogLevel.ERROR, message);
|
||||
}
|
||||
|
||||
public static void error(Throwable ex) {
|
||||
log(LogLevel.ERROR, ex);
|
||||
}
|
||||
|
||||
public static void debug(String message) {
|
||||
log(LogLevel.DEBUG, message);
|
||||
}
|
||||
|
||||
public static void debug(Throwable ex) {
|
||||
log(LogLevel.DEBUG, ex);
|
||||
}
|
||||
|
||||
public static void log(LogLevel level, String message) {
|
||||
if (level == LogLevel.DEBUG && !HeadDB.getInstance().getConfig().getBoolean("debug")) {
|
||||
return;
|
||||
}
|
||||
Bukkit.getConsoleSender().sendMessage(Utils.colorize("&7[&9&l" + name + "&7] " + level.getColor() + "[" + level.name() + "]: " + message));
|
||||
}
|
||||
|
||||
public static void log(LogLevel level, Throwable ex) {
|
||||
if (level == LogLevel.DEBUG && !HeadDB.getInstance().getConfig().getBoolean("debug")) {
|
||||
return;
|
||||
}
|
||||
Bukkit.getConsoleSender().sendMessage(Utils.colorize("&7[" + name + "&7] " + level.getColor() + "[" + level.name() + "]: " + "&4&l[EXCEPTION]: " + ex.getMessage()));
|
||||
Bukkit.getConsoleSender().sendMessage(Utils.colorize("&4&l[StackTrace]: " + getStackTrace(ex)));
|
||||
}
|
||||
|
||||
public static void setName(String logName) {
|
||||
name = logName;
|
||||
}
|
||||
|
||||
public static String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public static String getStackTrace(Throwable throwable) {
|
||||
StringWriter sw = new StringWriter();
|
||||
PrintWriter pw = new PrintWriter(sw, true);
|
||||
throwable.printStackTrace(pw);
|
||||
return sw.getBuffer().toString();
|
||||
}
|
||||
|
||||
public enum LogLevel {
|
||||
|
||||
INFO,
|
||||
WARNING,
|
||||
ERROR,
|
||||
DEBUG;
|
||||
|
||||
private ChatColor getColor() {
|
||||
switch (this) {
|
||||
case INFO:
|
||||
return ChatColor.GREEN;
|
||||
case WARNING:
|
||||
return ChatColor.YELLOW;
|
||||
case ERROR:
|
||||
return ChatColor.DARK_RED;
|
||||
case DEBUG:
|
||||
return ChatColor.DARK_AQUA;
|
||||
default:
|
||||
return ChatColor.WHITE;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,89 +0,0 @@
|
||||
package tsp.headdb.util;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Sound;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import tsp.headdb.HeadDB;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Several utilities used by the plugin
|
||||
*/
|
||||
public final class Utils {
|
||||
|
||||
private Utils() {}
|
||||
|
||||
private static final FileConfiguration config = HeadDB.getInstance().getConfig();
|
||||
public static final Pattern UUID_PATTERN = Pattern.compile("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}");
|
||||
|
||||
public static void async(Consumer<BukkitTask> task) {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(HeadDB.getInstance(), task);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the latest release from spigot
|
||||
*
|
||||
* @param plugin The plugin
|
||||
* @param id The resource id on spigot
|
||||
* @param latest Whether the plugin is on the latest version
|
||||
*/
|
||||
public static void isLatestVersion(JavaPlugin plugin, int id, Consumer<Boolean> latest) {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, checkTask -> {
|
||||
try {
|
||||
URLConnection connection = new URL("https://api.spigotmc.org/legacy/update.php?resource=" + id).openConnection();
|
||||
connection.setConnectTimeout(5000);
|
||||
connection.setRequestProperty("User-Agent", plugin.getName() + "-VersionChecker");
|
||||
|
||||
latest.accept(new BufferedReader(new InputStreamReader(connection.getInputStream())).readLine().equals(plugin.getDescription().getVersion()));
|
||||
} catch (IOException ex) {
|
||||
latest.accept(true); // Assume the version is latest if checking fails
|
||||
Log.error(ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a UUID (version 4)
|
||||
*
|
||||
* @param uuid UUID to be validated
|
||||
* @return Returns true if the string is a valid UUID
|
||||
*/
|
||||
public static boolean validateUniqueId(String uuid) {
|
||||
return UUID_PATTERN.matcher(uuid).matches();
|
||||
}
|
||||
|
||||
public static void playSound(Player player, String key) {
|
||||
// Check if sound is enabled
|
||||
if (!config.getBoolean("ui.sound.enabled")) {
|
||||
return;
|
||||
}
|
||||
|
||||
player.playSound(player.getLocation(),
|
||||
Sound.valueOf(config.getString("ui.sound." + key + ".name")),
|
||||
(float) config.getDouble("ui.sound." + key + ".volume"),
|
||||
(float) config.getDouble("ui.sound." + key + ".pitch"));
|
||||
}
|
||||
|
||||
public static void sendMessage(CommandSender sender, String message) {
|
||||
if (!message.isEmpty()) {
|
||||
sender.sendMessage(colorize(message));
|
||||
}
|
||||
}
|
||||
|
||||
public static String colorize(String string) {
|
||||
return ChatColor.translateAlternateColorCodes('&', string);
|
||||
}
|
||||
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
noPermission: "&cNo permission!"
|
||||
onlyPlayers: "&cOnly players may open the database."
|
||||
databaseOpen: "&7Opening &cHead Database"
|
||||
invalidPlayer: "&cPlayer is not online!"
|
||||
localFavorites: "&cLocal heads can not be added to favorites!"
|
||||
noEconomy: "&7You received &e%d &7x &e%s&7!"
|
||||
purchasedHead: "&7You purchased &e%dx %s &7for &e%.2f&7!"
|
||||
processPayment: "&7Purchasing &e%dx %s&7. Please wait..."
|
||||
notEnoughMoney: "&cYou do not have enough to purchase &e%dx %s&c."
|
||||
free: "&7You received &e%dx %s &7for &efree&7!"
|
||||
reloadMessages: "&aReloaded messages file!"
|
||||
|
||||
menu:
|
||||
main: "&c&lHeadDB &7(%size%)"
|
||||
category: "&c&lHeadDB &8- &e%category%"
|
||||
tagSearch: "&c&lHeadDB &8- &eTag Search: %search%"
|
||||
search: "&c&lHeadDB &8- &eSearch: %search%"
|
||||
favorites: "&c&lHeadDB &8- &eFavorites: %player%"
|
||||
local: "&c&lHeadDB &8- &aLocal Heads &7(%size%)"
|
||||
lore: "&e%size% heads"
|
||||
heads:
|
||||
alphabet: "&e&lALPHABET"
|
||||
animals: "&3&lANIMALS"
|
||||
blocks: "&8&lBLOCKS"
|
||||
decoration: "&6&lDECORATION"
|
||||
food-drinks: "&6&lFOOD-DRINKS"
|
||||
humans: "&1&lHUMANS"
|
||||
humanoid: "&b&lHUMANOID"
|
||||
miscellaneous: "&2&lMISCELLANEOUS"
|
||||
monsters: "&c&lMONSTERS"
|
||||
plants: "&a&lPLANTS"
|
12
src/main/resources/messages/en.yml
Normale Datei
12
src/main/resources/messages/en.yml
Normale Datei
@ -0,0 +1,12 @@
|
||||
noConsole: "&cOnly for in-game players!"
|
||||
noPermission: "&cNo permission!"
|
||||
invalidSubCommand: "&cInvalid sub-command! Run &e/hdb help&c for more help."
|
||||
openDatabase: "&aOpening HeadDatabase..."
|
||||
|
||||
menu:
|
||||
main:
|
||||
title: "&cHeadDB &7(%size%)"
|
||||
category:
|
||||
name: "&e&l%category%"
|
||||
head:
|
||||
name: "&e%name%"
|
@ -1 +0,0 @@
|
||||
{}
|
@ -4,9 +4,9 @@ description: ${project.description}
|
||||
main: tsp.headdb.HeadDB
|
||||
version: ${project.version}
|
||||
softdepend: [Vault, Treasury]
|
||||
api-version: 1.13
|
||||
author: Silent
|
||||
spigot-id: ${spigot.id}
|
||||
api-version: 1.19
|
||||
author: TheSilentPro (Silent)
|
||||
spigot-id: 84967
|
||||
|
||||
commands:
|
||||
headdb:
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren