3
0
Mirror von https://github.com/TheSilentPro/HeadDB.git synchronisiert 2024-12-26 02:50:07 +01:00
Dieser Commit ist enthalten in:
Silent 2022-11-01 16:54:40 +01:00
Ursprung 0440d2be17
Commit 5bdca1ac67
51 geänderte Dateien mit 619 neuen und 3958 gelöschten Zeilen

56
pom.xml
Datei anzeigen

@ -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>

Datei anzeigen

@ -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";
}));
}
}

Datei anzeigen

@ -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;
}
}
}
}

Datei anzeigen

@ -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();
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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!");
}
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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();
}
}

Datei anzeigen

@ -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.");
}
}

Datei anzeigen

@ -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"));
}
}

Datei anzeigen

@ -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);
}
}

Datei anzeigen

@ -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);
}
}

Datei anzeigen

@ -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!");
});
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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);
}
}

Datei anzeigen

@ -0,0 +1,9 @@
package tsp.headdb.core.command;
public abstract class SubCommand extends HeadDBCommand {
public SubCommand(String name) {
super(name, "headdb.command." + name);
}
}

Datei anzeigen

@ -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) {}

Datei anzeigen

@ -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);
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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();
}
}
}

Datei anzeigen

@ -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();
}

Datei anzeigen

@ -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));
}
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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();
}
}

Datei anzeigen

@ -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!"));
}
}

Datei anzeigen

@ -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();
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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();
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -0,0 +1,3 @@
package tsp.headdb.implementation.requester;
public record Response(String response, int code, String date) {}

Datei anzeigen

@ -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);
}
}

Datei anzeigen

@ -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);
}
}

Datei anzeigen

@ -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();
}
}
}

Datei anzeigen

@ -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();
}
}

Datei anzeigen

@ -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);
}
}
}
}
}
}

Datei anzeigen

@ -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);
}
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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;
}
}

Datei anzeigen

@ -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;
}
}
}
}

Datei anzeigen

@ -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);
}
}

Datei anzeigen

@ -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"

Datei anzeigen

@ -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%"

Datei anzeigen

@ -1 +0,0 @@
{}

Datei anzeigen

@ -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: