3
0
Mirror von https://github.com/TheSilentPro/HeadDB.git synchronisiert 2024-12-26 19:02:39 +01:00
Dieser Commit ist enthalten in:
Silent 2024-11-27 04:02:26 +01:00
Ursprung ff558c118b
Commit d7b5a90104
68 geänderte Dateien mit 3324 neuen und 2264 gelöschten Zeilen

79
pom.xml
Datei anzeigen

@ -14,8 +14,8 @@
<properties> <properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source> <maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target> <maven.compiler.target>21</maven.compiler.target>
<build.author>TheSilentPro (Silent)</build.author> <build.author>TheSilentPro (Silent)</build.author>
<maven.build.timestamp.format>dd-MM-yyyy HH:mm:ss</maven.build.timestamp.format> <maven.build.timestamp.format>dd-MM-yyyy HH:mm:ss</maven.build.timestamp.format>
<build.timestamp>${maven.build.timestamp}</build.timestamp> <build.timestamp>${maven.build.timestamp}</build.timestamp>
@ -55,42 +55,33 @@
<dependency> <dependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId> <artifactId>spigot-api</artifactId>
<version>1.20.2-R0.1-SNAPSHOT</version> <version>1.21.1-R0.1-SNAPSHOT</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.mojang</groupId> <groupId>org.slf4j</groupId>
<artifactId>authlib</artifactId> <artifactId>slf4j-api</artifactId>
<version>4.0.43</version> <version>2.0.16</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.google.code.gson</groupId> <groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId> <artifactId>gson</artifactId>
<version>2.10.1</version> <version>2.11.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.47.0.0</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<!-- Hard Dependencies (Shaded) --> <!-- Hard Dependencies (Shaded) -->
<dependency> <dependency>
<groupId>com.github.TheSilentPro</groupId> <groupId>com.github.TheSilentPro</groupId>
<artifactId>NexusLib</artifactId> <artifactId>InvLib</artifactId>
<version>45b4813899</version> <version>c184c31f6e</version>
</dependency>
<dependency>
<groupId>com.github.TheSilentPro</groupId>
<artifactId>Warehouse</artifactId>
<version>b228e4f8b1</version>
</dependency>
<dependency>
<groupId>net.wesjd</groupId>
<artifactId>anvilgui</artifactId>
<version>1.9.2-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.github.TheSilentPro</groupId>
<artifactId>HelperLite</artifactId>
<version>775582f23b</version>
</dependency> </dependency>
<!-- Soft Dependencies --> <!-- Soft Dependencies -->
@ -124,18 +115,18 @@
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version> <version>3.13.0</version>
<configuration> <configuration>
<source>${maven.compiler.source}</source> <source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target> <target>${maven.compiler.target}</target>
</configuration> </configuration>
</plugin> </plugin>
<!-- Shade --> <!-- Shade Plugin Configuration -->
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId> <artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version> <version>3.6.0</version>
<executions> <executions>
<execution> <execution>
<phase>package</phase> <phase>package</phase>
@ -159,41 +150,19 @@
<artifact>*:*</artifact> <artifact>*:*</artifact>
<excludeDefaults>false</excludeDefaults> <excludeDefaults>false</excludeDefaults>
<includes> <includes>
<include>tsp.headdb.core.util.anvilgui</include> <include>tsp/headdb/core/util/anvilgui/**</include>
</includes> </includes>
<excludes>
<exclude>org/yaml/**</exclude>
<exclude>META-INF/maven/org.yaml</exclude>
<exclude>javax/**</exclude>
</excludes>
</filter> </filter>
</filters> </filters>
</configuration> </configuration>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>
<!-- Javadoc -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.4.1</version>
<executions>
<execution>
<id>javadoc-generate</id>
<phase>package</phase>
<goals>
<goal>javadoc</goal>
</goals>
</execution>
</executions>
<configuration>
<show>public</show>
<doclint>none</doclint>
<additionalDependencies>
<additionalDependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.20.2-R0.1-SNAPSHOT</version>
</additionalDependency>
</additionalDependencies>
</configuration>
</plugin>
</plugins> </plugins>
</build> </build>

Datei anzeigen

@ -1,223 +1,163 @@
package tsp.headdb; package tsp.headdb;
import org.bukkit.Bukkit;
import org.bukkit.command.PluginCommand; import org.bukkit.command.PluginCommand;
import tsp.headdb.core.command.*; import org.bukkit.plugin.java.JavaPlugin;
import tsp.headdb.core.economy.BasicEconomyProvider; import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tsp.headdb.core.commands.CommandManager;
import tsp.headdb.core.player.PlayerDatabase;
import tsp.headdb.core.config.ConfigData;
import tsp.headdb.api.HeadAPI;
import tsp.headdb.core.economy.EconomyProvider;
import tsp.headdb.core.economy.VaultProvider; import tsp.headdb.core.economy.VaultProvider;
import tsp.headdb.api.model.Head;
import tsp.headdb.core.storage.Storage; import tsp.headdb.core.storage.Storage;
import tsp.headdb.core.task.UpdateTask; import tsp.headdb.core.util.Localization;
import tsp.headdb.core.util.HeadDBLogger; import tsp.invlib.InvLib;
import tsp.headdb.core.util.Utils;
import tsp.helperlite.HelperLite;
import tsp.helperlite.Schedulers;
import tsp.helperlite.scheduler.promise.Promise;
import tsp.helperlite.scheduler.task.Task;
import tsp.nexuslib.NexusPlugin;
import tsp.nexuslib.inventory.PaneListener;
import tsp.nexuslib.localization.TranslatableLocalization;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.util.List;
import java.net.URLConnection; import java.util.concurrent.CompletableFuture;
import java.text.DecimalFormat;
import java.util.Optional;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class HeadDB extends NexusPlugin { /**
* @author TheSilentPro (Silent)
*/
public class HeadDB extends JavaPlugin {
private static final Logger LOGGER = LoggerFactory.getLogger(HeadDB.class);
private static HeadDB instance; private static HeadDB instance;
private HeadDBLogger logger; private ConfigData config;
private TranslatableLocalization localization; private EconomyProvider economyProvider;
private boolean PAPI;
private PlayerDatabase playerDatabase;
private Localization localization;
private Storage storage; private Storage storage;
private BasicEconomyProvider economyProvider;
private CommandManager commandManager; private CommandManager commandManager;
private Task updateTask;
@Override @Override
public void onStart(NexusPlugin nexusPlugin) { public void onEnable() {
instance = this; instance = this;
HelperLite.init(this); saveDefaultConfig();
this.config = new ConfigData(getConfig());
this.playerDatabase = new PlayerDatabase();
this.storage = new Storage().init();
this.playerDatabase.load();
LOGGER.info("Loaded {} languages!", loadLocalization());
instance.saveDefaultConfig(); InvLib.init(this);
instance.logger = new HeadDBLogger(getConfig().getBoolean("debug"));
instance.logger.info("Loading HeadDB - " + Utils.getVersion().orElse(getDescription().getVersion() + " (UNKNOWN SEMVER)"));
instance.logger.info("Loaded " + loadLocalization() + " languages!"); this.PAPI = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI");
instance.initStorage(); if (this.config.isEconomyEnabled()) {
instance.initEconomy(); if (this.config.getEconomyProvider().equalsIgnoreCase("VAULT")) {
this.economyProvider = new VaultProvider();
} else {
LOGGER.error("Invalid economy provider in config.yml!");
this.setEnabled(false);
return;
}
startUpdateTask(); this.economyProvider.init();
}
new PaneListener(this); Bukkit.getScheduler().runTaskTimerAsynchronously(this, this::updateDatabase, 0L, 86400 * 20);
// TODO: Commands helperlite this.commandManager = new CommandManager().init();
instance.commandManager = new CommandManager(); PluginCommand mainCommand = getCommand("headdb");
loadCommands(); if (mainCommand == null) {
LOGGER.error("Failed to get main /headdb command!");
initMetrics(); this.setEnabled(false);
ensureLatestVersion(); return;
instance.logger.info("Done!"); }
mainCommand.setExecutor(commandManager);
mainCommand.setTabCompleter(commandManager);
} }
@Override @Override
public void onDisable() { public void onDisable() {
if (storage != null) { // Save language data
storage.getPlayerStorage().suspend(); if (playerDatabase != null) {
File langFile = new File(getDataFolder(), "langs.data"); playerDatabase.save();
if (!langFile.exists()) {
try {
//noinspection ResultOfMethodCallIgnored
langFile.createNewFile();
localization.saveLanguages(langFile);
} catch (IOException ex) {
logger.error("Failed to save receiver langauges!");
ex.printStackTrace();
}
} }
} }
updateTask.stop(); public CompletableFuture<List<Head>> updateDatabase() {
} LOGGER.info("Fetching heads...");
long fetchStart = System.currentTimeMillis();
return HeadAPI.getDatabase().getHeadsNoCache().thenApply(result -> {
LOGGER.info("Fetched {} total heads! ({}s)", HeadAPI.getTotalHeads(), TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - fetchStart));
private void startUpdateTask() { long preloadStart = System.currentTimeMillis();
updateTask = Schedulers.builder() int total = result.size();
.async() int index = 0;
.every(getConfig().getLong("refresh", 86400L), TimeUnit.SECONDS)
.run(new UpdateTask());
}
private void ensureLatestVersion() { LOGGER.info("Preloading {} heads...", total);
Promise.start().thenApplyAsync(a -> { // Milestone percentages we want to print
try { int[] milestones = {25, 50, 75, 100};
URLConnection connection = new URL("https://api.spigotmc.org/legacy/update.php?resource=" + 84967).openConnection(); int nextMilestoneIndex = 0;
connection.setConnectTimeout(5000);
connection.setRequestProperty("User-Agent", this.getName() + "-VersionChecker");
return new BufferedReader(new InputStreamReader(connection.getInputStream())).readLine().equals(Utils.getVersion().orElse(getDescription().getVersion())); for (Head head : result) {
} catch (IOException ex) { // Simulate processing each head
return false; head.getItem();
index++;
// Calculate percentage completion
int progress = (int) ((index / (double) total) * 100);
// Check if the current progress matches the next milestone
if (nextMilestoneIndex < milestones.length && progress >= milestones[nextMilestoneIndex]) {
LOGGER.info("Preloading heads... {}%", progress);
nextMilestoneIndex++; // Move to the next milestone
} }
}).thenAcceptAsync(latest -> {
if (latest) {
instance.logger.warning("There is a new update available for HeadDB on spigot!");
instance.logger.warning("Download: https://www.spigotmc.org/resources/84967");
} }
LOGGER.info("Preloaded {} total heads! ({}s)", index, TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - preloadStart));
return result;
}); });
} }
// Loaders
private void initMetrics() {
Metrics metrics = new Metrics(this, 9152);
metrics.addCustomChart(new Metrics.SimplePie("economy_provider", () -> {
if (getEconomyProvider().isPresent()) {
return this.getConfig().getString("economy.provider");
}
return "None";
}));
}
private void initStorage() {
storage = new Storage();
storage.getPlayerStorage().init();
}
private int loadLocalization() { private int loadLocalization() {
instance.localization = new TranslatableLocalization(this, "messages"); instance.localization = new Localization(this, "messages");
try { try {
instance.localization.createDefaults(); instance.localization.createDefaults();
int count = instance.localization.load(); return instance.localization.load();
File langFile = new File(getDataFolder(), "langs.data");
if (langFile.exists()) {
localization.loadLanguages(langFile);
}
return count;
} catch (URISyntaxException | IOException ex) { } catch (URISyntaxException | IOException ex) {
instance.logger.error("Failed to load localization!"); LOGGER.error("Failed to load localization!", ex);
ex.printStackTrace();
this.setEnabled(false); this.setEnabled(false);
return 0; return 0;
} }
} }
private void initEconomy() {
if (!getConfig().getBoolean("economy.enabled")) {
instance.logger.debug("Economy disabled by config.yml!");
instance.economyProvider = null;
return;
}
String raw = getConfig().getString("economy.provider", "VAULT");
if (raw.equalsIgnoreCase("VAULT")) {
economyProvider = new VaultProvider();
}
economyProvider.init();
instance.logger.info("Economy Provider: " + raw);
}
private void loadCommands() {
PluginCommand main = getCommand("headdb");
if (main != null) {
main.setExecutor(new CommandMain());
main.setTabCompleter(new CommandMain());
} else {
instance.logger.error("Could not find main 'headdb' command!");
this.setEnabled(false);
return;
}
new CommandHelp().register();
new CommandCategory().register();
new CommandSearch().register();
new CommandGive().register();
new CommandUpdate().register();
new CommandReload().register();
new CommandTexture().register();
new CommandLanguage().register();
new CommandSettings().register();
new CommandInfo().register();
}
// Getters
public Optional<Task> getUpdateTask() {
return Optional.ofNullable(updateTask);
}
public Storage getStorage() {
return storage;
}
public CommandManager getCommandManager() { public CommandManager getCommandManager() {
return commandManager; return commandManager;
} }
public Optional<BasicEconomyProvider> getEconomyProvider() { public Storage getStorage() {
return Optional.ofNullable(economyProvider); return storage;
} }
@SuppressWarnings("DataFlowIssue") public PlayerDatabase getPlayerDatabase() {
private DecimalFormat decimalFormat = new DecimalFormat(getConfig().getString("economy.format")); return playerDatabase;
public DecimalFormat getDecimalFormat() {
return decimalFormat != null ? decimalFormat : (decimalFormat = new DecimalFormat("##.##"));
} }
public TranslatableLocalization getLocalization() { public Localization getLocalization() {
return localization; return localization;
} }
public HeadDBLogger getLog() { public boolean isPAPI() {
return logger; return PAPI;
}
public EconomyProvider getEconomyProvider() {
return economyProvider;
}
@NotNull
public ConfigData getCfg() {
return config;
} }
public static HeadDB getInstance() { public static HeadDB getInstance() {

Datei anzeigen

@ -22,13 +22,27 @@ import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.zip.GZIPOutputStream; import java.util.zip.GZIPOutputStream;
@SuppressWarnings({"all", "deprecation"}) // Class is from bstats, can't modify it. @SuppressWarnings("all")
class Metrics { class Metrics {
private final Plugin plugin; private final Plugin plugin;
private final MetricsBase metricsBase; private final MetricsBase metricsBase;
private static final Metrics METRICS = new Metrics(HeadDB.getInstance(), 9152);
static {
/*
METRICS.addCustomChart(new Metrics.SimplePie("economy_provider", () -> {
if (HeadDB.getInstance().getEconomyProvider().isPresent()) {
return this.getConfig().getString("economy.provider");
}
return "None";
}));
*/
}
/** /**
* Creates a new Metrics instance. * Creates a new Metrics instance.
* *
@ -36,7 +50,7 @@ class Metrics {
* @param serviceId The id of the service. It can be found at <a * @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> * href="https://bstats.org/what-is-my-plugin-id">What is my plugin id?</a>
*/ */
public Metrics(JavaPlugin plugin, int serviceId) { private Metrics(JavaPlugin plugin, int serviceId) {
this.plugin = plugin; this.plugin = plugin;
// Get the config file // Get the config file
File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats"); File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
@ -108,7 +122,7 @@ class Metrics {
} }
private void appendServiceData(JsonObjectBuilder builder) { private void appendServiceData(JsonObjectBuilder builder) {
builder.appendField("pluginVersion", Utils.getVersion().orElse("Unknown")); builder.appendField("pluginVersion", Utils.getUserAgent());
} }
private int getPlayerAmount() { private int getPlayerAmount() {

Datei anzeigen

@ -0,0 +1,333 @@
package tsp.headdb.api;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import tsp.headdb.HeadDB;
import tsp.headdb.api.model.Head;
import tsp.headdb.api.model.LocalHead;
import tsp.headdb.api.provider.HeadDataProvider;
import tsp.headdb.core.player.PlayerData;
import tsp.headdb.core.util.Utils;
import org.jetbrains.annotations.NotNull;
import java.time.Instant;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Head API for interacting with the main {@link HeadDatabase}.
*
* @author TheSilentPro (Silent)
* @see HeadDatabase
*/
public final class HeadAPI {
/**
* Utility class. No initialization nor extension.
*/
private HeadAPI() {}
static final ExecutorService executor = Executors.newFixedThreadPool(2, r -> new Thread(r, "HeadDB Fetcher"));
/**
* The main {@link HeadDatabase}.
*/
private static final HeadDatabase database = new HeadDatabase(HeadDB.getInstance(), executor, new HeadDataProvider());
/**
* Retrieve a {@link List} of {@link Head} matching the name.
*
* @param name The name to match against
* @param lenient Whether the filter should be lenient when matching
* @return {@link List<Head> Heads}
*/
@NotNull
public static CompletableFuture<List<Head>> getHeadsByName(String name, boolean lenient) {
return CompletableFuture.supplyAsync(() -> getHeadStream().filter(head -> (lenient ? Utils.matches(head.getName(), name) : head.getName().equalsIgnoreCase(name))).collect(Collectors.toList()), executor);
}
/**
* Retrieve a {@link List} of {@link Head} matching the name.
*
* @param name The name to match against
* @return {@link List<Head> Heads}
*/
@NotNull
public static CompletableFuture<List<Head>> getHeadsByName(String name) {
return getHeadsByName(name, true);
}
/**
* Retrieve a {@link Head} by its exact name.
*
* @param name The name to look for
* @param lenient Whether the filter to be lenient when matching
* @return The {@link Head}, else empty
*/
public static CompletableFuture<Optional<Head>> getHeadByExactName(String name, boolean lenient) {
return CompletableFuture.supplyAsync(getHeadStream().filter(head -> (lenient ? Utils.matches(head.getName(), name) : head.getName().equalsIgnoreCase(name)))::findAny, executor);
}
/**
* Retrieve a {@link Head} by its exact name.
*
* @param name The name to look for
* @return The {@link Head}, else empty
*/
@NotNull
public static CompletableFuture<Optional<Head>> getHeadByExactName(String name) {
return getHeadByExactName(name, false);
}
/**
* Retrieve a {@link Head} by its id.
*
* @param id The id to look for
* @return The {@link Head}, else empty
*/
@NotNull
public static CompletableFuture<Optional<Head>> getHeadById(int id) {
return CompletableFuture.supplyAsync(getHeadStream().filter(head -> head.getId() == id)::findAny, executor);
}
/**
* Retrieve a {@link Head} by its texture value.
*
* @param texture The texture to look for
* @return The {@link Head}, else empty
*/
@NotNull
public static CompletableFuture<Optional<Head>> getHeadByTexture(String texture) {
return CompletableFuture.supplyAsync(getHeadStream().filter(head -> head.getTexture().orElse("N/A").equals(texture))::findAny, executor);
}
/**
* Retrieve a {@link List} of {@link Head Heads} matching the category.
*
* @param category The category
* @return The list of matching heads.
*/
@NotNull
public static CompletableFuture<List<Head>> getHeadsByCategory(String category) {
return CompletableFuture.supplyAsync(() -> getHeadStream().filter(head -> head.getCategory().orElse("?").equalsIgnoreCase(category)).toList(), executor);
}
/**
* Retrieve a {@link List} of {@link Head Heads} matching the date.
*
* @param date The date
* @return The list of matching heads.
*/
@NotNull
public static CompletableFuture<List<Head>> getHeadsByDate(String date) {
return CompletableFuture.supplyAsync(() -> getHeadStream().filter(head -> head.getPublishDate().orElse("?").equalsIgnoreCase(date)).toList(), executor);
}
/**
* Retrieve a {@link List} of {@link Head Heads} matching the date.
*
* @param dates The dates
* @return The list of matching heads.
*/
@NotNull
public static CompletableFuture<List<Head>> getHeadsByDates(String... dates) {
return CompletableFuture.supplyAsync(() -> getHeadStream().filter(head -> {
if (head.getPublishDate().isEmpty()) {
return false;
}
for (String date : dates) {
if (head.getPublishDate().get().equalsIgnoreCase(date)) {
return true;
}
}
return false;
}).toList(), executor);
}
/**
* Retrieve a {@link List} of {@link Head Heads} matching the tags.
*
* @param tags The tags
* @return The list of matching heads.
*/
@NotNull
public static CompletableFuture<List<Head>> getHeadsByTags(String... tags) {
return CompletableFuture.supplyAsync(() -> getHeadStream().filter(head -> {
String[] array = head.getTags().orElse(null);
if (array == null) {
return false;
}
for (String entry : array) {
for (String tag : tags) {
if (entry.equalsIgnoreCase(tag)) {
return true;
}
}
}
return false;
}).toList(), executor);
}
/**
* Retrieve a {@link List} of {@link Head Heads} matching the contributors.
*
* @param contributors The contributors
* @return The list of matching heads.
*/
@NotNull
public static CompletableFuture<List<Head>> getHeadsByContributors(String... contributors) {
return CompletableFuture.supplyAsync(() -> getHeadStream().filter(head -> {
String[] array = head.getContributors().orElse(null);
if (array == null) {
return false;
}
for (String entry : array) {
for (String contributor : contributors) {
if (entry.equalsIgnoreCase(contributor)) {
return true;
}
}
}
return false;
}).toList(), executor);
}
/**
* Retrieve a {@link List} of {@link Head Heads} matching the collections.
*
* @param collections The collections
* @return The list of matching heads.
*/
@NotNull
public static CompletableFuture<List<Head>> getHeadsByCollections(String... collections) {
return CompletableFuture.supplyAsync(() -> getHeadStream().filter(head -> {
String[] array = head.getTags().orElse(null);
if (array == null) {
return false;
}
for (String entry : array) {
for (String collection : collections) {
if (entry.equalsIgnoreCase(collection)) {
return true;
}
}
}
return false;
}).toList(), executor);
}
/**
* Retrieve a {@link Stream} of {@link Head} within the main {@link HeadDatabase}.
*
* @return The streamed heads
*/
@NotNull
public static Stream<Head> getHeadStream() {
return getHeads().stream();
}
/**
* Retrieve a {@link List} of {@link Head} within the main {@link HeadDatabase}.
*
* @return {@link List<Head> Heads}
*/
@NotNull
public static List<Head> getHeads() {
return database.getHeads();
}
/**
* Retrieve the total amount of {@link Head heads} present in the main {@link HeadDatabase}.
*
* @return Amount of heads
*/
public static int getTotalHeads() {
return getHeads().size();
}
/**
* Retrieve a {@link Set} of local heads.
* Note that this calculates the heads on every call.
*
* @return {@link Set<LocalHead> Local Heads}
*/
@NotNull
public static CompletableFuture<Set<LocalHead>> getLocalHeads(boolean calculateDate) {
OfflinePlayer[] players = Bukkit.getOfflinePlayers();
return CompletableFuture.supplyAsync(() -> Arrays.stream(players)
.filter(player -> player.getName() != null)
.map(player -> {
PlayerData data = HeadDB.getInstance().getPlayerDatabase().getOrCreate(player.getUniqueId());
String date = null;
if (calculateDate) {
date = Instant.ofEpochMilli(player.getFirstPlayed())
.atZone(ZoneId.systemDefault())
.toLocalDate()
.format(Utils.DATE_FORMATTER);
}
return new LocalHead(data.getId(), data.getUuid(), player.getName(), date);
})
.collect(Collectors.toSet()), executor);
}
public static CompletableFuture<Set<LocalHead>> getLocalHeads() {
return getLocalHeads(false);
}
/**
* Retrieve a {@link Set} of favorite heads for the specified {@link UUID player id}.
* Note that this calculates the heads on every call.
*
* @return {@link Set<Head> Favorite Heads}
*/
@NotNull
public static CompletableFuture<Set<Head>> getFavoriteHeads(UUID player) {
return CompletableFuture.supplyAsync(() -> HeadDB.getInstance()
.getPlayerDatabase()
.getOrCreate(player)
.getFavorites()
.stream()
.map(id -> {
Optional<Head> head = HeadAPI.getHeadById(id).join();
if (head.isPresent()) {
return head;
} else {
return getLocalHeads()
.join()
.stream()
.filter(localHead -> localHead.getId() == id)
.findAny();
}
})
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toSet()), executor);
}
/**
* Returns true when the database is ready with all heads cached.
*
* @return If the database has cached all heads.
*/
public boolean isReady() {
return database.isReady();
}
/**
* Retrieve the main {@link HeadDatabase} used by the plugin.
*
* @return {@link HeadDatabase Database}
*/
@NotNull
public static HeadDatabase getDatabase() {
return database;
}
}

Datei anzeigen

@ -0,0 +1,62 @@
package tsp.headdb.api;
import org.bukkit.plugin.java.JavaPlugin;
import tsp.headdb.api.model.Category;
import tsp.headdb.api.model.Head;
import tsp.headdb.api.provider.HeadProvider;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutorService;
/**
* @author TheSilentPro (Silent)
*/
public class HeadDatabase {
private final JavaPlugin plugin;
private final ExecutorService executor;
private final CopyOnWriteArrayList<Head> heads;
private final HeadProvider provider;
private long timestamp;
private volatile boolean ready;
public HeadDatabase(JavaPlugin plugin, ExecutorService executor, HeadProvider provider) {
this.plugin = plugin;
this.executor = executor;
this.provider = provider;
this.heads = new CopyOnWriteArrayList<>();
this.ready = false;
}
public List<Head> getHeads() {
return Collections.unmodifiableList(heads);
}
public CompletableFuture<List<Head>> getHeadsNoCache() {
this.ready = false;
this.heads.clear();
return CompletableFuture.supplyAsync(() -> {
heads.addAll(provider.fetchHeads(executor).join().heads());
this.ready = true;
timestamp = System.currentTimeMillis();
return Collections.unmodifiableList(heads);
}, executor);
}
public boolean isReady() {
return ready;
}
public long getTimestamp() {
return timestamp;
}
public JavaPlugin getPlugin() {
return plugin;
}
}

Datei anzeigen

@ -0,0 +1,101 @@
/**
* @author TheSilentPro (Silent)
*/
package tsp.headdb.api.model;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import tsp.headdb.api.HeadAPI;
import tsp.headdb.core.util.Utils;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Optional;
public enum Category {
ALPHABET("alphabet", "Alphabet", 20),
ANIMALS("animals", "Animals", 21),
BLOCKS("blocks", "Blocks", 22),
DECORATION("decoration", "Decoration", 23),
FOOD_DRINKS("food-drinks", "Food & Drinks", 24),
HUMANS("humans", "Humans", 29),
HUMANOID("humanoid", "Humanoid", 30),
MISCELLANEOUS("miscellaneous", "Miscellaneous", 31),
MONSTERS("monsters", "Monsters", 32),
PLANTS("plants", "Plants", 33);
private final String name;
private final String displayName;
private final int defaultSlot;
private ItemStack item;
public static final Category[] VALUES = values();
Category(String name, String displayName, int defaultSlot) {
this.name = name;
this.displayName = displayName;
this.defaultSlot = defaultSlot;
}
public String getName() {
return name;
}
public String getDisplayName() {
return displayName;
}
public int getDefaultSlot() {
return defaultSlot;
}
public static Optional<Category> getByName(String cname) {
for (Category value : VALUES) {
if (value.name.equalsIgnoreCase(cname) || value.name().equalsIgnoreCase(cname)) {
return Optional.of(value);
}
}
return Optional.empty();
}
@NotNull
public ItemStack getDisplayItem() {
if (item == null) {
List<Head> headsList = HeadAPI.getHeads()
.stream()
.filter(head -> head.getCategory().orElse("N/A").equalsIgnoreCase(getName()))
.toList();
headsList.stream()
.findFirst()
.ifPresentOrElse(head -> {
ItemStack retrieved = new ItemStack(head.getItem());
ItemMeta meta = retrieved.getItemMeta();
if (meta != null) {
meta.setDisplayName(ChatColor.BOLD + "" + ChatColor.GOLD + getName().toUpperCase());
meta.setLore(List.of(ChatColor.GRAY + "Total Heads » " + ChatColor.GOLD + headsList.size()));
retrieved.setItemMeta(meta);
} else {
retrieved = new ItemStack(Material.PLAYER_HEAD);
}
item = retrieved;
},
() -> {
ItemStack copy = new ItemStack(Material.PLAYER_HEAD);
ItemMeta meta = copy.getItemMeta();
//noinspection DataFlowIssue
meta.setDisplayName(Utils.colorize(getName().toUpperCase()));
copy.setItemMeta(meta);
item = copy;
});
}
return item.clone();
}
}

Datei anzeigen

@ -0,0 +1,95 @@
package tsp.headdb.api.model;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import tsp.headdb.core.util.Utils;
import java.util.Objects;
import java.util.Optional;
/**
* @author TheSilentPro (Silent)
*/
public class Head {
private final int id;
private final String name;
private final String texture;
private final String category;
private final String publishDate;
private final String[] tags;
private final String[] contributors;
private final String[] collections;
protected ItemStack item;
public Head(
int id,
@NotNull String name,
@Nullable String texture,
@Nullable String category,
@Nullable String publishDate,
@Nullable String[] tags,
@Nullable String[] contributors,
@Nullable String[] collections
) {
Objects.requireNonNull(name, "Name must not be null!");
this.id = id;
this.name = name;
this.texture = texture;
this.category = category;
this.publishDate = publishDate;
this.tags = tags;
this.contributors = contributors;
this.collections = collections;
}
@NotNull
public ItemStack getItem() {
if (item == null) {
if (texture != null) {
item = new ItemStack(Utils.asItem(this));
} else {
item = new ItemStack(Material.PLAYER_HEAD);
}
}
return item;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public Optional<String> getTexture() {
return Optional.ofNullable(texture);
}
public Optional<String> getCategory() {
return Optional.ofNullable(category);
}
public Optional<String> getPublishDate() {
return Optional.ofNullable(publishDate);
}
public Optional<String[]> getTags() {
return Optional.ofNullable(tags);
}
public Optional<String[]> getContributors() {
return Optional.ofNullable(contributors);
}
public Optional<String[]> getCollections() {
return Optional.ofNullable(collections);
}
}

Datei anzeigen

@ -0,0 +1,58 @@
package tsp.headdb.api.model;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
/**
* Represents a head from a player that has joined the server.
*
* @author TheSilentPro (Silent)
*/
public class LocalHead extends Head {
private final UUID uuid;
public LocalHead(int id, @NotNull UUID uniqueId, @NotNull String name, @org.jetbrains.annotations.Nullable String date) {
super(-id, name, null, "Local Head", date, new String[]{"Local Heads"}, null, null);
this.uuid = uniqueId;
}
public UUID getUniqueId() {
return uuid;
}
@Nullable
@Override
public Optional<String> getCategory() {
return Optional.of("Local");
}
@NotNull
@Override
public ItemStack getItem() {
if (this.item == null) {
ItemStack item = new ItemStack(Material.PLAYER_HEAD);
SkullMeta meta = (SkullMeta) item.getItemMeta();
if (meta != null) {
meta.setOwningPlayer(Bukkit.getOfflinePlayer(getUniqueId()));
meta.setDisplayName(ChatColor.GOLD + getName());
meta.setLore(List.of(ChatColor.GRAY + "UUID » " + ChatColor.GOLD + getUniqueId().toString(), getPublishDate().isPresent() ? (ChatColor.GRAY + "First Joined » " + ChatColor.GOLD + getPublishDate().get()) : ""));
item.setItemMeta(meta);
}
this.item = new ItemStack(item);
}
return item;
}
}

Datei anzeigen

@ -0,0 +1,118 @@
package tsp.headdb.api.provider;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tsp.headdb.api.HeadAPI;
import tsp.headdb.api.model.Head;
import tsp.headdb.core.util.Utils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
/**
* @author TheSilentPro (Silent)
*/
public class HeadDataProvider implements HeadProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(HeadDataProvider.class);
@Override
public String getUrl() {
return "https://raw.githubusercontent.com/TheSilentPro/HeadData/refs/heads/main/heads.json";
}
@Override
public CompletableFuture<ProviderResponse> fetchHeads(ExecutorService executor) {
return CompletableFuture.supplyAsync(() -> {
try {
List<Head> heads = new ArrayList<>();
HttpURLConnection connection = (HttpURLConnection) URI.create(getUrl()).toURL().openConnection();
connection.setConnectTimeout(5000);
connection.setRequestProperty("User-Agent", Utils.getUserAgent());
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);
}
connection.disconnect();
JsonArray main = JsonParser.parseString(builder.toString()).getAsJsonArray();
for (JsonElement entry : main) {
JsonObject obj = entry.getAsJsonObject();
// Index for json arrays
int i = 0;
String[] tags = null;
if (!obj.get("tags").isJsonNull()) {
JsonArray rawTags = obj.get("tags").getAsJsonArray();
tags = new String[rawTags.size()];
for (JsonElement rawTag : rawTags) {
tags[i] = rawTag.getAsString();
i++;
}
}
String[] contributors = null;
if (!obj.get("contributors").isJsonNull()) {
i = 0;
JsonArray rawContributors = obj.get("contributors").getAsJsonArray();
contributors = new String[rawContributors.size()];
for (JsonElement rawContributor : rawContributors) {
contributors[i] = rawContributor.getAsString();
i++;
}
}
String[] collections = null;
if (!obj.get("collections").isJsonNull()) {
i = 0;
JsonArray rawCollections = obj.get("collections").getAsJsonArray();
collections = new String[rawCollections.size()];
for (JsonElement rawCollection : rawCollections) {
collections[i] = rawCollection.getAsString();
i++;
}
}
String date = obj.has("publish_date") && !obj.get("publish_date").isJsonNull() ? obj.get("publish_date").getAsString() : null;
heads.add(new Head(
obj.get("id").getAsInt(),
obj.get("name").getAsString(),
obj.has("texture") && !obj.get("texture").isJsonNull() ? obj.get("texture").getAsString() : null,
obj.has("category") && !obj.get("category").isJsonNull() ? obj.get("category").getAsString() : null,
date != null ? (date.substring(8, 10) + "-" + date.substring(5, 7) + "-" + date.substring(0, 4)) : null, // Parses and reverses the date format (yyyy-MM-dd -> dd-MM-yyyy)
tags,
contributors,
collections
));
}
}
return new ProviderResponse(heads, Date.from(Instant.now()));
} catch (Exception ex) {
LOGGER.error("Failed to fetch heads!", ex);
return new ProviderResponse(new ArrayList<>(), Date.from(Instant.now()));
}
}, executor);
}
}

Datei anzeigen

@ -0,0 +1,117 @@
package tsp.headdb.api.provider;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tsp.headdb.api.model.Head;
import tsp.headdb.core.util.Utils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
/**
* {@link HeadProvider} that will read from a json file.
* The json format should be that of {@link HeadDataProvider}.
*
* @author TheSilentPro (Silent)
*/
public class HeadFileProvider implements HeadProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(HeadFileProvider.class);
private final File file;
public HeadFileProvider(File file) {
this.file = file;
}
public File getFile() {
return file;
}
/**
* Reading is done from the file, not an url.
*
* @return Always null.
*/
@Override
public String getUrl() {
return null;
}
@Override
public CompletableFuture<ProviderResponse> fetchHeads(ExecutorService executor) {
return CompletableFuture.supplyAsync(() -> {
try {
List<Head> heads = new ArrayList<>();
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
StringBuilder builder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
}
JsonArray main = JsonParser.parseString(builder.toString()).getAsJsonArray();
for (JsonElement entry : main) {
JsonObject obj = entry.getAsJsonObject();
JsonArray rawTags = obj.get("tags").getAsJsonArray();
String[] tags = new String[rawTags.size()];
int i = 0;
for (JsonElement rawTag : rawTags) {
tags[i] = rawTag.getAsString();
i++;
}
i = 0;
JsonArray rawContributors = obj.get("contributors").getAsJsonArray();
String[] contributors = new String[rawContributors.size()];
for (JsonElement rawContributor : rawContributors) {
contributors[i] = rawContributor.getAsString();
i++;
}
i = 0;
JsonArray rawCollections = obj.get("collections").getAsJsonArray();
String[] collections = new String[rawCollections.size()];
for (JsonElement rawCollection : rawCollections) {
collections[i] = rawCollection.getAsString();
i++;
}
heads.add(new Head(
obj.get("id").getAsInt(),
obj.get("name").getAsString(),
obj.get("texture").getAsString(),
obj.get("category").getAsString(),
obj.get("publish_date").getAsString(),
tags,
contributors,
collections
));
}
}
return new ProviderResponse(heads, Date.from(Instant.now()));
} catch (Exception ex) {
LOGGER.error("Failed to fetch heads!", ex);
return new ProviderResponse(new ArrayList<>(), Date.from(Instant.now()));
}
}, executor);
}
}

Datei anzeigen

@ -0,0 +1,15 @@
package tsp.headdb.api.provider;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
/**
* @author TheSilentPro (Silent)
*/
public interface HeadProvider {
String getUrl();
CompletableFuture<ProviderResponse> fetchHeads(ExecutorService executor);
}

Datei anzeigen

@ -0,0 +1,92 @@
package tsp.headdb.api.provider;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tsp.headdb.api.model.Category;
import tsp.headdb.api.model.Head;
import tsp.headdb.core.util.Utils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
/**
* @author TheSilentPro (Silent)
*
* @deprecated Replaced by {@link HeadDataProvider}.
*/
@Deprecated(forRemoval = true, since = "5.0.0")
public class HeadStorageProvider implements HeadProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(HeadStorageProvider.class);
@Override
public String getUrl() {
return "https://raw.githubusercontent.com/TheSilentPro/HeadStorage/master/storage/%s.json";
}
public CompletableFuture<ProviderResponse> fetchHeads(Category category, ExecutorService executor) {
return CompletableFuture.supplyAsync(() -> {
try {
List<Head> heads = new ArrayList<>();
HttpURLConnection connection = (HttpURLConnection) URI.create(String.format(getUrl(), category.getName())).toURL().openConnection();
connection.setConnectTimeout(5000);
connection.setRequestProperty("User-Agent", Utils.getUserAgent());
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);
}
connection.disconnect();
JsonArray main = JsonParser.parseString(builder.toString()).getAsJsonArray();
for (JsonElement entry : main) {
JsonObject obj = entry.getAsJsonObject();
heads.add(new Head(
obj.get("id").getAsInt(),
obj.get("name").getAsString(),
obj.get("value").getAsString(),
category.getName(),
null,
obj.get("tags").getAsString().split(","),
null,
null
));
}
}
return new ProviderResponse(heads, Date.from(Instant.now()));
} catch (IOException ex) {
LOGGER.error("Failed to fetch heads for category: {}", category, ex);
return new ProviderResponse(new ArrayList<>(), Date.from(Instant.now()));
}
}, executor);
}
@Override
public CompletableFuture<ProviderResponse> fetchHeads(ExecutorService executor) {
return CompletableFuture.supplyAsync(() -> {
List<Head> heads = new ArrayList<>();
for (Category category : Category.VALUES) {
heads.addAll(fetchHeads(category, executor).join().heads());
}
return new ProviderResponse(heads, Date.from(Instant.now()));
});
}
}

Datei anzeigen

@ -0,0 +1,11 @@
package tsp.headdb.api.provider;
import tsp.headdb.api.model.Head;
import java.util.Date;
import java.util.List;
/**
* @author TheSilentPro (Silent)
*/
public record ProviderResponse(List<Head> heads, Date date) {}

Datei anzeigen

@ -1,189 +0,0 @@
package tsp.headdb.core.api;
import org.bukkit.Bukkit;
import tsp.headdb.HeadDB;
import tsp.headdb.core.storage.PlayerData;
import tsp.headdb.core.util.Utils;
import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.head.Head;
import tsp.headdb.implementation.head.HeadDatabase;
import tsp.headdb.implementation.head.LocalHead;
import tsp.headdb.implementation.requester.HeadProvider;
import tsp.helperlite.scheduler.promise.Promise;
import javax.annotation.Nonnull;
import java.util.*;
import java.util.stream.Collectors;
/**
* Head API for interacting with the main {@link HeadDatabase}.
*
* @author TheSilentPro (Silent)
* @see HeadDatabase
*/
public final class HeadAPI {
/**
* Utility class. No initialization nor extension.
*/
private HeadAPI() {}
/**
* The main {@link HeadDatabase}.
*/
private static final HeadDatabase database = new HeadDatabase(HeadDB.getInstance(), HeadProvider.HEAD_STORAGE);
/**
* Retrieve a {@link List} of {@link Head} matching the name.
*
* @param name The name to match against
* @param lenient Whether the filter should be lenient when matching
* @return {@link List<Head> Heads}
*/
@Nonnull
public static List<Head> getHeadsByName(String name, boolean lenient) {
return getHeads().stream().filter(head -> (lenient ? Utils.matches(head.getName(), name) : head.getName().equalsIgnoreCase(name))).collect(Collectors.toList());
}
/**
* Retrieve a {@link List} of {@link Head} matching the name.
*
* @param name The name to match against
* @return {@link List<Head> Heads}
*/
@Nonnull
public static List<Head> getHeadsByName(String name) {
return getHeadsByName(name, true);
}
/**
* Retrieve a {@link Head} by its exact name.
*
* @param name The name to look for
* @param lenient Whether the filter to be lenient when matching
* @return The {@link Head}, else empty
*/
public static Optional<Head> getHeadByExactName(String name, boolean lenient) {
return getHeads().stream().filter(head -> (lenient ? Utils.matches(head.getName(), name) : head.getName().equalsIgnoreCase(name))).findAny();
}
/**
* Retrieve a {@link Head} by its exact name.
*
* @param name The name to look for
* @return The {@link Head}, else empty
*/
@Nonnull
public static Optional<Head> getHeadByExactName(String name) {
return getHeadByExactName(name, false);
}
/**
* Retrieve a {@link Head} by its id.
*
* @param id The id to look for
* @return The {@link Head}, else empty
*/
@Nonnull
public static Optional<Head> getHeadById(int id) {
return getHeads().stream().filter(head -> head.getId() == id).findAny();
}
/**
* Retrieve a {@link Head} by its texture value.
*
* @param texture The texture to look for
* @return The {@link Head}, else empty
*/
@Nonnull
public static Optional<Head> getHeadByTexture(String texture) {
return getHeads().stream().filter(head -> head.getTexture().equals(texture)).findAny();
}
/**
* Retrieve a {@link List} of {@link Head} within the main {@link HeadDatabase}.
*
* @return {@link List<Head> Heads}
*/
@Nonnull
public static List<Head> getHeads() {
List<Head> result = new ArrayList<>();
for (Category category : getHeadsMap().keySet()) {
result.addAll(getHeads(category));
}
return result;
}
/**
* Retrieve a {@link List} of {@link Head} within a {@link Category}.
*
* @param category The category to retrieve the heads from
* @return {@link List<Head> Heads}
*/
@Nonnull
public static List<Head> getHeads(Category category) {
return getHeadsMap().get(category);
}
/**
* Retrieve an unmodifiable view of the database head map.
*
* @return The map
*/
@Nonnull
public static Map<Category, List<Head>> getHeadsMap() {
return Collections.unmodifiableMap(database.getHeads());
}
/**
* Retrieve the total amount of {@link Head heads} present in the main {@link HeadDatabase}.
*
* @return Amount of heads
*/
public static int getTotalHeads() {
return getHeads().size();
}
/**
* Retrieve a {@link Set} of local heads.
* Note that this calculates the heads on every call.
*
* @return {@link Set<LocalHead> Local Heads}
*/
@Nonnull
public static Set<LocalHead> getLocalHeads() {
return Arrays.stream(Bukkit.getOfflinePlayers()).map(player -> new LocalHead(player.getUniqueId(), player.getName())).collect(Collectors.toSet());
}
/**
* Retrieve a {@link Set} of favorite heads for the specified {@link UUID player id}.
* Note that this calculates the heads on every call.
*
* @param player The players id
* @return {@link Set<Head> Favorite Heads}
*/
@Nonnull
public static Promise<List<Head>> getFavoriteHeads(UUID player) {
return Promise.supplyingAsync(() -> {
List<Head> result = new ArrayList<>();
Optional<PlayerData> data = HeadDB.getInstance().getStorage().getPlayerStorage().get(player);
data.ifPresent(playerData -> playerData.favorites()
.forEach(texture -> getHeadByTexture(texture)
.ifPresent(result::add))
);
return result;
});
}
/**
* Retrieve the main {@link HeadDatabase} used by the plugin.
*
* @return {@link HeadDatabase Database}
*/
@Nonnull
public static HeadDatabase getDatabase() {
return database;
}
}

Datei anzeigen

@ -1,47 +0,0 @@
package tsp.headdb.core.api.events;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.head.Head;
import java.util.List;
import java.util.Map;
public class AsyncHeadsFetchedEvent extends Event {
private static final HandlerList HANDLER_LIST = new HandlerList();
private final Map<Category, List<Head>> heads;
private final String providerName;
private final long timeTook;
public AsyncHeadsFetchedEvent(Map<Category, List<Head>> heads, String providerName, long timeTook) {
super(true);
this.heads = heads;
this.providerName = providerName;
this.timeTook = timeTook;
}
@NotNull
public static HandlerList getHandlerList() {
return HANDLER_LIST;
}
@NotNull
@Override
public HandlerList getHandlers() {
return HANDLER_LIST;
}
public Map<Category, List<Head>> getHeads() {
return heads;
}
public String getProviderName() {
return providerName;
}
public long getTimeTook() {
return timeTook;
}
}

Datei anzeigen

@ -1,61 +0,0 @@
package tsp.headdb.core.command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import tsp.headdb.HeadDB;
import tsp.headdb.core.api.HeadAPI;
import tsp.headdb.core.util.Utils;
import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.head.Head;
import tsp.nexuslib.inventory.PagedPane;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class CommandCategory extends SubCommand {
public CommandCategory() {
super("open", Arrays.stream(Category.VALUES).map(Category::getName).collect(Collectors.toList()), "o");
}
@Override
public void handle(CommandSender sender, String[] args) {
if (!(sender instanceof Player player)) {
getLocalization().sendConsoleMessage("noConsole");
return;
}
if (args.length < 2) {
getLocalization().sendMessage(player.getUniqueId(), "invalidArguments");
return;
}
Category.getByName(args[1]).ifPresentOrElse(category -> {
boolean requirePermission = HeadDB.getInstance().getConfig().getBoolean("requireCategoryPermission");
if (requirePermission
&& !player.hasPermission("headdb.category." + category.getName())
&& !player.hasPermission("headdb.category.*")) {
getLocalization().sendMessage(player.getUniqueId(), "noPermission");
return;
}
int page = 0;
if (args.length >= 3) {
page = Utils.resolveInt(args[2]) - 1;
}
List<Head> heads = HeadAPI.getHeads(category);
PagedPane main = Utils.createPaged(player, Utils.translateTitle(getLocalization().getMessage(player.getUniqueId(), "menu.category.name").orElse(category.getName()), heads.size(), category.getName()));
Utils.addHeads(player, category, main, heads);
if (page < 0 || page > main.getPageAmount()) {
getLocalization().sendMessage(player.getUniqueId(), "invalidPageIndex", msg -> msg.replace("%pages%", String.valueOf(main.getPageAmount())));
return;
}
main.selectPage(page);
main.open(player);
}, () -> getLocalization().sendMessage(player.getUniqueId(), "invalidCategory"));
}
}

Datei anzeigen

@ -1,57 +0,0 @@
package tsp.headdb.core.command;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import tsp.headdb.core.api.HeadAPI;
import tsp.headdb.core.util.Utils;
import tsp.headdb.implementation.head.Head;
import java.util.Optional;
import java.util.stream.Collectors;
public class CommandGive extends SubCommand {
public CommandGive() {
super("give", HeadAPI.getHeads().stream().map(Head::getName).collect(Collectors.toList()), "g");
}
@Override
public void handle(CommandSender sender, String[] args) {
// /hdb give <id> <player> [amount]
if (args.length < 3) {
getLocalization().sendMessage(sender, "invalidArguments");
return;
}
int amount = args.length >= 4 ? Utils.resolveInt(args[3]) : 1;
Optional<Head> head;
String id = args[1];
if (id.startsWith("t:")) {
head = HeadAPI.getHeadByTexture(id.substring(2));
} else {
try {
head = HeadAPI.getHeadById(Integer.parseInt(id));
} catch (NumberFormatException nfe) {
// Attempt to find it by exact name (useful for tab completions)
head = HeadAPI.getHeadByExactName(id);
}
}
Player player = Bukkit.getPlayer(args[2]);
if (player == null) {
getLocalization().sendMessage(sender, "invalidTarget", msg -> msg.replace("%name%", args[2]));
return;
}
head.ifPresentOrElse(
value -> {
player.getInventory().addItem(value.getItem(player.getUniqueId()));
getLocalization().sendMessage(sender, "giveCommand", msg -> msg.replace("%size%", String.valueOf(amount)).replace("%name%", value.getName()).replace("%receiver%", player.getName()));
},
() -> getLocalization().sendMessage(sender, "giveCommandInvalid", msg -> msg.replace("%name%", id))
);
}
}

Datei anzeigen

@ -1,30 +0,0 @@
package tsp.headdb.core.command;
import org.bukkit.command.CommandSender;
import tsp.nexuslib.player.PlayerUtils;
public class CommandHelp extends SubCommand {
public CommandHelp() {
super("help", "h");
}
@Override
public void handle(CommandSender sender, String[] args) {
PlayerUtils.sendMessage(sender, "&7<==================== [ &cHeadDB &7| &5Commands ] &7====================>");
PlayerUtils.sendMessage(sender, "&7Format: /hdb &9<sub-command>(aliases) &c<parameters> &7- Description");
PlayerUtils.sendMessage(sender, "&7Required: &c<> &7| Optional: &b[]");
PlayerUtils.sendMessage(sender, " ");
PlayerUtils.sendMessage(sender, "&7/hdb &9info(i) &7- Show plugin information.");
PlayerUtils.sendMessage(sender, "&7/hdb &9open(o) &c<category> &b[page] &7- Open a specific category.");
PlayerUtils.sendMessage(sender, "&7/hdb &9search(s) &b(id:|tg:)&c<query> &7- Search for specific heads.");
PlayerUtils.sendMessage(sender, "&7/hdb &9give(g) &b(t:)&c<id> <player> &b[amount] &7- Give the player a specific head.");
PlayerUtils.sendMessage(sender, "&7/hdb &9update(u) &7- Manually update the database.");
PlayerUtils.sendMessage(sender, "&7/hdb &9reload(r) &7- Reload configuration files.");
PlayerUtils.sendMessage(sender, "&7/hdb &9language(l) &7- Change your language.");
PlayerUtils.sendMessage(sender, "&7/hdb &9settings(st) &7- Open the settings menu.");
PlayerUtils.sendMessage(sender, "&7/hdb &9texture(t) &7- Get the texture for the head your item.");
PlayerUtils.sendMessage(sender, "&7<===============================================================>");
}
}

Datei anzeigen

@ -1,26 +0,0 @@
package tsp.headdb.core.command;
import org.bukkit.command.CommandSender;
import tsp.headdb.HeadDB;
import tsp.headdb.core.util.Utils;
import tsp.nexuslib.player.PlayerUtils;
public class CommandInfo extends SubCommand {
public CommandInfo() {
super("info", "i");
}
@Override
public void handle(CommandSender sender, String[] args) {
if (HeadDB.getInstance().getConfig().getBoolean("showAdvancedPluginInfo")) {
PlayerUtils.sendMessage(sender, "&7Running &6HeadDB - " + Utils.getVersion().orElse(HeadDB.getInstance().getDescription().getVersion() + " &7(&4UNKNOWN SEMVER&7)"));
//PlayerUtils.sendMessage(sender, "&7Build: " + HeadDB.getInstance().getDescription().getVersion());
PlayerUtils.sendMessage(sender, "&7GitHub: &6https://github.com/TheSilentPro/HeadDB");
} else {
PlayerUtils.sendMessage(sender, "&7Running &6HeadDB &7by &6TheSilentPro (Silent)");
PlayerUtils.sendMessage(sender, "&7GitHub: &6https://github.com/TheSilentPro/HeadDB");
}
}
}

Datei anzeigen

@ -1,36 +0,0 @@
package tsp.headdb.core.command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import tsp.headdb.HeadDB;
import tsp.headdb.core.util.Utils;
public class CommandLanguage extends SubCommand {
public CommandLanguage() {
super("language", HeadDB.getInstance().getLocalization().getData().keySet(), "l", "lang");
}
@Override
public void handle(CommandSender sender, String[] args) {
if (args.length < 2) {
getLocalization().sendMessage(sender, "invalidArguments");
return;
}
String lang = args[1];
if (!getLocalization().getData().containsKey(lang)) {
getLocalization().sendMessage(sender, "invalidLanguage", msg -> msg.replace("%languages%", Utils.toString(getLocalization().getData().keySet())));
return;
}
if (!(sender instanceof Player player)) {
getLocalization().setConsoleLanguage(lang);
} else {
getLocalization().setLanguage(player.getUniqueId(), lang);
}
getLocalization().sendMessage(sender, "languageChanged", msg -> msg.replace("%language%", lang));
}
}

Datei anzeigen

@ -1,192 +0,0 @@
package tsp.headdb.core.command;
import net.wesjd.anvilgui.AnvilGUI;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import tsp.headdb.HeadDB;
import tsp.headdb.core.api.HeadAPI;
import tsp.headdb.core.util.Utils;
import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.head.Head;
import tsp.headdb.implementation.head.LocalHead;
import tsp.nexuslib.inventory.Button;
import tsp.nexuslib.inventory.PagedPane;
import tsp.nexuslib.inventory.Pane;
import tsp.nexuslib.util.StringUtils;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.*;
import java.util.stream.Collectors;
public class CommandMain extends HeadDBCommand implements CommandExecutor, TabCompleter {
public CommandMain() {
super(
"headdb",
"headdb.command.open",
HeadDB.getInstance().getCommandManager().getCommandsMap().values().stream().map(HeadDBCommand::getName).collect(Collectors.toList())
);
}
@Override
@ParametersAreNonnullByDefault
public void handle(CommandSender sender, String[] args) {
if (args.length == 0) {
if (!(sender instanceof Player player)) {
getLocalization().sendConsoleMessage("noConsole");
return;
}
if (!player.hasPermission(getPermission())) {
getLocalization().sendMessage(sender, "noPermission");
return;
}
getLocalization().sendMessage(player.getUniqueId(), "openDatabase");
Pane pane = new Pane(6, Utils.translateTitle(getLocalization().getMessage(player.getUniqueId(), "menu.main.title").orElse("&cHeadDB &7(" + HeadAPI.getTotalHeads() + ")"), HeadAPI.getTotalHeads(), "Main"));
// Set category buttons
for (Category category : Category.VALUES) {
pane.setButton(getInstance().getConfig().getInt("gui.main.category." + category.getName(), category.getDefaultSlot()), new Button(category.getItem(player.getUniqueId()), e -> {
e.setCancelled(true);
if (e.isLeftClick()) {
Bukkit.dispatchCommand(e.getWhoClicked(), "hdb open " + category.getName());
} else if (e.isRightClick()) {
new AnvilGUI.Builder().onClick((slot, stateSnapshot) -> {
try {
int page = Integer.parseInt(stateSnapshot.getText());
// to be replaced with own version of anvil-gui
List<Head> heads = HeadAPI.getHeads(category);
PagedPane main = Utils.createPaged(player, Utils.translateTitle(getLocalization().getMessage(player.getUniqueId(), "menu.category.name").orElse(category.getName()), heads.size(), category.getName()));
Utils.addHeads(player, category, main, heads);
main.selectPage(page);
main.reRender();
return Arrays.asList(AnvilGUI.ResponseAction.openInventory(main.getInventory()));
} catch (NumberFormatException nfe) {
return Arrays.asList(AnvilGUI.ResponseAction.replaceInputText("Invalid number!"));
}
})
.text("Query")
.title(StringUtils.colorize(getLocalization().getMessage(player.getUniqueId(), "menu.main.category.page.name").orElse("Enter page")))
.plugin(getInstance())
.open(player);
}
}));
}
// Set meta buttons
// favorites
pane.setButton(getInstance().getConfig().getInt("gui.main.meta.favorites.slot"), new Button(Utils.getItemFromConfig("gui.main.meta.favorites.item", Material.BOOK), e -> {
e.setCancelled(true);
if (!player.hasPermission("headdb.favorites")) {
HeadDB.getInstance().getLocalization().sendMessage(player, "noAccessFavorites");
return;
}
Utils.openFavoritesMenu(player);
}));
// search
pane.setButton(getInstance().getConfig().getInt("gui.main.meta.search.slot"), new Button(Utils.getItemFromConfig("gui.main.meta.search.item", Material.DARK_OAK_SIGN), e -> {
e.setCancelled(true);
new AnvilGUI.Builder()
.onClick((slot, stateSnapshot) -> {
// Copied from CommandSearch
List<Head> heads = new ArrayList<>();
List<Head> headList = HeadAPI.getHeads();
if (stateSnapshot.getText().length() > 3) {
if (stateSnapshot.getText().startsWith("id:")) {
try {
HeadAPI.getHeadById(Integer.parseInt(stateSnapshot.getText().substring(3))).ifPresent(heads::add);
} catch (NumberFormatException ignored) {
}
} else if (stateSnapshot.getText().startsWith("tg:")) {
heads.addAll(headList.stream().filter(head -> Utils.matches(head.getTags(), stateSnapshot.getText().substring(3))).toList());
} else {
// no query prefix
heads.addAll(headList.stream().filter(head -> Utils.matches(head.getName(), stateSnapshot.getText())).toList());
}
} else {
// query is <=3, no point in looking for prefixes
heads.addAll(headList.stream().filter(head -> Utils.matches(head.getName(), stateSnapshot.getText())).toList());
}
PagedPane main = Utils.createPaged(player, Utils.translateTitle(getLocalization().getMessage(player.getUniqueId(), "menu.search.name").orElse("&cHeadDB - &eSearch Results"), heads.size(), "None", stateSnapshot.getText()));
Utils.addHeads(player, null, main, heads);
main.reRender();
return AnvilGUI.Response.openInventory(main.getInventory());
})
.title(StringUtils.colorize(getLocalization().getMessage(player.getUniqueId(), "menu.main.search.name").orElse("Search")))
.text("Query")
.plugin(getInstance())
.open(player);
}));
// local
if (getInstance().getConfig().getBoolean("localHeads")) {
pane.setButton(getInstance().getConfig().getInt("gui.main.meta.local.slot"), new Button(Utils.getItemFromConfig("gui.main.meta.local.item", Material.COMPASS), e -> {
Set<LocalHead> localHeads = HeadAPI.getLocalHeads();
PagedPane localPane = Utils.createPaged(player, Utils.translateTitle(getLocalization().getMessage(player.getUniqueId(), "menu.main.local.name").orElse("Local Heads"), localHeads.size(), "Local"));
for (LocalHead head : localHeads) {
localPane.addButton(new Button(head.getItem(), le -> {
if (le.isLeftClick()) {
ItemStack localItem = head.getItem();
if (le.isShiftClick()) {
localItem.setAmount(64);
}
player.getInventory().addItem(localItem);
}
}));
}
localPane.open(player);
}));
}
// Fill
Utils.fill(pane, Utils.getItemFromConfig("gui.main.fill", Material.BLACK_STAINED_GLASS_PANE));
pane.open(player);
return;
}
getInstance().getCommandManager().getCommand(args[0]).ifPresentOrElse(command -> {
if (sender instanceof Player player && !player.hasPermission(command.getPermission())) {
getLocalization().sendMessage(player.getUniqueId(), "noPermission");
return;
}
command.handle(sender, args);
}, () -> getLocalization().sendMessage(sender, "invalidSubCommand"));
}
@Override
@ParametersAreNonnullByDefault
public boolean onCommand(CommandSender sender, Command command, String s, String[] args) {
handle(sender, args);
return true;
}
@Nullable
@Override
@ParametersAreNonnullByDefault
public List<String> onTabComplete(CommandSender sender, Command command, String label, String[] args) {
if (args.length == 0) {
return new ArrayList<>(getCompletions());
} else {
Optional<SubCommand> sub = getInstance().getCommandManager().getCommand(args[0]);
if (sub.isPresent()) {
return new ArrayList<>(sub.get().getCompletions());
}
}
return null;
}
}

Datei anzeigen

@ -1,40 +0,0 @@
package tsp.headdb.core.command;
import java.util.Arrays;
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) {
SubCommand command = commands.get(name);
if (command != null) {
return Optional.of(command);
}
return getCommandByAlias(name);
}
public Optional<SubCommand> getCommandByAlias(String alias) {
for (SubCommand entry : commands.values()) {
if (entry.getAliases().isPresent()) {
if (Arrays.stream(entry.getAliases().get()).anyMatch(name -> name.equalsIgnoreCase(alias))) {
return Optional.of(entry);
}
}
}
return Optional.empty();
}
public Map<String, SubCommand> getCommandsMap() {
return commands;
}
}

Datei anzeigen

@ -1,23 +0,0 @@
package tsp.headdb.core.command;
import org.bukkit.command.CommandSender;
import tsp.headdb.HeadDB;
import java.util.HashSet;
// Todo: async
public class CommandReload extends SubCommand {
public CommandReload() {
super("reload", new HashSet<>(0), "r");
}
@Override
public void handle(CommandSender sender, String[] args) {
getLocalization().sendMessage(sender, "reloadCommand");
getLocalization().load();
HeadDB.getInstance().reloadConfig();
getLocalization().sendMessage(sender, "reloadCommandDone");
}
}

Datei anzeigen

@ -1,67 +0,0 @@
package tsp.headdb.core.command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import tsp.headdb.core.api.HeadAPI;
import tsp.headdb.core.util.Utils;
import tsp.headdb.implementation.head.Head;
import tsp.nexuslib.inventory.PagedPane;
import java.util.ArrayList;
import java.util.List;
public class CommandSearch extends SubCommand {
public CommandSearch() {
super("search", "s");
}
@Override
public void handle(CommandSender sender, String[] args) {
if (!(sender instanceof Player player)) {
getLocalization().sendConsoleMessage("noConsole");
return;
}
if (args.length < 2) {
getLocalization().sendMessage(player, "invalidArguments");
return;
}
StringBuilder builder = new StringBuilder();
for (int i = 1; i < args.length; i++) {
builder.append(args[i]);
if (i != args.length - 1) {
builder.append(" ");
}
}
final String query = builder.toString();
List<Head> heads = new ArrayList<>();
List<Head> headList = HeadAPI.getHeads();
if (query.length() > 3) {
if (query.startsWith("id:")) {
try {
HeadAPI.getHeadById(Integer.parseInt(query.substring(3))).ifPresent(heads::add);
} catch (NumberFormatException ignored) {
}
} else if (query.startsWith("tg:")) {
heads.addAll(headList.stream().filter(head -> Utils.matches(head.getTags(), query.substring(3))).toList());
} else {
// no query prefix
heads.addAll(headList.stream().filter(head -> Utils.matches(head.getName(), query)).toList());
}
} else {
// query is <=3, no point in looking for prefixes
heads.addAll(headList.stream().filter(head -> Utils.matches(head.getName(), query)).toList());
}
getLocalization().sendMessage(player.getUniqueId(), "searchCommand", msg -> msg.replace("%query%", query));
PagedPane main = Utils.createPaged(player, Utils.translateTitle(getLocalization().getMessage(player.getUniqueId(), "menu.search.name").orElse("&cHeadDB - &eSearch Results"), heads.size(), "None", query));
Utils.addHeads(player, null, main, heads);
getLocalization().sendMessage(player.getUniqueId(), "searchCommandResults", msg -> msg.replace("%size%", String.valueOf(heads.size())).replace("%query%", query));
main.open(player);
}
}

Datei anzeigen

@ -1,53 +0,0 @@
package tsp.headdb.core.command;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import tsp.headdb.core.util.Utils;
import tsp.nexuslib.builder.ItemBuilder;
import tsp.nexuslib.inventory.Button;
import tsp.nexuslib.inventory.PagedPane;
import tsp.nexuslib.inventory.Pane;
import tsp.nexuslib.util.StringUtils;
import java.util.Set;
public class CommandSettings extends SubCommand {
public CommandSettings() {
super("settings", "st");
}
@Override
public void handle(CommandSender sender, String[] args) {
if (!(sender instanceof Player player)) {
getLocalization().sendConsoleMessage("noConsole");
return;
}
Set<String> languages = getLocalization().getData().keySet();
Pane pane = new Pane(1, StringUtils.colorize(getLocalization().getMessage(player.getUniqueId(), "menu.settings.name").orElse("&cHeadDB - Settings")));
pane.addButton(new Button(new ItemBuilder(Material.BOOK)
.name(getLocalization().getMessage(player.getUniqueId(), "menu.settings.language.name").orElse("&cLanguage"))
.setLore(getLocalization().getMessage(player.getUniqueId(), "menu.settings.language.available").orElse("&7Languages Available: &e%size%").replace("%size%", String.valueOf(languages.size())))
.build(), e -> {
e.setCancelled(true);
PagedPane langPane = new PagedPane(4, 6, Utils.translateTitle(getLocalization().getMessage(player.getUniqueId(), "menu.settings.language.title").orElse("&cHeadDB &7- &eSelect Language").replace("%languages%", "%size%"), languages.size(), "Selector: Language"));
for (String lang : languages) {
langPane.addButton(new Button(new ItemBuilder(Material.PAPER)
.name(getLocalization().getMessage(player.getUniqueId(), "menu.settings.language.format").orElse(ChatColor.YELLOW + lang).replace("%language%", lang))
.build(), langEvent -> {
e.setCancelled(true);
getLocalization().setLanguage(player.getUniqueId(), lang);
getLocalization().sendMessage(player.getUniqueId(), "languageChanged", msg -> msg.replace("%language%", lang));
}));
}
langPane.open(player);
}));
pane.open(player);
}
}

Datei anzeigen

@ -1,33 +0,0 @@
package tsp.headdb.core.command;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.hover.content.Text;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import tsp.headdb.core.util.Utils;
import tsp.nexuslib.util.StringUtils;
public class CommandTexture extends SubCommand {
public CommandTexture() {
super("texture", "t");
}
@Override
public void handle(CommandSender sender, String[] args) {
if (!(sender instanceof Player player)) {
getLocalization().sendConsoleMessage("noConsole");
return;
}
Utils.getTexture(player.getInventory().getItemInMainHand()).ifPresentOrElse(texture -> getLocalization().getMessage(player.getUniqueId(), "itemTexture").ifPresent(message -> {
TextComponent component = new TextComponent(StringUtils.colorize(message.replace("%texture%", texture)));
component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(StringUtils.colorize(getLocalization().getMessage(player.getUniqueId(), "copyTexture").orElse("Click to copy!")))));
component.setClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, texture));
player.spigot().sendMessage(component);
}), () -> getLocalization().sendMessage(sender,"itemNoTexture"));
}
}

Datei anzeigen

@ -1,28 +0,0 @@
package tsp.headdb.core.command;
import org.bukkit.command.CommandSender;
import tsp.headdb.HeadDB;
import tsp.headdb.core.api.HeadAPI;
import tsp.headdb.implementation.head.HeadResult;
import tsp.helperlite.scheduler.promise.Promise;
public class CommandUpdate extends SubCommand {
public CommandUpdate() {
super("update", "u");
}
@Override
public void handle(CommandSender sender, String[] args) {
getLocalization().sendMessage(sender, "updateDatabase");
try (Promise<HeadResult> promise = HeadAPI.getDatabase().update()) {
promise.thenAcceptSync(result -> {
HeadDB.getInstance().getLog().debug("Database Updated! Heads: " + result.heads().values().size() + " | Took: " + result.elapsed() + "ms");
getLocalization().sendMessage(sender, "updateDatabaseDone", msg -> msg.replace("%size%", String.valueOf(result.heads().values().size())));
});
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}

Datei anzeigen

@ -1,59 +0,0 @@
package tsp.headdb.core.command;
import org.bukkit.command.CommandSender;
import tsp.headdb.HeadDB;
import tsp.nexuslib.localization.TranslatableLocalization;
import tsp.nexuslib.util.Validate;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.ArrayList;
import java.util.Collection;
public abstract class HeadDBCommand {
private static final TranslatableLocalization localization = HeadDB.getInstance().getLocalization();
private final HeadDB instance = HeadDB.getInstance();
private final String name;
private final String permission;
private final Collection<String> completions;
@ParametersAreNonnullByDefault
public HeadDBCommand(String name, String permission, Collection<String> completions) {
Validate.notNull(name, "Name can not be null!");
Validate.notNull(permission, "Permission can not be null!");
Validate.notNull(completions, "Completions can not be null!");
this.name = name;
this.permission = permission;
this.completions = completions;
}
@ParametersAreNonnullByDefault
public HeadDBCommand(String name, String permission) {
this(name, permission, new ArrayList<>());
}
public abstract void handle(CommandSender sender, String[] args);
public Collection<String> getCompletions() {
return completions;
}
public String getName() {
return name;
}
public String getPermission() {
return permission;
}
public TranslatableLocalization getLocalization() {
return localization;
}
public HeadDB getInstance() {
return instance;
}
}

Datei anzeigen

@ -1,36 +0,0 @@
package tsp.headdb.core.command;
import tsp.headdb.HeadDB;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Optional;
// args[0] = sub-command | args[1+] = params
public abstract class SubCommand extends HeadDBCommand {
private final String[] aliases;
public SubCommand(String name, Collection<String> completions, @Nullable String... aliases) {
super(name, "headdb.command." + name, completions);
this.aliases = aliases;
}
public SubCommand(String name, String... aliases) {
this(name, new ArrayList<>(), aliases);
}
public SubCommand(String name) {
this(name, (String[]) null);
}
public Optional<String[]> getAliases() {
return Optional.ofNullable(aliases);
}
public void register() {
HeadDB.getInstance().getCommandManager().register(this);
}
}

Datei anzeigen

@ -0,0 +1,106 @@
package tsp.headdb.core.commands;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import tsp.headdb.api.HeadAPI;
import tsp.headdb.api.model.Head;
import tsp.headdb.core.util.Utils;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
* @author TheSilentPro (Silent)
*/
public class CommandGive extends HDBCommand {
public CommandGive() {
super("give", "<player> <amount> <head>", "Give a specific head. You can use names, texture values, or prefix 'id:' for ids.", false, "g");
}
// hdb give <player> <amount> <head>
@Override
public void handle(@NotNull CommandSender sender, String[] args) {
if (args.length < 3) {
localization.sendMessage(sender, "invalidArguments");
return;
}
Player target = Bukkit.getPlayer(args[0]);
if (target == null) {
localization.sendMessage(sender, "invalidTarget", msg -> msg.replace("%name%", args[0]));
return;
}
int amount = 1;
try {
amount = Integer.parseInt(args[1]);
} catch (NumberFormatException nfe) {
localization.sendMessage(sender, "invalidNumber", msg -> msg.replace("%name%", args[1]));
}
//localization.sendMessage(sender, "");
final int fAmount = amount;
String id = String.join(" ", Arrays.copyOfRange(args, 2, args.length));
HeadAPI.getHeadByExactName(id, true)
.thenCompose(optionalHead -> {
if (optionalHead.isPresent()) {
return CompletableFuture.completedFuture(optionalHead.get());
} else if (id.startsWith("id:")) {
try {
int numericId = Integer.parseInt(id.substring(3));
return HeadAPI.getHeadById(numericId).thenApply(optional -> optional.orElse(null));
} catch (NumberFormatException e) {
return CompletableFuture.completedFuture(null);
}
} else {
return HeadAPI.getHeadByTexture(id).thenApply(optional -> optional.orElse(null));
}
})
.thenAcceptAsync(head -> {
if (head == null) {
localization.sendMessage(sender, "command.give.invalid", msg -> msg.replace("%name%", id));
return;
}
ItemStack item = head.getItem();
item.setAmount(fAmount);
target.getInventory().addItem(item);
localization.sendMessage(sender, "command.give.done", msg ->
msg.replace("%name%", head.getName())
.replace("%amount%", String.valueOf(fAmount))
.replace("%player%", target.getName()));
}, Utils.SYNC);
}
private final List<String> one = Collections.singletonList("1"); // No reason to do more.
@Override
public List<String> handleCompletions(CommandSender sender, String[] args) {
if (args.length == 1) {
return Bukkit.getOnlinePlayers().stream().map(Player::getName).toList();
} else if (args.length == 2) {
return one;
} else if (args.length == 3) {
if (sender instanceof ConsoleCommandSender) {
return HeadAPI.getHeads().stream().map(Head::getName).toList().subList(1, 100); // The huge amount of heads can cause console terminals to crash.
} else {
return HeadAPI.getHeads().stream().map(Head::getName).toList();
}
} else {
return null;
}
}
@Override
public boolean waitUntilReady() {
return true;
}
}

Datei anzeigen

@ -0,0 +1,52 @@
package tsp.headdb.core.commands;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.hover.content.Text;
import org.bukkit.command.CommandSender;
import tsp.headdb.HeadDB;
/**
* @author TheSilentPro (Silent)
*/
public class CommandHelp extends HDBCommand {
private TextComponent[] messages;
public CommandHelp() {
super("help", "Shows the help message.", false, "h", "commands", "cmds");
}
@Override
public void handle(CommandSender sender, String[] args) {
if (messages == null) {
int size = HeadDB.getInstance().getCommandManager().getCommands().size();
messages = new TextComponent[size];
int index = 0;
for (HDBCommand command : HeadDB.getInstance().getCommandManager().getCommands()) {
TextComponent component = new TextComponent(ChatColor.GRAY + "/hdb " + ChatColor.DARK_AQUA + command.getName() + " " + ChatColor.DARK_AQUA + command.getParameters() + ChatColor.GRAY + " - " + command.getDescription());
component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
new Text(ChatColor.GRAY + "Aliases: " + ChatColor.GOLD + String.join(ChatColor.GRAY + " | " + ChatColor.GOLD, command.getAliases())),
new Text(ChatColor.GRAY + "\nPermission: " + ChatColor.RED + "headdb.command." + command.getName()),
new Text("\n"),
new Text(command.isNoConsole() ? ChatColor.RED + "\nThis command CAN NOT be used from console!" : ChatColor.GREEN + "\nThis command CAN be used from console!")
));
component.setClickEvent(new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, "/hdb " + command.getName() + " "));
messages[index] = component;
index++;
}
}
sender.sendMessage(ChatColor.GRAY + "<==================== [ " + ChatColor.RED + "HeadDB " + ChatColor.GRAY + "|" + ChatColor.DARK_PURPLE + "Commands" + ChatColor.GRAY + "] ====================>");
sender.sendMessage(ChatColor.GRAY + "Format: /hdb " + ChatColor.DARK_AQUA + "<sub-command> " + ChatColor.RED + "<parameters> " + ChatColor.GRAY + "- Description");
sender.sendMessage(ChatColor.GRAY + "Required: " + ChatColor.RED + "<> " + ChatColor.GRAY + "| Optional: " + ChatColor.AQUA + "[]");
sender.sendMessage(" ");
for (TextComponent message : messages) {
sender.spigot().sendMessage(message);
}
sender.sendMessage(ChatColor.GRAY + "<===============================================================>");
}
}

Datei anzeigen

@ -0,0 +1,31 @@
package tsp.headdb.core.commands;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.chat.hover.content.Text;
import org.bukkit.command.CommandSender;
import tsp.headdb.HeadDB;
import tsp.headdb.core.util.Utils;
/**
* @author TheSilentPro (Silent)
*/
public class CommandInfo extends HDBCommand {
public CommandInfo() {
super("info", "Show information about the plugin.", false, "i");
}
@Override
public void handle(CommandSender sender, String[] args) {
TextComponent component = new TextComponent(ChatColor.GRAY + "Running " + ChatColor.GOLD + "HeadDB - " + HeadDB.getInstance().getDescription().getVersion() + "\n" + ChatColor.GRAY + "GitHub: " + ChatColor.GOLD + "https://github.com/TheSilentPro/HeadDB");
if (sender.hasPermission("headdb.admin")) {
component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(ChatColor.GOLD + "CLICK TO COPY INFO: " + Utils.getUserAgent())));
component.setClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, Utils.getUserAgent()));
}
sender.spigot().sendMessage(component);
}
}

Datei anzeigen

@ -0,0 +1,116 @@
package tsp.headdb.core.commands;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tsp.headdb.HeadDB;
import tsp.headdb.api.HeadAPI;
import tsp.headdb.core.util.Localization;
import tsp.headdb.core.util.MenuSetup;
import java.util.*;
/**
* @author TheSilentPro (Silent)
*/
public class CommandManager implements CommandExecutor, TabCompleter {
private static final Logger LOGGER = LoggerFactory.getLogger(CommandManager.class);
private final Localization localization = HeadDB.getInstance().getLocalization();
private final Set<HDBCommand> commands = new HashSet<>();
public CommandManager init() {
register(new CommandHelp());
register(new CommandInfo());
register(new CommandOpen());
register(new CommandGive());
register(new CommandSearch());
register(new CommandUpdate());
return this;
}
public void register(HDBCommand command) {
this.commands.add(command);
LOGGER.debug("Registered sub-command: {}", command.getName());
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length > 0) {
for (HDBCommand sub : commands) {
if (sub.matches(args[0])) {
if (!sender.hasPermission("headdb.command." + sub.getName())) {
localization.sendMessage(sender, "noPermission");
return true;
}
if (sub.isNoConsole() && !(sender instanceof Player)) {
localization.sendMessage(sender, "noConsole");
return true;
}
if (sub.waitUntilReady() && !HeadAPI.getDatabase().isReady()) {
localization.sendMessage(sender, "notReadyDatabase");
return true;
}
sub.handle(sender, Arrays.copyOfRange(args, 1, args.length));
return true;
}
}
localization.sendMessage(sender, "invalidSubCommand", msg -> msg.replace("%name%", args[0]));
return true;
}
// No sub command provided, open gui
if (!(sender instanceof Player player)) {
localization.sendMessage(sender, "noConsole");
return true;
}
if (!HeadAPI.getDatabase().isReady()) {
localization.sendMessage(sender, "notReadyDatabase");
return true;
}
MenuSetup.mainGui.open(player);
localization.sendMessage(player, "openDatabase");
return true;
}
@Override
public @Nullable List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
if (args.length == 0) {
return null;
}
if (args.length == 1) {
return commands.stream().map(HDBCommand::getName).toList();
}
for (HDBCommand sub : commands) {
if (sub.matches(args[0])) {
if (!sender.hasPermission("headdb.command." + sub.getName())) {
return null;
}
if (sub.isNoConsole() && !(sender instanceof Player)) {
return null;
}
return sub.handleCompletions(sender, Arrays.copyOfRange(args, 1, args.length));
}
}
return null;
}
public Set<HDBCommand> getCommands() {
return Collections.unmodifiableSet(commands);
}
}

Datei anzeigen

@ -0,0 +1,51 @@
package tsp.headdb.core.commands;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import tsp.headdb.HeadDB;
import tsp.headdb.api.model.Category;
import tsp.headdb.core.util.MenuSetup;
import java.util.Arrays;
import java.util.List;
/**
* @author TheSilentPro (Silent)
*/
public class CommandOpen extends HDBCommand {
private final static List<String> categories = Arrays.stream(Category.VALUES).map(Category::getName).toList();
public CommandOpen() {
super("open", "[category]", "Open the head menu. Optionally a specific category.", true, "o");
}
@Override
public void handle(CommandSender sender, String[] args) {
if (args.length == 0) {
if (!(sender instanceof Player player)) {
localization.sendMessage(sender, "noConsole");
return;
}
MenuSetup.mainGui.open(player);
localization.sendMessage(player, "openDatabase");
return;
}
Category.getByName(args[0]).ifPresentOrElse(category -> MenuSetup.categoryGuis.get(category).open((Player) sender),
() -> localization.sendMessage(sender, "invalidCategory", msg -> msg.replace("%name%", args[0]))
);
}
@Override
public List<String> handleCompletions(CommandSender sender, String[] args) {
return categories;
}
@Override
public boolean waitUntilReady() {
return true;
}
}

Datei anzeigen

@ -0,0 +1,100 @@
package tsp.headdb.core.commands;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import tsp.headdb.api.HeadAPI;
import tsp.headdb.api.model.Head;
import tsp.headdb.core.util.MenuSetup;
import tsp.headdb.core.util.Utils;
import java.time.LocalDate;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
/**
* @author TheSilentPro (Silent)
*/
public class CommandSearch extends HDBCommand {
public CommandSearch() {
super("search", "[tags:|contributors:|collections:|dates:|before:|after:] <query>", "Search the database with possible filters.", true, "s", "find");
}
// hdb search <search>
@Override
public void handle(CommandSender sender, String[] args) {
if (args.length < 1) {
localization.sendMessage(sender, "invalidArguments");
return;
}
String id = String.join(" ", Arrays.copyOfRange(args, 0, args.length));
localization.sendMessage(sender, "command.search.wait", msg -> msg.replace("%name%", id));
if (id.startsWith("tags:")) {
args[0] = args[0].substring(5); // Remove prefix
HeadAPI.getHeadsByTags(args).thenAcceptAsync(heads -> handle(heads, sender, id.substring(5)), Utils.SYNC);
return;
}
if (id.startsWith("contributors:")) {
args[0] = args[0].substring(13); // Remove prefix
HeadAPI.getHeadsByContributors(args).thenAcceptAsync(heads -> handle(heads, sender, id.substring(13)), Utils.SYNC);
return;
}
if (id.startsWith("collections:")) {
args[0] = args[0].substring(12); // Remove prefix
HeadAPI.getHeadsByCollections(args).thenAcceptAsync(heads -> handle(heads, sender, id.substring(12)), Utils.SYNC);
return;
}
if (id.startsWith("dates:")) {
args[0] = args[0].substring(6); // Remove prefix
HeadAPI.getHeadsByDates(args).thenAcceptAsync(heads -> handle(heads, sender, id.substring(6)), Utils.SYNC);
return;
}
if (id.startsWith("before:")) {
LocalDate date = LocalDate.parse(id.substring(7), Utils.DATE_FORMATTER);
CompletableFuture.supplyAsync(() -> HeadAPI.getHeadStream().filter(head -> {
if (head.getPublishDate().isEmpty()) {
return false;
}
return LocalDate.parse(head.getPublishDate().get(), Utils.DATE_FORMATTER).isBefore(date);
}).toList()).thenAcceptAsync(heads -> handle(heads, sender, id.substring(7)), Utils.SYNC);
return;
}
if (id.startsWith("after:")) {
LocalDate date = LocalDate.parse(id.substring(6), Utils.DATE_FORMATTER);
CompletableFuture.supplyAsync(() -> HeadAPI.getHeadStream().filter(head -> {
if (head.getPublishDate().isEmpty()) {
return false;
}
return LocalDate.parse(head.getPublishDate().get(), Utils.DATE_FORMATTER).isAfter(date);
}).toList()).thenAcceptAsync(heads -> handle(heads, sender, id.substring(6)), Utils.SYNC);
return;
}
HeadAPI.getHeadsByName(id, true).thenAcceptAsync(heads -> handle(heads, sender, id), Utils.SYNC);
}
private void handle(List<Head> heads, CommandSender sender, String id) {
if (heads.isEmpty()) {
localization.sendMessage(sender, "command.search.invalid", msg -> msg.replace("%name%", id));
return;
}
localization.sendMessage(sender, "command.search.done", msg -> msg.replace("%name%", id).replace("%size%", String.valueOf(heads.size())));
MenuSetup.openSearch(heads, id, (Player) sender);
}
@Override
public boolean waitUntilReady() {
return true;
}
}

Datei anzeigen

@ -0,0 +1,28 @@
package tsp.headdb.core.commands;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import tsp.headdb.HeadDB;
import tsp.headdb.core.util.Utils;
/**
* @author TheSilentPro (Silent)
*/
public class CommandUpdate extends HDBCommand {
public CommandUpdate() {
super("update", "Forcefully start updating the database. " + ChatColor.RED + "(NOT RECOMMENDED)", false, "u");
}
@Override
public void handle(CommandSender sender, String[] args) {
localization.sendMessage(sender, "updateStarted");
HeadDB.getInstance().updateDatabase().thenAcceptAsync(heads -> localization.sendMessage(sender, "updateFinished", msg -> msg.replace("%size%", String.valueOf(heads.size()))), Utils.SYNC);
}
@Override
public boolean waitUntilReady() {
return true;
}
}

Datei anzeigen

@ -0,0 +1,110 @@
package tsp.headdb.core.commands;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import tsp.headdb.HeadDB;
import tsp.headdb.core.util.Localization;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;
/**
* @author TheSilentPro (Silent)
*/
public abstract class HDBCommand {
private static final Pattern DELIMITER = Pattern.compile(" ");
protected final Localization localization = HeadDB.getInstance().getLocalization();
private final String name;
private final String parameters;
private final String[] aliases;
private final String description;
private final boolean noConsole;
public HDBCommand(String name, String parameters, String description, boolean noConsole, String... aliases) {
this.name = name;
this.aliases = aliases;
this.noConsole = noConsole;
this.description = description;
if (parameters != null && !parameters.isEmpty()) {
String[] params = DELIMITER.split(parameters);
for (int i = 0; i < params.length; i++) {
String param = params[i];
if (param.startsWith("[") && param.endsWith("]")) {
params[i] = ChatColor.DARK_AQUA + param;
} else {
params[i] = ChatColor.RED + param;
}
}
this.parameters = String.join(" ", params);
} else {
this.parameters = "";
}
}
public HDBCommand(String name, String description, boolean noConsole, String... aliases) {
this(name, null, description, noConsole, aliases);
}
public boolean waitUntilReady() {
return false;
}
public boolean matches(String raw) {
if (raw.equalsIgnoreCase(name)) {
return true;
}
for (String alias : aliases) {
if (raw.equalsIgnoreCase(alias)) {
return true;
}
}
return false;
}
/**
* Handle sub command execution.
* Arguments do not include the sub command.
*
* @param sender The sender of the command.
* @param args The arguments.
*/
public abstract void handle(CommandSender sender, String[] args);
/**
* Handle sub command execution.
* Arguments do not include the sub command.
*
* @param sender The sender of the command.
* @param args The arguments.
*/
public List<String> handleCompletions(CommandSender sender, String[] args) {
return Collections.emptyList();
}
public String getName() {
return name;
}
public String getParameters() {
return parameters;
}
public String[] getAliases() {
return aliases;
}
public String getDescription() {
return description;
}
public boolean isNoConsole() {
return noConsole;
}
}

Datei anzeigen

@ -0,0 +1,115 @@
package tsp.headdb.core.config;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.file.FileConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tsp.headdb.api.model.Category;
import tsp.headdb.api.HeadAPI;
import tsp.headdb.api.model.Head;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
/**
* @author TheSilentPro (Silent)
*/
public class ConfigData {
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigData.class);
private boolean requireCategoryPermission;
private boolean preloadHeads;
private boolean includeLore;
private boolean moreInfo;
private boolean economyEnabled;
private String economyProvider;
private double defaultCost;
private double categoryDefaultCost;
private double localCost;
private final Map<String, Double> categoryCosts = new HashMap<>();
private final Map<Head, Double> costs = new HashMap<>();
public ConfigData(FileConfiguration config) {
reload(config);
}
public void reload(FileConfiguration config) {
this.requireCategoryPermission = config.getBoolean("requireCategoryPermissions");
this.preloadHeads = config.getBoolean("preloadHeads");
this.includeLore = config.getBoolean("includeLore");
this.moreInfo = config.getBoolean("moreInfo");
this.economyEnabled = config.getBoolean("economy.enabled");
this.economyProvider = config.getString("economy.provider");
this.defaultCost = config.getDouble("economy.defaultCost");
this.categoryDefaultCost = config.getDouble("economy.defaultCategoryCost");
this.localCost = config.getDouble("economy.localCost");
for (Category category : Category.VALUES) {
categoryCosts.put(category.getName(), config.getDouble("economy.categoryCost." + category.getName())); // Do not put default category cost as default value for this!
}
if (this.economyEnabled) {
ConfigurationSection costSection = config.getConfigurationSection("economy.cost");
if (costSection != null) {
for (String key : costSection.getKeys(false)) {
try {
HeadAPI.getHeadById(Integer.parseInt(key)).join().ifPresentOrElse(head -> costs.put(head, config.getDouble("economy.cost." + key)), () -> LOGGER.warn("Failed to find head with id: {}", key));
} catch (NumberFormatException nfe) {
HeadAPI.getHeadByTexture(key).join().ifPresentOrElse(head -> costs.put(head, config.getDouble("economy.cost." + key)), () -> LOGGER.warn("Failed to find head with texture: {}", key));
}
}
} else {
LOGGER.warn("No cost section defined in the configuration.");
}
}
}
public boolean isEconomyEnabled() {
return economyEnabled;
}
public String getEconomyProvider() {
return economyProvider;
}
public double getLocalCost() {
return localCost;
}
public double getDefaultCost() {
return defaultCost;
}
public double getCategoryDefaultCost() {
return categoryDefaultCost;
}
public Map<String, Double> getCategoryCosts() {
return categoryCosts;
}
public Map<Head, Double> getCosts() {
return costs;
}
public boolean shouldPreloadHeads() {
return preloadHeads;
}
public boolean shouldRequireCategoryPermission() {
return requireCategoryPermission;
}
public boolean shouldIncludeLore() {
return includeLore;
}
public boolean shouldIncludeMoreInfo() {
return moreInfo;
}
}

Datei anzeigen

@ -1,20 +0,0 @@
package tsp.headdb.core.economy;
import org.bukkit.entity.Player;
import tsp.helperlite.scheduler.promise.Promise;
import java.math.BigDecimal;
public interface BasicEconomyProvider {
Promise<Boolean> canPurchase(Player player, BigDecimal cost);
Promise<Boolean> withdraw(Player player, BigDecimal amount);
default Promise<Boolean> purchase(Player player, BigDecimal amount) {
return canPurchase(player, amount).thenComposeAsync(result -> result ? withdraw(player, amount) : Promise.completed(false));
}
void init();
}

Datei anzeigen

@ -0,0 +1,28 @@
package tsp.headdb.core.economy;
import org.bukkit.entity.Player;
import java.util.concurrent.CompletableFuture;
/**
* @author TheSilentPro (Silent)
*/
public interface EconomyProvider {
void init();
CompletableFuture<Boolean> canAfford(Player player, double amount);
CompletableFuture<Boolean> withdraw(Player player, double amount);
default CompletableFuture<Boolean> purchase(Player player, double cost) {
return canAfford(player, cost).thenCompose(afforded -> {
if (!afforded) {
return CompletableFuture.completedFuture(false);
} else {
return withdraw(player, cost);
}
});
}
}

Datei anzeigen

@ -4,42 +4,43 @@ import net.milkbowl.vault.economy.Economy;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.RegisteredServiceProvider;
import tsp.headdb.HeadDB; import org.slf4j.Logger;
import tsp.helperlite.scheduler.promise.Promise; import org.slf4j.LoggerFactory;
import java.math.BigDecimal; import java.util.concurrent.CompletableFuture;
public class VaultProvider implements BasicEconomyProvider { /**
* @author TheSilentPro (Silent)
*/
public class VaultProvider implements EconomyProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(VaultProvider.class);
private Economy economy; private Economy economy;
@Override
public Promise<Boolean> canPurchase(Player player, BigDecimal cost) {
double effectiveCost = cost.doubleValue();
return Promise.supplyingAsync(() -> economy.has(player, effectiveCost >= 0 ? effectiveCost : 0));
}
@Override
public Promise<Boolean> withdraw(Player player, BigDecimal amount) {
double effectiveCost = amount.doubleValue();
return Promise.supplyingAsync(() -> economy.withdrawPlayer(player, effectiveCost >= 0 ? effectiveCost : 0).transactionSuccess());
}
@Override @Override
public void init() { public void init() {
if (Bukkit.getServer().getPluginManager().getPlugin("Vault") == null) { if (Bukkit.getServer().getPluginManager().getPlugin("Vault") == null) {
HeadDB.getInstance().getLog().error("Vault is not installed!"); LOGGER.error("Vault is not installed but is enabled in the config.yml!");
return; return;
} }
RegisteredServiceProvider<Economy> economyProvider = Bukkit.getServer().getServicesManager().getRegistration(net.milkbowl.vault.economy.Economy.class); RegisteredServiceProvider<Economy> economyProvider = Bukkit.getServer().getServicesManager().getRegistration(net.milkbowl.vault.economy.Economy.class);
if (economyProvider == null) { if (economyProvider == null) {
HeadDB.getInstance().getLog().error("Could not find vault economy provider!"); LOGGER.error("Could not find vault economy provider!");
return; return;
} }
economy = economyProvider.getProvider(); this.economy = economyProvider.getProvider();
}
@Override
public CompletableFuture<Boolean> canAfford(Player player, double amount) {
return CompletableFuture.supplyAsync(() -> economy.has(player, Math.max(0, amount)));
}
@Override
public CompletableFuture<Boolean> withdraw(Player player, double amount) {
return CompletableFuture.supplyAsync(() -> economy.withdrawPlayer(player, Math.max(0, amount)).transactionSuccess());
} }
} }

Datei anzeigen

@ -1,9 +0,0 @@
package tsp.headdb.core.hook;
import org.bukkit.Bukkit;
public class Hooks {
public static final PluginHook PAPI = new PluginHook(Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null);
}

Datei anzeigen

@ -1,3 +0,0 @@
package tsp.headdb.core.hook;
public record PluginHook(boolean enabled) {}

Datei anzeigen

@ -0,0 +1,65 @@
package tsp.headdb.core.player;
import java.util.*;
/**
* @author TheSilentPro (Silent)
*/
public class PlayerData {
private final int id;
private final UUID uuid;
private String lang;
private boolean soundEnabled;
private final Set<Integer> favorites;
public PlayerData(int id, UUID uuid, String lang, boolean soundEnabled, int... favorites) {
this.id = id;
this.uuid = uuid;
this.lang = lang != null ? lang : "en";
this.soundEnabled = soundEnabled;
this.favorites = new HashSet<>();
if (favorites != null) {
for (int favorite : favorites) {
this.favorites.add(favorite);
}
}
}
public int getId() {
return id;
}
public UUID getUuid() {
return uuid;
}
public String getLang() {
return lang;
}
public boolean isSoundEnabled() {
return soundEnabled;
}
public void setSoundEnabled(boolean soundEnabled) {
this.soundEnabled = soundEnabled;
}
public void setLang(String lang) {
this.lang = lang != null ? lang : "en";
}
public Set<Integer> getFavorites() {
return favorites;
}
public boolean addFavorite(int favorite) {
return this.favorites.add(favorite);
}
public boolean removeFavorite(int favorite) {
return this.favorites.remove(favorite);
}
}

Datei anzeigen

@ -0,0 +1,85 @@
package tsp.headdb.core.player;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tsp.headdb.HeadDB;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Pattern;
/**
* @author TheSilentPro (Silent)
*/
public class PlayerDatabase {
private static final Logger LOGGER = LoggerFactory.getLogger(PlayerDatabase.class);
private static final Pattern FAVORITES_DELIMITER = Pattern.compile("\\|");
private final ConcurrentHashMap<UUID, PlayerData> data = new ConcurrentHashMap<>();
public PlayerData getOrCreate(UUID uuid) {
PlayerData data = this.data.get(uuid);
if (data == null) {
data = new PlayerData(this.data
.values()
.stream()
.mapToInt(PlayerData::getId)
.max()
.orElse(0) + 1,
uuid,
"en",
true);
this.data.put(uuid, data);
}
return data;
}
private PlayerData register(int id, UUID uuid, String lang, boolean enableSounds, int... favorites) {
if (!this.data.containsKey(uuid)) {
this.data.put(uuid, new PlayerData(id, uuid, lang, enableSounds, favorites));
}
return this.data.get(uuid);
}
public void save() {
HeadDB.getInstance().getStorage().insertAllPlayers(data).whenComplete((result, ex) -> {
if (ex != null) {
LOGGER.error("Failed to save all players!", ex);
return;
}
LOGGER.info("Successfully saved all players to the database.");
});
}
public void load() {
HeadDB.getInstance().getStorage().selectPlayers().whenComplete((resultSet, ex) -> {
if (ex != null) {
LOGGER.error("Failed to load players!", ex);
return;
}
try {
int count = 0;
while (resultSet.next()) {
count++;
UUID uuid = UUID.fromString(resultSet.getString("uuid"));
int[] favorites = Arrays.stream(FAVORITES_DELIMITER.split(resultSet.getString("favorites")))
.mapToInt(Integer::parseInt)
.toArray();
this.data.put(uuid, register(resultSet.getInt("id"), uuid, resultSet.getString("lang"), resultSet.getBoolean("soundEnabled"), favorites));
}
LOGGER.info("Loaded {} players!", count);
} catch (SQLException sqlex) {
LOGGER.error("Failed to iterate players!", sqlex);
}
});
}
public Map<UUID, PlayerData> getData() {
return Collections.unmodifiableMap(data);
}
}

Datei anzeigen

@ -1,7 +0,0 @@
package tsp.headdb.core.storage;
import java.io.Serializable;
import java.util.Set;
import java.util.UUID;
public record PlayerData(UUID uniqueId, Set<String> favorites) implements Serializable {}

Datei anzeigen

@ -1,73 +0,0 @@
package tsp.headdb.core.storage;
import tsp.headdb.HeadDB;
import tsp.warehouse.storage.file.SerializableFileDataManager;
import java.io.File;
import java.util.*;
public class PlayerStorage extends SerializableFileDataManager<HashSet<PlayerData>> {
private final Map<UUID, PlayerData> players = new HashMap<>();
public PlayerStorage(HeadDB instance, Storage storage) {
super(new File(instance.getDataFolder(), "data/players.data"), storage.getExecutor());
}
public void set(PlayerData data) {
this.players.put(data.uniqueId(), data);
}
public Set<String> getFavorites(UUID uuid) {
return players.containsKey(uuid) ? players.get(uuid).favorites() : new HashSet<>();
}
public void addFavorite(UUID uuid, String texture) {
Set<String> fav = getFavorites(uuid);
fav.add(texture);
players.put(uuid, new PlayerData(uuid, new HashSet<>(fav)));
}
public void removeFavorite(UUID uuid, String texture) {
Set<String> fav = getFavorites(uuid);
fav.remove(texture);
players.put(uuid, new PlayerData(uuid, new HashSet<>(fav)));
}
public Optional<PlayerData> get(UUID uuid) {
return Optional.ofNullable(players.get(uuid));
}
public Map<UUID, PlayerData> getPlayersMap() {
return Collections.unmodifiableMap(players);
}
public void init() {
load().whenComplete((data, ex) -> {
for (PlayerData entry : data.orElse(new HashSet<>())) {
players.put(entry.uniqueId(), entry);
}
HeadDB.getInstance().getLog().debug("Loaded " + players.values().size() + " player data!");
});
}
public void backup() {
save(new HashSet<>(players.values())).whenComplete((success, ex) -> HeadDB.getInstance().getLog().debug("Saved " + players.values().size() + " player data!"));
}
public void suspend() {
Boolean saved = save(new HashSet<>(players.values()))
.exceptionally(ex -> {
HeadDB.getInstance().getLog().error("Failed to save player data! | Stack Trace: ");
ex.printStackTrace();
return false;
})
.join();
if (Boolean.TRUE.equals(saved)) {
HeadDB.getInstance().getLog().debug("Saved " + players.values().size() + " player data!");
}
}
}

Datei anzeigen

@ -0,0 +1,103 @@
package tsp.headdb.core.storage;
import tsp.headdb.core.player.PlayerData;
import java.io.IOException;
import java.sql.*;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* @author TheSilentPro (Silent)
*/
public enum SQLStatement {
TABLES_CREATE(
"""
CREATE TABLE IF NOT EXISTS hdb_players (
id INTEGER PRIMARY KEY AUTOINCREMENT,
uuid UUID UNIQUE NOT NULL,
lang VARCHAR(8),
soundEnabled BOOLEAN,
favorites TEXT
);
CREATE TABLE IF NOT EXISTS hdb_heads (
id INTEGER UNIQUE PRIMARY KEY NOT NULL, -- numeric id of the head
texture VARCHAR(255), -- texture
unique_id UUID NOT NULL, -- Unique UUID of the head
name VARCHAR(60) NOT NULL, -- Name of the head
tags VARCHAR(255), -- Tags for the head (optional)
category VARCHAR(255) -- Category of the head (optional)
);
"""
),
SELECT_PLAYERS("SELECT * FROM hdb_players;"),
INSERT_PLAYER(
"""
INSERT INTO hdb_players(id, uuid, lang, soundEnabled, favorites)
VALUES(?, ?, ?, ?, ?)
ON CONFLICT(uuid) DO UPDATE SET
id = COALESCE(EXCLUDED.id, hdb_players.id),
lang = COALESCE(EXCLUDED.lang, hdb_players.lang),
soundEnabled = COALESCE(EXCLUDED.soundEnabled, hdb_players.soundEnabled),
favorites = COALESCE(EXCLUDED.favorites, hdb_players.favorites);
""",
Types.VARCHAR, Types.VARCHAR, Types.BOOLEAN, Types.VARCHAR
);
private final String statement;
private final int[] types;
SQLStatement(String statement, int... types) {
this.statement = statement;
this.types = types;
}
SQLStatement(String statement) {
this(statement, (int[]) null);
}
public int executeUpdate(Connection connection) throws SQLException, IOException {
return connection.createStatement().executeUpdate(statement);
}
public int executePreparedUpdate(Connection connection, Object... parameters) throws SQLException {
try (PreparedStatement preparedStatement = connection.prepareStatement(statement)) {
if (parameters.length != types.length) {
throw new IllegalArgumentException("Number of parameters does not match the number of placeholders.");
}
for (int i = 0; i < parameters.length; i++) {
preparedStatement.setObject(i + 1, parameters[i], types[i]);
}
return preparedStatement.executeUpdate();
}
}
public ResultSet executeQuery(Connection connection) throws SQLException {
return connection.createStatement().executeQuery(statement);
}
public int executePreparedBatch(Connection connection, Map<UUID, PlayerData> players) throws SQLException {
try (PreparedStatement preparedStatement = connection.prepareStatement(statement)) {
for (Map.Entry<UUID, PlayerData> entry : players.entrySet()) {
PlayerData player = entry.getValue();
preparedStatement.setInt(1, entry.getValue().getId());
preparedStatement.setString(2, entry.getKey().toString());
preparedStatement.setString(3, player.getLang());
preparedStatement.setBoolean(4, player.isSoundEnabled());
preparedStatement.setString(5, player.getFavorites().stream().map(String::valueOf).collect(Collectors.joining("|")));
preparedStatement.addBatch();
}
int[] results = preparedStatement.executeBatch();
return results.length;
}
}
}

Datei anzeigen

@ -1,33 +1,117 @@
package tsp.headdb.core.storage; package tsp.headdb.core.storage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tsp.headdb.HeadDB; import tsp.headdb.HeadDB;
import tsp.helperlite.Schedulers; import tsp.headdb.core.player.PlayerData;
import java.io.File; import java.io.IOException;
import java.util.concurrent.Executor; import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author TheSilentPro (Silent)
*/
public class Storage { public class Storage {
private final Executor executor; private static final Logger LOGGER = LoggerFactory.getLogger(Storage.class);
private final PlayerStorage playerStorage; private final ExecutorService executor;
private Connection connection;
public Storage() { public Storage() {
executor = Schedulers.async(); this.executor = Executors.newSingleThreadExecutor(r -> new Thread(r, "HeadDB Storage"));
validateDataDirectory();
playerStorage = new PlayerStorage(HeadDB.getInstance(), this);
} }
public PlayerStorage getPlayerStorage() { public Storage init() {
return playerStorage; createTables();
return this;
} }
public Executor getExecutor() { public CompletableFuture<ResultSet> selectPlayers() {
return executor; return connect().thenComposeAsync(conn -> {
try {
LOGGER.debug("Fetching players from database...");
return CompletableFuture.completedFuture(SQLStatement.SELECT_PLAYERS.executeQuery(conn));
} catch (SQLException ex) {
LOGGER.error("Failed to select players!", ex);
return CompletableFuture.failedFuture(ex);
} finally {
disconnect();
}
}, executor);
} }
private void validateDataDirectory() { public CompletableFuture<Void> insertPlayer(UUID id, String lang, boolean soundEnabled, String favorites) {
//noinspection ResultOfMethodCallIgnored return connect().thenAcceptAsync(conn -> {
new File(HeadDB.getInstance().getDataFolder(), "data").mkdir(); try {
int res = SQLStatement.INSERT_PLAYER.executeUpdate(conn);
LOGGER.debug("[INSERT]: RESPONSE={} | ID={} | LANG={} | sound={} | FAVORITES={}", res, id, lang, soundEnabled, favorites.substring(0, 16));
} catch (SQLException | IOException ex) {
LOGGER.error("Failed to create tables!", ex);
} finally {
disconnect();
}
}, executor);
} }
public CompletableFuture<Void> insertAllPlayers(Map<UUID, PlayerData> players) {
return connect().thenAcceptAsync(conn -> {
try {
int inserted = SQLStatement.INSERT_PLAYER.executePreparedBatch(conn, players);
LOGGER.debug("[INSERT | ALL]: IN={} | OUT={}", players.size(), inserted);
} catch (SQLException ex) {
LOGGER.error("Failed to insert all players!", ex);
} finally {
disconnect();
}
}, executor);
}
public CompletableFuture<Void> createTables() {
return connect().thenAcceptAsync(conn -> {
try {
int res = SQLStatement.TABLES_CREATE.executeUpdate(conn);
LOGGER.debug("[CREATE]: RES={}", res);
} catch (SQLException | IOException ex) {
LOGGER.error("Failed to create tables!", ex);
} finally {
disconnect();
}
}, executor);
}
public CompletableFuture<Connection> connect() {
return CompletableFuture.supplyAsync(() -> {
try {
this.connection = DriverManager.getConnection("jdbc:sqlite:" + HeadDB.getInstance().getDataFolder() + "/data.db");
LOGGER.debug("SQL connection established!");
return this.connection;
} catch (SQLException ex) {
LOGGER.error("Failed to connect to database!", ex);
throw new CompletionException("Could not connect to database!", ex);
}
}, executor);
}
public CompletableFuture<Void> disconnect() {
return CompletableFuture.runAsync(() -> {
try {
if (this.connection != null && !this.connection.isClosed()) {
this.connection.close();
LOGGER.debug("SQL connection terminated!");
}
} catch (SQLException ex) {
LOGGER.error("Failed to close connection!", ex);
throw new CompletionException("Could not close database connection", ex);
}
}, executor);
}
} }

Datei anzeigen

@ -1,47 +0,0 @@
package tsp.headdb.core.task;
import org.bukkit.Bukkit;
import tsp.headdb.HeadDB;
import tsp.headdb.core.api.HeadAPI;
import tsp.headdb.core.api.events.AsyncHeadsFetchedEvent;
import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.head.Head;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
public class UpdateTask implements Runnable {
@Override
public void run() {
HeadAPI.getDatabase().update().thenAcceptAsync(result -> {
HeadDB instance = HeadDB.getInstance();
String providerName = HeadAPI.getDatabase().getRequester().getProvider().name();
instance.getLog().debug("Fetched: " + getHeadsCount(result.heads()) + " Heads | Provider: " + providerName + " | Time: " + result.elapsed() + "ms (" + TimeUnit.MILLISECONDS.toSeconds(result.elapsed()) + "s)");
Bukkit.getPluginManager().callEvent(
new AsyncHeadsFetchedEvent(
result.heads(),
providerName,
result.elapsed()));
instance.getStorage().getPlayerStorage().backup();
instance.getUpdateTask().ifPresentOrElse(task -> {
instance.getLog().debug("UpdateTask completed! Times ran: " + task.getTimesRan());
}, () -> instance.getLog().debug("Initial UpdateTask completed!"));
});
}
private int getHeadsCount(Map<Category, List<Head>> heads) {
int n = 0;
for (List<Head> list : heads.values()) {
for (int i = 0; i < list.size(); i++) {
n++;
}
}
return n;
}
}

Datei anzeigen

@ -1,12 +0,0 @@
package tsp.headdb.core.util;
import tsp.nexuslib.logger.NexusLogger;
@SuppressWarnings("unused")
public class HeadDBLogger extends NexusLogger {
public HeadDBLogger(boolean debug) {
super("HeadDB", debug);
}
}

Datei anzeigen

@ -0,0 +1,463 @@
package tsp.headdb.core.util;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.command.RemoteConsoleCommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import me.clip.placeholderapi.PlaceholderAPI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tsp.headdb.HeadDB;
/**
* Translatable Localization Utility Class.
* <a href="https://gist.github.com/TheSilentPro/ce7d9ee24a8136f7aa486738a4b85e46">Gist (Source)</a>
*
* @author TheSilentPro (Silent)
*/
@ParametersAreNonnullByDefault
public class Localization {
private static final Logger LOGGER = LoggerFactory.getLogger(Localization.class);
private final JavaPlugin plugin;
private final String messagesPath;
private final File container;
private final String defaultLanguage;
private final Map<String, FileConfiguration> data; // Lang, Data
private String consoleLanguage;
private boolean colorize;
@SuppressWarnings("RegExpRedundantEscape")
private final Pattern ARGS_PATTERN = Pattern.compile("(\\{\\$arg(\\d+)\\})", Pattern.CASE_INSENSITIVE); // (\{\$arg(\d+)\})
/**
* Creates a new {@link Localization} instance.
*
* @param plugin The plugin associated with this instance.
* @param messagesPath The path for the messages in the /resources directory.
* @param container The container for the messages. Default: {pluginDataFolder}/messages
* @param defaultLanguage The default language file in your /resources directory. Default: en
*/
public Localization(JavaPlugin plugin, String messagesPath, @Nullable File container, @Nullable String defaultLanguage) {
notNull(plugin, "Plugin can not be null!");
notNull(messagesPath, "Messages path can not be null!");
this.plugin = plugin;
this.messagesPath = messagesPath;
this.container = container;
this.defaultLanguage = defaultLanguage;
//this.languages = new HashMap<>();
this.data = new HashMap<>();
this.consoleLanguage = defaultLanguage;
this.colorize = true;
}
/**
* Create a new {@link Localization} instance with default parameters.
* <bold>Notice: If you use this constructor make sure to FIRST create your {@link JavaPlugin#getDataFolder() plugins data folder} before calling the constructor!</bold>
*
* @param plugin The plugin associated with this instance.
* @param messagesPath The path for the messages in the /resources directory.
*/
public Localization(JavaPlugin plugin, String messagesPath) {
this(plugin, messagesPath, new File(plugin.getDataFolder() + "/messages"), "en");
}
// Non-Console
/**
* Send message to the receiver.
*
* @param uuid The receiver.
* @param message The message.
* @see #sendMessage(UUID, String)
*/
private void sendTranslatedMessage(UUID uuid, String message) {
notNull(uuid, "UUID can not be null!");
notNull(message, "Message can not be null!");
if (message.isEmpty()) {
return;
}
Entity receiver = Bukkit.getEntity(uuid);
if (receiver == null) {
//noinspection UnnecessaryToStringCall
throw new IllegalArgumentException("Invalid receiver with uuid: " + uuid.toString());
}
receiver.sendMessage(message);
}
/**
* Send a message to a receiver.
*
* @param receiver The receiver of the message.
* @param key The key of the message.
* @param function Optional: Function to apply to the message.
* @param args Optional: Arguments for replacing. Format: {$argX} where X can be any argument number starting from 0.
*/
public void sendMessage(UUID receiver, String key, @Nullable UnaryOperator<String> function, @Nullable String... args) {
notNull(receiver, "Receiver can not be null!");
notNull(key, "Key can not be null!");
getMessage(receiver, key).ifPresent(message -> {
if (args != null) {
for (String arg : args) {
if (arg != null) {
Matcher matcher = ARGS_PATTERN.matcher(message);
while (matcher.find()) {
message = matcher.replaceAll(args[Integer.parseInt(matcher.group(2))]);
}
}
}
}
// Apply function
message = function != null ? function.apply(message) : message;
sendTranslatedMessage(receiver, colorize ? ChatColor.translateAlternateColorCodes('&', message) : message);
});
}
public void sendMessage(UUID receiver, String key, @Nullable UnaryOperator<String> function) {
sendMessage(receiver, key, function, (String[]) null);
}
public void sendMessage(UUID receiver, String key, @Nullable String... args) {
sendMessage(receiver, key, null, args);
}
public void sendMessage(UUID receiver, String key) {
sendMessage(receiver, key, null, (String[]) null);
}
public void sendMessages(String key, UUID... receivers) {
for (UUID receiver : receivers) {
sendMessage(receiver, key);
}
}
/**
* Retrieve a message by the receiver's language and the key.
*
* @param uuid The receiver.
* @param key The message key.
* @return If present, the message, otherwise an empty {@link Optional}
*/
@NotNull
public Optional<String> getMessage(UUID uuid, String key) {
notNull(uuid, "UUID can not be null!");
notNull(key, "Key can not be null!");
FileConfiguration messages = data.get(HeadDB.getInstance().getPlayerDatabase().getOrCreate(uuid).getLang());
if (messages == null) {
return Optional.empty();
}
String message = messages.getString(key);
if (message == null) {
// Message not specified in language file, attempt to find it in the main one.
messages = data.get(defaultLanguage);
message = messages.getString(key);
}
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null && message != null) {
message = PlaceholderAPI.setPlaceholders(Bukkit.getOfflinePlayer(uuid), message);
}
return Optional.ofNullable(message);
}
// Console
private ConsoleMessageFunction logFunction = message -> Bukkit.getConsoleSender().sendMessage(message);
public void sendTranslatedConsoleMessage(String message) {
notNull(message, "Message can not be null!");
if (message.isEmpty()) {
return;
}
logFunction.logMessage(message);
}
public void sendConsoleMessage(String key, @Nullable UnaryOperator<String> function, @Nullable String... args) {
notNull(key, "Key can not be null!");
getConsoleMessage(key).ifPresent(message -> {
if (args != null) {
for (String arg : args) {
if (arg != null) {
Matcher matcher = ARGS_PATTERN.matcher(message);
while (matcher.find()) {
message = matcher.replaceAll(args[Integer.parseInt(matcher.group(2))]);
}
}
}
}
// Apply function
message = function != null ? function.apply(message) : message;
sendTranslatedConsoleMessage(colorize ? ChatColor.translateAlternateColorCodes('&', message) : message);
});
}
public void sendConsoleMessage(String key, @Nullable UnaryOperator<String> function) {
sendConsoleMessage(key, function, (String[]) null);
}
public void sendConsoleMessage(String key, @Nullable String... args) {
sendConsoleMessage(key, null, args);
}
public void sendConsoleMessage(String key) {
sendConsoleMessage(key, null, (String[]) null);
}
public Optional<String> getConsoleMessage(String key) {
notNull(key, "Key can not be null!");
FileConfiguration messages = data.get(consoleLanguage != null ? consoleLanguage : defaultLanguage);
if (messages == null) {
return Optional.empty();
}
String message = messages.getString(key);
if (message == null) {
// Message not specified in language file, attempt to find it in the main one.
messages = data.get(defaultLanguage);
message = messages.getString(key);
}
return Optional.ofNullable(message);
}
public interface ConsoleMessageFunction {
void logMessage(String message);
}
public void setLogFunction(ConsoleMessageFunction logFunction) {
this.logFunction = logFunction;
}
// Auto Resolve
public void sendMessage(CommandSender receiver, String key, @Nullable UnaryOperator<String> function, @Nullable String... args) {
if (receiver instanceof ConsoleCommandSender || receiver instanceof RemoteConsoleCommandSender) {
sendConsoleMessage(key, function, args);
} else if (receiver instanceof Player player) {
sendMessage(player.getUniqueId(), key, function, args);
}
}
public void sendMessage(CommandSender receiver, String key, @Nullable UnaryOperator<String> function) {
sendMessage(receiver, key, function, (String[]) null);
}
public void sendMessage(CommandSender receiver, String key) {
sendMessage(receiver, key, null);
}
public void sendMessage(String key, CommandSender... receivers) {
for (CommandSender receiver : receivers) {
sendMessage(receiver, key);
}
}
// Loader
/**
* Load all language files.
*
* @return Number of files loaded.
*/
public int load() {
File[] files = container.listFiles();
if (files == null) {
throw new NullPointerException("No files in container!");
}
int count = 0;
for (File file : files) {
String name = file.getName();
// If the file is not of YAML type, ignore it.
if (name.endsWith(".yml") || name.endsWith(".yaml")) {
data.put(name.substring(0, name.lastIndexOf(".")), YamlConfiguration.loadConfiguration(file));
count++;
}
}
return count;
}
/**
* Create the default language files from your /resources folder.
*
* @throws URISyntaxException URI Syntax Error
* @throws IOException Error
*/
public void createDefaults() throws URISyntaxException, IOException {
URL url = plugin.getClass().getClassLoader().getResource(messagesPath);
if (url == null) {
throw new NullPointerException("No resource with path: " + messagesPath);
}
if (!container.exists()) {
//noinspection ResultOfMethodCallIgnored
container.mkdir();
}
// This is required otherwise Files.walk will throw FileSystem Exception.
String[] array = url.toURI().toString().split("!");
FileSystem fs = FileSystems.newFileSystem(URI.create(array[0]), new HashMap<>());
//noinspection resource
Files.walk(Paths.get(url.toURI()))
.forEach(path -> {
try {
File out = new File(container.getAbsolutePath() + "/" + path.getFileName());
// If file is not of YAML type or if it already exists, ignore it.
if ((out.getName().endsWith(".yml") || out.getName().endsWith(".yaml")) && !out.exists()) {
Files.copy(path, out.toPath());
}
} catch (IOException ex) {
LOGGER.error("Could not create file!", ex);
}
});
fs.close();
}
/**
* Retrieve the {@link JavaPlugin plugin} associated with this {@link Localization}.
*
* @return The plugin.
*/
@NotNull
public JavaPlugin getPlugin() {
return plugin;
}
/**
* Retrieve the {@link File container} for the language files.
*
* @return The container.
*/
@NotNull
public File getContainer() {
return container;
}
/**
* Retrieve the message file path.
*
* @return The message file path.
*/
@NotNull
public String getMessagesPath() {
return messagesPath;
}
/**
* Retrieve a {@link Map} containing all language/message data.
*
* @return The language/message data. Format: Language, Messages
*/
public Map<String, FileConfiguration> getData() {
return data;
}
/**
* Retrieve the default language.
*
* @return The default language.
*/
@NotNull
public String getDefaultLanguage() {
return defaultLanguage;
}
/**
* Retrieve whether messages are colorized before sending.
*
* @return Colorize option value
*/
public boolean isColorize() {
return colorize;
}
/**
* Set whether messages should be colorized before sending.
*
* @param colorize Colorize option
*/
public void setColorize(boolean colorize) {
this.colorize = colorize;
}
/**
* Retrieve the language for the console.
*
* @return The consoles' language. Default: Default Language
*/
@NotNull
public String getConsoleLanguage() {
return consoleLanguage;
}
/**
* Set the language for the console.
*
* @param consoleLanguage The consoles' language.
*/
public void setConsoleLanguage(String consoleLanguage) {
notNull(consoleLanguage, "Language can not be null!");
this.consoleLanguage = consoleLanguage;
}
/**
* Just a not-null validator.
*
* @param object The object to validate.
* @param message The message sent if validation fails.
* @param <T> Type
*/
private <T> void notNull(T object, String message) {
//noinspection ConstantConditions
if (object == null) throw new NullPointerException(message);
}
}

Datei anzeigen

@ -0,0 +1,303 @@
package tsp.headdb.core.util;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import tsp.headdb.HeadDB;
import tsp.headdb.api.model.Category;
import tsp.headdb.api.HeadAPI;
import tsp.headdb.api.model.Head;
import tsp.headdb.api.model.LocalHead;
import tsp.headdb.core.player.PlayerData;
import tsp.invlib.gui.GUI;
import tsp.invlib.gui.SimpleGUI;
import tsp.invlib.gui.button.SimpleButton;
import tsp.invlib.gui.page.PageBuilder;
import java.util.*;
/**
* @author TheSilentPro (Silent)
*/
@SuppressWarnings("DataFlowIssue")
public final class MenuSetup {
public static final GUI mainGui = new SimpleGUI();
public static final GUI allGui = new SimpleGUI();
public static final Map<Category, GUI> categoryGuis = new HashMap<>();
private static final Localization localization = HeadDB.getInstance().getLocalization();
private static final ItemStack FILLER;
private static final boolean categoryPermission = HeadDB.getInstance().getCfg().shouldRequireCategoryPermission();
static {
FILLER = new ItemStack(Material.BLACK_STAINED_GLASS_PANE);
ItemMeta fillerMeta = FILLER.getItemMeta();
//noinspection DataFlowIssue
fillerMeta.setDisplayName(" ");
FILLER.setItemMeta(fillerMeta);
// Prebuild main page
PageBuilder builder = new PageBuilder(mainGui)
.fill(new SimpleButton(FILLER, e -> e.setCancelled(true)))
.name(ChatColor.RED + "HeadDB" + ChatColor.DARK_GRAY + " (" + HeadAPI.getTotalHeads() + ")")
.onClick(event -> {
// Compare material type instead of ItemStack because performance.
if (event.getCurrentItem().getType() != FILLER.getType()) {
Sounds.PAGE_OPEN.play((Player) event.getWhoClicked());
}
})
.preventClick();
// Settings button
// Set all heads button in main page
ItemStack allItem = new ItemStack(Material.BOOK);
ItemMeta meta = allItem.getItemMeta();
meta.setItemName(ChatColor.GOLD + "All Heads");
meta.setLore(Collections.singletonList(ChatColor.GRAY + "Total heads » " + ChatColor.GOLD + HeadAPI.getTotalHeads()));
allItem.setItemMeta(meta);
builder.button(40, new SimpleButton(allItem, e -> {
Player player = (Player) e.getWhoClicked();
if (!player.hasPermission("headdb.category.all") && !player.hasPermission("headdb.category.*")) {
localization.sendMessage(player, "noPermission");
return;
}
if (e.isLeftClick()) {
allGui.open(player);
} else if (e.isRightClick()) {
// todo: input specific page
}
}));
// Set category buttons in main page
for (Category category : Category.VALUES) {
builder.button(category.getDefaultSlot(), new SimpleButton(category.getDisplayItem(), e -> {
e.setCancelled(true);
if (e.isLeftClick()) {
categoryGuis.get(category).open((Player) e.getWhoClicked());
} else if (e.isRightClick()) {
// todo: input specific page
}
}));
}
// Set Favorites button in main page
ItemStack favoritesHeadsItem = HeadAPI.getHeadByTexture("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzZmZGQ0YjEzZDU0ZjZjOTFkZDVmYTc2NWVjOTNkZDk0NThiMTlmOGFhMzRlZWI1YzgwZjQ1NWIxMTlmMjc4In19fQ==")
.join()
.map(Head::getItem) // Get the ItemStack if the Head is present
.orElse(new ItemStack(Material.BOOK)); // Default to BOOK if not present
ItemMeta favoriteHeadsItemMeta = favoritesHeadsItem.getItemMeta();
favoriteHeadsItemMeta.setDisplayName(ChatColor.GOLD + "Favorites");
favoriteHeadsItemMeta.setLore(null);
favoritesHeadsItem.setItemMeta(favoriteHeadsItemMeta);
builder.button(39, new SimpleButton(favoritesHeadsItem, e -> {
Player player = (Player) e.getWhoClicked();
if (!player.hasPermission("headdb.favorites")) {
localization.sendMessage(player, "noAccessFavorites");
return;
}
favorites(player, 0);
}));
// Set local heads button in main page
ItemStack localHeadsItem = new ItemStack(Material.COMPASS);
ItemMeta localHeadsItemMeta = localHeadsItem.getItemMeta();
//noinspection DataFlowIssue
localHeadsItemMeta.setDisplayName(ChatColor.GOLD + "Local Heads");
localHeadsItem.setItemMeta(localHeadsItemMeta);
builder.button(41, new SimpleButton(localHeadsItem, e -> {
// Local heads must be calculated on every opening since new players can join at any time.
List<LocalHead> localHeadsList = new ArrayList<>(HeadAPI.getLocalHeads(e.getWhoClicked().hasPermission("headdb.admin")).join()); // Convert Set to List for indexed access
GUI localGui = new SimpleGUI();
Player mainPlayer = (Player) e.getWhoClicked();
for (int i = 0; i < localHeadsList.size(); i += 45) {
int end = Math.min(i + 45, localHeadsList.size());
List<LocalHead> section = localHeadsList.subList(i, end); // Get the sublist for the current page
PaginationBuilder localPageBuilder = new PaginationBuilder(localGui)
.parentGui(mainGui)
.name(ChatColor.RED + "Local Heads" + ChatColor.DARK_GRAY + " (" + HeadAPI.getLocalHeads().join().size() + ")");
// Iterate over the heads in the current section and add them to the inventory
for (int j = 0; j < section.size(); j++) {
LocalHead localHead = section.get(j);
localPageBuilder.button(j, new SimpleButton(localHead.getItem(), ice -> {
ice.setCancelled(true);
Player player = (Player) ice.getWhoClicked();
if (categoryPermission && !player.hasPermission("headdb.category.local.*") && !player.hasPermission("headdb.category.local." + localHead.getUniqueId())) {
localization.sendMessage(player, "noPermission");
return;
}
handleClick(player, localHead, ice);
}));
}
// Build the page and add it to the local GUI
localGui.addPage(localPageBuilder.build());
localGui.open(mainPlayer);
}
}));
mainGui.addPage(builder.build());
prebuildCategoryGuis();
}
private static void favorites(Player player, int page) {
Set<Head> favorites = HeadAPI.getFavoriteHeads(player.getUniqueId()).join();
if (!favorites.isEmpty()) {
// Build favorites GUI
GUI favoritesGui = new SimpleGUI();
List<Head> favoriteList = new ArrayList<>(favorites); // Copy to list for consistent indexing
for (int i = 0; i < favoriteList.size(); i += 45) {
int end = Math.min(i + 45, favoriteList.size());
List<Head> section = favoriteList.subList(i, end);
PaginationBuilder favoritesPageBuilder = new PaginationBuilder(favoritesGui)
.parentGui(mainGui)
.name(ChatColor.GOLD + "Favorites " + ChatColor.DARK_GRAY + "(" + favoriteList.size() + ")");
for (int j = 0; j < section.size(); j++) {
Head head = section.get(j);
favoritesPageBuilder.button(j, new SimpleButton(head.getItem(), ice -> {
handleClick(player, head, ice);
// Update favorites after removing the head
Set<Head> updatedFavorites = HeadAPI.getFavoriteHeads(player.getUniqueId()).join();
if (!updatedFavorites.isEmpty()) {
favorites(player, page); // Refresh the GUI
} else {
mainGui.open(player);
}
}));
}
favoritesGui.addPage(favoritesPageBuilder.build());
}
favoritesGui.open(player, !favoritesGui.getPages().isEmpty() ? page : 0);
} else {
localization.sendMessage(player, "noFavorites");
Sounds.FAIL.play(player);
}
}
public static void prebuildCategoryGuis() {
// Prebuild category guis
for (Category category : Category.VALUES) {
List<Head> heads = HeadAPI.getHeads().stream().filter(head -> head.getCategory().orElse("N/A").equalsIgnoreCase(category.getName())).toList();
GUI categoryGui = new SimpleGUI();
// Iterate over the heads, chunking them into pages
for (int i = 0; i < heads.size(); i += 45) {
int end = Math.min(i + 45, heads.size());
List<Head> section = heads.subList(i, end);
PaginationBuilder categoryPageBuilder = new PaginationBuilder(categoryGui)
.parentGui(mainGui)
.name(ChatColor.GOLD + category.getDisplayName() + ChatColor.DARK_GRAY + " (" + heads.size() + ")");
// Iterate over the heads in the current section and add them to the inventory
for (int j = 0; j < section.size(); j++) {
Head head = section.get(j);
categoryPageBuilder.button(j, new SimpleButton(head.getItem(), e -> {
Player player = (Player) e.getWhoClicked();
if (categoryPermission && !player.hasPermission("headdb.category." + category.getName()) && !player.hasPermission("headdb.category.*")) {
localization.sendMessage(player, "noPermission");
return;
}
handleClick(player, head, e);
}));
}
// Build the page and add it to the category GUI
categoryGui.addPage(categoryPageBuilder.build());
}
categoryGuis.put(category, categoryGui);
}
// Prebuild ALL gui.
List<Head> heads = HeadAPI.getHeads();
for (int i = 0; i < heads.size(); i += 45) {
int end = Math.min(i + 45, heads.size());
List<Head> section = heads.subList(i, end);
PaginationBuilder allPageBuilder = new PaginationBuilder(allGui)
.parentGui(mainGui)
.name(ChatColor.GOLD + "All Heads" + ChatColor.DARK_GRAY + " (" + heads.size() + ")");
// Iterate over the heads in the current section and add them to the inventory
for (int j = 0; j < section.size(); j++) {
Head head = section.get(j);
// This is the slot for the current head, relative to the page (0-35)
allPageBuilder.button(j, new SimpleButton(head.getItem(), e -> {
Player player = (Player) e.getWhoClicked();
if (categoryPermission && !player.hasPermission("headdb.category.all") && !player.hasPermission("headdb.category.*")) {
localization.sendMessage(player, "noPermission");
return;
}
handleClick(player, head, e);
}));
}
// Build the page and add it to the category GUI
allGui.addPage(allPageBuilder.build());
}
}
public static void openSearch(List<Head> heads, String id, Player player) {
GUI gui = new SimpleGUI();
for (int i = 0; i < heads.size(); i += 45) {
int end = Math.min(i + 45, heads.size());
List<Head> section = heads.subList(i, end); // Get the sublist for the current page
PaginationBuilder pageBuilder = new PaginationBuilder(gui)
.parentGui(mainGui)
.name(ChatColor.DARK_GRAY + "Search » " + ChatColor.GOLD + id);
// Iterate over the heads in the current section and add them to the inventory
for (int j = 0; j < section.size(); j++) {
Head head = section.get(j);
pageBuilder.button(j, new SimpleButton(head.getItem(), ice -> {
handleClick(player, head, ice);
}));
}
// Build the page and add it to the local GUI
gui.addPage(pageBuilder.build());
gui.open(player);
}
}
private static void handleClick(Player player, Head head, InventoryClickEvent ice) {
if (ice.isLeftClick()) {
Utils.purchaseHead(player, head, ice.isShiftClick() ? 64 : 1); // Give the player the head item
} else if (ice.isRightClick()) {
PlayerData playerData = HeadDB.getInstance().getPlayerDatabase().getOrCreate(player.getUniqueId());
if (playerData.getFavorites().contains(head.getId())) {
playerData.getFavorites().remove(head.getId());
localization.sendMessage(player, "removedFavorite", msg -> msg.replace("%name%", head.getName()));
Sounds.FAVORITE_REMOVE.play(player);
} else {
playerData.getFavorites().add(head.getId());
localization.sendMessage(player, "addedFavorite", msg -> msg.replace("%name%", head.getName()));
Sounds.FAVORITE.play(player);
}
}
}
}

Datei anzeigen

@ -0,0 +1,47 @@
package tsp.headdb.core.util;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.InventoryClickEvent;
import tsp.invlib.gui.GUI;
import tsp.invlib.gui.button.control.ControlButton;
import tsp.invlib.gui.page.Page;
import tsp.invlib.gui.page.PageBuilder;
import java.util.function.BiConsumer;
/**
* @author TheSilentPro (Silent)
*/
public class PaginationBuilder extends PageBuilder {
public PaginationBuilder(GUI gui) {
super(gui);
}
@Override
public PaginationBuilder parentGui(GUI gui) {
super.parentGui(gui);
return this;
}
@Override
public PaginationBuilder name(String name) {
super.name(name);
return this;
}
@Override
public PaginationBuilder onControlClick(BiConsumer<ControlButton, InventoryClickEvent> event) {
super.onControlClick(event);
return this;
}
@Override
public Page build() {
preventClick();
includeControlButtons();
onControlClick((button, event) -> Sounds.PAGE_CHANGE.play((Player) event.getWhoClicked()));
return super.build();
}
}

Datei anzeigen

@ -0,0 +1,36 @@
package tsp.headdb.core.util;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
/**
* @author TheSilentPro (Silent)
*/
public enum Sounds {
FAIL(Sound.BLOCK_ANVIL_LAND, 0.3f, 0.5f),
SUCCESS(Sound.ENTITY_PLAYER_LEVELUP, 2f),
PAGE_CHANGE(Sound.BLOCK_LEVER_CLICK, 0.5f, 1f),
PAGE_OPEN(Sound.ENTITY_BAT_TAKEOFF, 1f),
FAVORITE(Sound.ENTITY_ARROW_HIT_PLAYER, 1f),
FAVORITE_REMOVE(Sound.ENTITY_ARROW_HIT_PLAYER, 2f);
private final Sound sound;
private final float volume;
private final float pitch;
Sounds(Sound sound, float volume, float pitch) {
this.sound = sound;
this.volume = volume;
this.pitch = pitch;
}
Sounds(Sound sound, float pitch) {
this(sound, 1f, pitch);
}
public void play(Player player) {
player.playSound(player, sound, volume, pitch);
}
}

Datei anzeigen

@ -1,357 +1,198 @@
package tsp.headdb.core.util; package tsp.headdb.core.util;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import me.clip.placeholderapi.PlaceholderAPI; import me.clip.placeholderapi.PlaceholderAPI;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta; import org.bukkit.inventory.meta.SkullMeta;
import org.bukkit.profile.PlayerProfile; import org.bukkit.profile.PlayerProfile;
import org.bukkit.profile.PlayerTextures; import org.bukkit.profile.PlayerTextures;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tsp.headdb.HeadDB; import tsp.headdb.HeadDB;
import tsp.headdb.core.api.HeadAPI; import tsp.headdb.core.config.ConfigData;
import tsp.headdb.core.economy.BasicEconomyProvider; import tsp.headdb.core.economy.EconomyProvider;
import tsp.headdb.core.hook.Hooks; import tsp.headdb.api.model.Head;
import tsp.headdb.implementation.category.Category; import tsp.headdb.api.model.LocalHead;
import tsp.headdb.implementation.head.Head;
import tsp.helperlite.scheduler.promise.Promise;
import tsp.nexuslib.builder.ItemBuilder;
import tsp.nexuslib.inventory.Button;
import tsp.nexuslib.inventory.PagedPane;
import tsp.nexuslib.inventory.Pane;
import tsp.nexuslib.localization.TranslatableLocalization;
import tsp.nexuslib.server.ServerVersion;
import tsp.nexuslib.util.StringUtils;
import tsp.nexuslib.util.Validate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URI;
import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.*;
import java.util.concurrent.Executor;
import java.util.regex.Pattern;
/**
* @author TheSilentPro (Silent)
*/
public class Utils { public class Utils {
private static final HeadDB instance = HeadDB.getInstance(); private static final Logger LOGGER = LoggerFactory.getLogger(Utils.class);
private static Properties properties = null; private static final Pattern HEAD_PATTERN = Pattern.compile("[^a-zA-Z0-9]");
public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("dd-MM-yyyy");
public static final Executor SYNC = r -> Bukkit.getScheduler().runTask(HeadDB.getInstance(), r);
private static final ConfigData config = HeadDB.getInstance().getCfg();
public static Optional<String> getVersion() { @SuppressWarnings("DataFlowIssue")
if (properties == null) { public static ItemStack asItem(Head head) {
InputStream is = instance.getResource("build.properties"); ItemStack item = new ItemStack(Material.PLAYER_HEAD);
if (is == null) { ItemMeta meta = item.getItemMeta();
return Optional.empty(); meta.setDisplayName(ChatColor.GOLD + head.getName());
List<String> lore = new ArrayList<>();
lore.add(ChatColor.GRAY + "ID » " + ChatColor.GOLD + head.getId());
head.getTags().ifPresent(tags -> lore.add(ChatColor.GRAY + "Tags » " + ChatColor.GOLD + String.join(", ", tags)));
if (config.shouldIncludeMoreInfo()) {
head.getCategory().ifPresent(category -> {
if (category.isEmpty()) {
lore.add(ChatColor.GRAY + "Category » " + ChatColor.GOLD + category);
}
});
head.getContributors().ifPresent(contributors -> {
if (contributors.length != 0) {
lore.add(ChatColor.GRAY + "Contributors » " + ChatColor.GOLD + String.join(", ", contributors));
}
});
head.getCollections().ifPresent(collections -> {
if (collections.length != 0) {
lore.add(ChatColor.GRAY + "Collections » " + ChatColor.GOLD + String.join(", ", collections));
}
});
head.getPublishDate().ifPresent(date -> lore.add(ChatColor.GRAY + "Published » " + ChatColor.GOLD + date));
} }
if (HeadDB.getInstance().getEconomyProvider() != null) {
lore.add(" ");
lore.add(ChatColor.GRAY + "Cost (x1) » " + ChatColor.GOLD + getHeadCost(head) + ChatColor.GRAY + " (Left-Click)");
lore.add(ChatColor.GRAY + "Cost (x64) » " + ChatColor.GOLD + (getHeadCost(head) * 64) + ChatColor.GRAY + " (Shift-Left-Click)");
}
meta.setLore(lore);
item.setItemMeta(meta);
PlayerProfile profile;
try { try {
properties = new Properties(); profile = Bukkit.createPlayerProfile(null, head.getName());
properties.load(is); } catch (IllegalArgumentException ex) {
} catch (IOException ex) { // Head may contain special characters(@,!,<,>) that are not allowed in a PlayerProfile.
instance.getLog().debug("Failed to load build properties: " + ex.getMessage()); // Additionally, spaces are also removed as the profile name should not be visible to players.
return Optional.empty(); String name = HEAD_PATTERN.matcher(head.getName().trim()).replaceAll("");
if (name.length() > 16) { // Profile names can not be longer than 16 characters
name = name.substring(0, 16);
} }
profile = Bukkit.createPlayerProfile(null, name);
} }
return Optional.ofNullable(properties.getProperty("version")); PlayerTextures textures = profile.getTextures();
} String url = new String(Base64.getDecoder().decode(head.getTexture().orElseThrow(() -> new IllegalArgumentException("Head texture must not be null!"))));
public static String toString(Collection<String> set) {
String[] array = set.toArray(new String[0]);
StringBuilder builder = new StringBuilder();
for (int i = 0; i < array.length; i++) {
builder.append(array[i]);
if (i < array.length - 1) {
builder.append(",");
}
}
return builder.toString();
}
public static Optional<UUID> validateUniqueId(@Nonnull String raw) {
try { try {
return Optional.of(UUID.fromString(raw)); textures.setSkin(URI.create(url.substring("{\"textures\":{\"SKIN\":{\"url\":\"".length(), url.length() - "\"}}}".length())).toURL());
} catch (IllegalArgumentException ignored) { } catch (MalformedURLException ex) {
return Optional.empty(); throw new RuntimeException(ex);
} }
profile.setTextures(textures);
if (meta instanceof SkullMeta skullMeta) {
skullMeta.setOwnerProfile(profile);
item.setItemMeta(skullMeta);
}
return item;
} }
@ParametersAreNonnullByDefault public static void purchaseHead(Player player, Head head, int amount) {
public static String translateTitle(String raw, int size, String category, @Nullable String query) { EconomyProvider economyProvider = HeadDB.getInstance().getEconomyProvider();
return StringUtils.colorize(raw) if (economyProvider == null) {
.replace("%size%", String.valueOf(size)) ItemStack item = head.getItem().clone();
.replace("%category%", category) item.setAmount(amount);
.replace("%query%", (query != null ? query : "%query%")); if (!HeadDB.getInstance().getCfg().shouldIncludeLore()) {
}
@ParametersAreNonnullByDefault
public static String translateTitle(String raw, int size, String category) {
return translateTitle(raw, size, category, null);
}
public static boolean matches(String provided, String query) {
provided = ChatColor.stripColor(provided.toLowerCase(Locale.ROOT));
query = query.toLowerCase(Locale.ROOT);
return provided.equals(query)
|| provided.startsWith(query)
|| provided.contains(query);
//|| provided.endsWith(query);
}
public static void fill(@Nonnull Pane pane, @Nullable ItemStack item) {
Validate.notNull(pane, "Pane can not be null!");
if (item == null) {
item = new ItemStack(Material.BLACK_STAINED_GLASS_PANE);
ItemMeta meta = item.getItemMeta(); ItemMeta meta = item.getItemMeta();
//noinspection DataFlowIssue //noinspection DataFlowIssue
meta.setDisplayName(""); meta.setLore(null);
item.setItemMeta(meta); item.setItemMeta(meta);
} }
for (int i = 0; i < pane.getInventory().getSize(); i++) {
ItemStack current = pane.getInventory().getItem(i);
if (current == null || current.getType().isAir()) {
pane.setButton(i, new Button(item, e -> e.setCancelled(true)));
}
}
}
@SuppressWarnings("SpellCheckingInspection")
public static PagedPane createPaged(Player player, String title) {
PagedPane main = new PagedPane(4, 6, title);
HeadAPI.getHeadByTexture("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODY1MmUyYjkzNmNhODAyNmJkMjg2NTFkN2M5ZjI4MTlkMmU5MjM2OTc3MzRkMThkZmRiMTM1NTBmOGZkYWQ1ZiJ9fX0=").ifPresent(head -> main.setBackItem(head.getItem(player.getUniqueId())));
HeadAPI.getHeadByTexture("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2Q5MWY1MTI2NmVkZGM2MjA3ZjEyYWU4ZDdhNDljNWRiMDQxNWFkYTA0ZGFiOTJiYjc2ODZhZmRiMTdmNGQ0ZSJ9fX0=").ifPresent(head -> main.setCurrentItem(head.getItem(player.getUniqueId())));
HeadAPI.getHeadByTexture("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmEzYjhmNjgxZGFhZDhiZjQzNmNhZThkYTNmZTgxMzFmNjJhMTYyYWI4MWFmNjM5YzNlMDY0NGFhNmFiYWMyZiJ9fX0=").ifPresent(head -> main.setNextItem(head.getItem(player.getUniqueId())));
main.setControlCurrent(new Button(main.getCurrentItem(), e -> Bukkit.dispatchCommand(player, "hdb")));
return main;
}
public static void openFavoritesMenu(Player player) {
try (Promise<List<Head>> promise = HeadAPI.getFavoriteHeads(player.getUniqueId())) {
promise.thenAcceptSync(heads -> {
PagedPane main = Utils.createPaged(player, Utils.translateTitle(HeadDB.getInstance().getLocalization().getMessage(player.getUniqueId(), "menu.main.favorites.name").orElse("Favorites"), heads.size(), "Favorites"));
for (Head head : heads) {
main.addButton(new Button(head.getItem(player.getUniqueId()), fe -> {
if (!player.hasPermission("headdb.favorites")) {
HeadDB.getInstance().getLocalization().sendMessage(player, "noAccessFavorites");
return;
}
if (fe.isLeftClick()) {
int amount = 1;
if (fe.isShiftClick()) {
amount = 64;
}
Utils.purchase(player, head, amount);
} else if (fe.isRightClick()) {
HeadDB.getInstance().getStorage().getPlayerStorage().removeFavorite(player.getUniqueId(), head.getTexture());
HeadDB.getInstance().getLocalization().sendMessage(player, "removedFavorite", msg -> msg.replace("%name%", head.getName()));
openFavoritesMenu(player);
}
}));
}
main.open(player);
});
} catch (Exception ex) {
ex.printStackTrace();
}
}
@ParametersAreNonnullByDefault
public static void addHeads(Player player, @Nullable Category category, PagedPane pane, Collection<Head> heads) {
for (Head head : heads) {
ItemStack item = head.getItem(player.getUniqueId());
pane.addButton(new Button(item, e -> {
e.setCancelled(true);
if (category != null && instance.getConfig().getBoolean("requireCategoryPermission") && !player.hasPermission("headdb.category." + category.getName())) {
instance.getLocalization().sendMessage(player.getUniqueId(), "noPermission");
return;
}
if (e.isLeftClick()) {
int amount = 1;
if (e.isShiftClick()) {
amount = 64;
}
purchase(player, head, amount);
} else if (e.isRightClick()) {
if (player.hasPermission("headdb.favorites")) {
HeadDB.getInstance().getStorage().getPlayerStorage().addFavorite(player.getUniqueId(), head.getTexture());
HeadDB.getInstance().getLocalization().sendMessage(player, "addedFavorite", msg -> msg.replace("%name%", head.getName()));
} else {
HeadDB.getInstance().getLocalization().sendMessage(player, "noAccessFavorites");
}
}
}));
}
}
private static Promise<Boolean> processPayment(Player player, Head head, int amount) {
Optional<BasicEconomyProvider> optional = HeadDB.getInstance().getEconomyProvider();
if (optional.isEmpty()) {
return Promise.completed(true); // No economy, the head is free
} else {
BigDecimal cost = BigDecimal.valueOf(HeadDB.getInstance().getConfig().getDouble("economy.cost." + head.getCategory().getName()) * amount);
HeadDB.getInstance().getLocalization().sendMessage(player.getUniqueId(), "processPayment", msg -> msg
.replace("%name%", head.getName())
.replace("%amount%", String.valueOf(amount))
.replace("%cost%", HeadDB.getInstance().getDecimalFormat().format(cost))
);
return optional.get().purchase(player, cost).thenApplyAsync(success -> {
if (success) {
HeadDB.getInstance().getLocalization().sendMessage(player, "completePayment", msg -> msg
.replace("%name%", head.getName())
.replace("%cost%", cost.toString()));
} else {
HeadDB.getInstance().getLocalization().sendMessage(player, "invalidFunds", msg -> msg.replace("%name%", head.getName()));
}
return success;
});
}
}
public static void purchase(Player player, Head head, int amount) {
// Bukkit API - Has to be sync.
processPayment(player, head, amount).thenAcceptSync(success -> {
if (success) {
ItemStack item = head.getItem(player.getUniqueId());
item.setAmount(amount);
player.getInventory().addItem(item); player.getInventory().addItem(item);
Sounds.SUCCESS.play(player);
return;
}
double cost = getHeadCost(head);
economyProvider.purchase(player, cost * amount).whenComplete((success, ex) -> {
Bukkit.getScheduler().runTask(HeadDB.getInstance(), () -> {
if (ex != null) {
HeadDB.getInstance().getLocalization().sendMessage(player, "error");
LOGGER.error("Purchasing head(s) failed!", ex);
return;
}
if (success) {
ItemStack item = head.getItem().clone();
item.setAmount(amount);
if (!HeadDB.getInstance().getCfg().shouldIncludeLore()) {
ItemMeta meta = item.getItemMeta();
//noinspection DataFlowIssue
meta.setLore(null);
item.setItemMeta(meta);
}
player.getInventory().addItem(item);
Sounds.SUCCESS.play(player);
HeadDB.getInstance().getLocalization().sendMessage(player, "completePayment", msg -> msg.replace("%amount%", String.valueOf(amount)).replace("%name%", head.getName()).replace("%cost%", String.valueOf(cost * amount)));
HeadDB.getInstance().getConfig().getStringList("commands.purchase").forEach(command -> { HeadDB.getInstance().getConfig().getStringList("commands.purchase").forEach(command -> {
if (command.isEmpty()) { if (command.isEmpty()) {
return; return;
} }
if (Hooks.PAPI.enabled()) { if (HeadDB.getInstance().isPAPI()) {
command = PlaceholderAPI.setPlaceholders(player, command); command = PlaceholderAPI.setPlaceholders(player, command);
} }
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command); Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
}); });
} else {
Sounds.FAIL.play(player);
HeadDB.getInstance().getLocalization().sendMessage(player, "invalidFunds", msg -> msg.replace("%amount%", String.valueOf(amount)).replace("%name%", head.getName()).replace("%cost%", String.valueOf(cost * amount)));
} }
}); });
});
} }
public static Optional<String> getTexture(ItemStack head) { public static double getHeadCost(Head head) {
ItemMeta meta = head.getItemMeta(); if (head instanceof LocalHead) { // Local heads have only one cost
if (meta == null) { return config.getLocalCost();
return Optional.empty(); } else if (config.getCosts().containsKey(head)) { // Try get cost for specific head
} return config.getCosts().get(head);
} else if (config.getCategoryCosts().containsKey(head.getCategory().orElse("?"))) { // Try get cost for specific category for the head
try { return config.getCategoryCosts().get(head.getCategory().orElse("?"));
Field profileField = meta.getClass().getDeclaredField("profile"); } else { // Get the default cost for the head.
profileField.setAccessible(true); return config.getDefaultCost();
GameProfile profile = (GameProfile) profileField.get(meta);
if (profile == null) {
return Optional.empty();
}
return profile.getProperties().get("textures").stream()
.filter(p -> p.getName().equals("textures"))
.findAny()
.map(Property::getValue);
} catch (NoSuchFieldException | SecurityException | IllegalAccessException e ) {
e.printStackTrace();
return Optional.empty();
} }
} }
public static ItemStack asItem(UUID receiver, Head head) { public static String colorize(String s) {
TranslatableLocalization localization = HeadDB.getInstance().getLocalization(); return ChatColor.translateAlternateColorCodes('&', s);
ItemStack item = new ItemBuilder(Material.PLAYER_HEAD)
.name(localization.getMessage(receiver, "menu.head.name").orElse("&e" + head.getName().toUpperCase(Locale.ROOT)).replace("%name%", head.getName()))
.setLore("&cID: " + head.getId(), "&7Tags: &e" + head.getTags())
.build();
ItemMeta meta = item.getItemMeta();
// if version < 1.20.1 use reflection, else (1.20.2+) use PlayerProfile because spigot bitches otherwise.
// Assumes newer version has been released when optional is empty.
if (ServerVersion.getVersion().orElse(ServerVersion.v_1_20_2).isOlderThan(ServerVersion.v_1_20_1)) {
try {
GameProfile profile = new GameProfile(head.getUniqueId(), head.getName());
profile.getProperties().put("textures", new Property("textures", head.getTexture()));
//noinspection DataFlowIssue
Field profileField = meta.getClass().getDeclaredField("profile");
profileField.setAccessible(true);
profileField.set(meta, profile);
item.setItemMeta(meta);
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) {
//Log.error("Could not set skull owner for " + uuid.toString() + " | Stack Trace:");
ex.printStackTrace();
}
} else {
try {
PlayerProfile profile = Bukkit.createPlayerProfile(head.getUniqueId(), head.getName());
PlayerTextures textures = profile.getTextures();
String url = new String(Base64.getDecoder().decode(head.getTexture()));
textures.setSkin(new URL(url.substring("{\"textures\":{\"SKIN\":{\"url\":\"".length(), url.length() - "\"}}}".length())));
profile.setTextures(textures);
SkullMeta skullMeta = (SkullMeta) meta;
if (skullMeta != null) {
skullMeta.setOwnerProfile(profile);
}
item.setItemMeta(skullMeta);
} catch (MalformedURLException ex) {
ex.printStackTrace();
}
} }
return item; private static String userAgent = null;
public static String getUserAgent() {
if (userAgent == null) {
// Example output: HeadDB/5.0.0 (Windows 10; 10.0; amd64) Eclipse Adoptium/21.0.4 Paper/1.21.1 (1.21.1-40-2fdb2e9)
userAgent = "HeadDB/" + HeadDB.getInstance().getDescription().getVersion() +
" (" + System.getProperty("os.name") +
"; " + System.getProperty("os.version") +
"; " + System.getProperty("os.arch") +
") " + System.getProperty("java.vendor") + "/" + System.getProperty("java.version") +
" " + Bukkit.getName() + "/" + Bukkit.getBukkitVersion().substring(0, Bukkit.getBukkitVersion().indexOf("-")) + " (" + Bukkit.getVersion().substring(0, Bukkit.getVersion().indexOf("(") - 1) + ")";
}
return userAgent;
} }
public static int resolveInt(String raw) { public static boolean matches(String provided, String query) {
try { return ChatColor.stripColor(provided.toLowerCase()).contains(query.toLowerCase());
return Integer.parseInt(raw);
} catch (NumberFormatException nfe) {
return 1;
}
}
public static ItemStack getItemFromConfig(String path, Material def) {
ConfigurationSection section = HeadDB.getInstance().getConfig().getConfigurationSection(path);
Validate.notNull(section, "Section can not be null!");
Material material = Material.matchMaterial(section.getString("material", def.name()));
if (material == null) {
material = def;
}
ItemStack item = new ItemStack(material);
ItemMeta meta = item.getItemMeta();
if (meta != null) {
//noinspection DataFlowIssue
meta.setDisplayName(StringUtils.colorize(section.getString("name")));
List<String> lore = new ArrayList<>();
for (String line : section.getStringList("lore")) {
if (line != null && !line.isEmpty()) {
lore.add(StringUtils.colorize(line));
}
}
meta.setLore(lore);
item.setItemMeta(meta);
}
return item;
} }
} }

Datei anzeigen

@ -1,85 +0,0 @@
package tsp.headdb.implementation.category;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import tsp.headdb.HeadDB;
import tsp.headdb.core.api.HeadAPI;
import tsp.headdb.core.util.Utils;
import tsp.nexuslib.builder.ItemBuilder;
import tsp.nexuslib.util.StringUtils;
import javax.annotation.Nonnull;
import java.util.Locale;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;
public enum Category {
ALPHABET("alphabet", 20),
ANIMALS("animals", 21),
BLOCKS("blocks", 22),
DECORATION("decoration", 23),
FOOD_DRINKS("food-drinks", 24),
HUMANS("humans", 29),
HUMANOID("humanoid", 30),
MISCELLANEOUS("miscellaneous", 31),
MONSTERS("monsters", 32),
PLANTS("plants", 33);
private final String name;
private final int defaultSlot;
private ItemStack item;
public static final Category[] VALUES = values();
Category(String name, int slot) {
this.name = name;
this.defaultSlot = slot;
}
public String getName() {
return name;
}
public int getDefaultSlot() {
return defaultSlot;
}
public static Optional<Category> getByName(String cname) {
for (Category value : VALUES) {
if (value.name.equalsIgnoreCase(cname) || value.getName().equalsIgnoreCase(cname)) {
return Optional.of(value);
}
}
return Optional.empty();
}
@Nonnull
public ItemStack getItem(UUID receiver) {
if (item == null) {
HeadAPI.getHeads(this).stream().findFirst()
.ifPresentOrElse(head -> {
ItemStack retrieved = new ItemStack(head.getItem(receiver));
ItemMeta meta = retrieved.getItemMeta();
if (meta != null && meta.getLore() != null) {
meta.setDisplayName(Utils.translateTitle(HeadDB.getInstance().getLocalization().getMessage(receiver, "menu.main.category.name").orElse("&e" + getName()), HeadAPI.getHeads(this).size(), getName().toUpperCase(Locale.ROOT)));
meta.setLore(HeadDB.getInstance().getConfig().getStringList("menu.main.category.lore").stream()
.map(StringUtils::colorize)
.collect(Collectors.toList()));
retrieved.setItemMeta(meta);
item = retrieved;
} else {
item = new ItemStack(Material.PLAYER_HEAD);
HeadDB.getInstance().getLog().debug("Failed to get null-meta category item for: " + name());
}
},
() -> item = new ItemBuilder(Material.PLAYER_HEAD).name(getName().toUpperCase(Locale.ROOT)).build());
}
return item.clone(); // Return clone that changes are not reflected
}
}

Datei anzeigen

@ -1,76 +0,0 @@
package tsp.headdb.implementation.head;
import org.bukkit.inventory.ItemStack;
import tsp.headdb.core.util.Utils;
import tsp.headdb.implementation.category.Category;
import tsp.nexuslib.util.Validate;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.UUID;
public class Head {
private final int id;
private final UUID uniqueId;
private final String name;
private final String texture;
private final String tags;
private final String updated;
private final Category category;
private ItemStack item;
@ParametersAreNonnullByDefault
public Head(int id, UUID uniqueId, String name, String texture, String tags, String updated, Category category) {
Validate.notNull(uniqueId, "Unique id can not be null!");
Validate.notNull(name, "Name can not be null!");
Validate.notNull(texture, "Texture can not be null!");
Validate.notNull(tags, "Tags can not be null!");
Validate.notNull(updated, "Updated can not be null!");
Validate.notNull(category, "Category can not be null!");
this.id = id;
this.uniqueId = uniqueId;
this.name = name;
this.texture = texture;
this.tags = tags;
this.updated = updated;
this.category = category;
}
public ItemStack getItem(UUID receiver) {
if (item == null) {
item = Utils.asItem(receiver, this);
}
return item.clone(); // Return clone that changes are not reflected
}
public int getId() {
return id;
}
public UUID getUniqueId() {
return uniqueId;
}
public String getName() {
return name;
}
public String getTexture() {
return texture;
}
public String getTags() {
return tags;
}
public String getUpdated() {
return updated;
}
public Category getCategory() {
return category;
}
}

Datei anzeigen

@ -1,72 +0,0 @@
package tsp.headdb.implementation.head;
import org.bukkit.plugin.java.JavaPlugin;
import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.requester.HeadProvider;
import tsp.headdb.implementation.requester.Requester;
import tsp.helperlite.scheduler.promise.Promise;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class HeadDatabase {
private final JavaPlugin plugin;
private final Requester requester;
private final ConcurrentHashMap<Category, List<Head>> heads;
private long timestamp;
public HeadDatabase(JavaPlugin plugin, HeadProvider provider) {
this.plugin = plugin;
this.requester = new Requester(plugin, provider);
this.heads = new ConcurrentHashMap<>();
// Fill empty
for (Category cat : Category.VALUES) {
heads.put(cat, new ArrayList<>());
}
}
public Map<Category, List<Head>> getHeads() {
return heads;
}
public Promise<HeadResult> getHeadsNoCache() {
return Promise.supplyingAsync(() -> {
long start = System.currentTimeMillis();
Map<Category, List<Head>> result = new HashMap<>();
for (Category category : Category.VALUES) {
result.put(category, requester.fetchAndResolve(category));
}
return new HeadResult(System.currentTimeMillis() - start, result);
});
}
public Promise<HeadResult> update() {
return Promise.start()
.thenComposeAsync(compose -> getHeadsNoCache())
.thenApplyAsync(result -> {
heads.clear();
heads.putAll(result.heads());
timestamp = System.currentTimeMillis();
return result;
});
}
public long getTimestamp() {
return timestamp;
}
public JavaPlugin getPlugin() {
return plugin;
}
public Requester getRequester() {
return requester;
}
}

Datei anzeigen

@ -1,11 +0,0 @@
package tsp.headdb.implementation.head;
import tsp.headdb.implementation.category.Category;
import java.util.List;
import java.util.Map;
/**
* @author TheSilentPro (Silent)
*/
public record HeadResult(long elapsed, Map<Category, List<Head>> heads) {}

Datei anzeigen

@ -1,28 +0,0 @@
package tsp.headdb.implementation.head;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
import java.util.Collections;
import java.util.UUID;
public record LocalHead(UUID uniqueId, String name) {
public ItemStack getItem() {
ItemStack item = new ItemStack(Material.PLAYER_HEAD);
SkullMeta meta = (SkullMeta) item.getItemMeta();
if (meta != null) {
meta.setOwningPlayer(Bukkit.getOfflinePlayer(uniqueId));
meta.setDisplayName(ChatColor.GOLD + name);
//noinspection UnnecessaryToStringCall
meta.setLore(Collections.singletonList(ChatColor.GRAY + "UUID: " + uniqueId.toString()));
item.setItemMeta(meta);
}
return item.clone();
}
}

Datei anzeigen

@ -1,26 +0,0 @@
package tsp.headdb.implementation.requester;
import tsp.headdb.implementation.category.Category;
public enum HeadProvider {
HEAD_API("https://minecraft-heads.com/scripts/api.php?cat=%s&tags=true"), // No ids
HEAD_STORAGE("https://raw.githubusercontent.com/TheSilentPro/HeadStorage/master/storage/%s.json"),
HEAD_WORKER(""), // Unimplemented yet.
HEAD_ARCHIVE("https://heads.pages.dev/archive/%s.json");
private final String url;
HeadProvider(String url) {
this.url = url;
}
public String getUrl() {
return url;
}
public String getFormattedUrl(Category category) {
return String.format(getUrl(), category.getName());
}
}

Datei anzeigen

@ -1,111 +0,0 @@
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.HeadDB;
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;
/**
* Responsible for requesting heads from providers.
*
* @author TheSilentPro
* @see tsp.headdb.core.api.HeadAPI
* @see tsp.headdb.implementation.head.HeadDatabase
*/
public class Requester {
private final JavaPlugin plugin;
private HeadProvider provider;
public Requester(JavaPlugin plugin, HeadProvider provider) {
this.plugin = plugin;
this.provider = provider;
}
public List<Head> fetchAndResolve(Category category) {
try {
Response response = fetch(category);
List<Head> result = new ArrayList<>();
if (response.code() != 200) {
return result;
}
JsonArray main = JsonParser.parseString(response.response()).getAsJsonArray();
for (JsonElement entry : main) {
JsonObject obj = entry.getAsJsonObject();
int id = obj.get("id").getAsInt();
if (plugin.getConfig().contains("blockedHeads.ids")) {
List<Integer> blockedIds = plugin.getConfig().getIntegerList("blockedHeads.ids");
if (blockedIds.contains(id)) {
HeadDB.getInstance().getLog().debug("Skipped blocked head: " + obj.get("name").getAsString() + "(" + id + ")");
continue;
}
}
result.add(new Head(
id,
Utils.validateUniqueId(obj.get("uuid").getAsString()).orElse(UUID.randomUUID()),
obj.get("name").getAsString(),
obj.get("value").getAsString(),
obj.get("tags").getAsString(),
response.date(),
category
));
}
return result;
} catch (IOException ex) {
HeadDB.getInstance().getLog().debug("Failed to load from provider: " + provider.name());
if (HeadDB.getInstance().getConfig().getBoolean("fallback") && provider != HeadProvider.HEAD_ARCHIVE) { // prevent recursion. Maybe switch to an attempts counter down in the future
provider = HeadProvider.HEAD_ARCHIVE;
return fetchAndResolve(category);
} else {
HeadDB.getInstance().getLog().error("Could not fetch heads from any provider!");
return new ArrayList<>();
}
}
}
public Response fetch(Category category) throws IOException {
HttpURLConnection connection = (HttpURLConnection) new URL(provider.getFormattedUrl(category)).openConnection();
connection.setConnectTimeout(5000);
connection.setRequestMethod("GET");
connection.setRequestProperty("User-Agent", plugin.getName() + "/" + Utils.getVersion().orElse(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);
}
connection.disconnect();
return new Response(builder.toString(), connection.getResponseCode(), connection.getHeaderField("date"));
}
}
public HeadProvider getProvider() {
return provider;
}
public JavaPlugin getPlugin() {
return plugin;
}
}

Datei anzeigen

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

Datei anzeigen

@ -1 +0,0 @@
version=${project.version}

Datei anzeigen

@ -1,20 +1,15 @@
# How often the database should be updated in seconds.
refresh: 86400
# If local heads should be enabled.
# Local heads are heads from players that have joined your server at least once.
localHeads: true
# If enabled categories will require a permission to be used.
# Permission: headdb.category.<category>
requireCategoryPermission: false
# Economy Options # Economy Options
economy: economy:
enabled: false enabled: false
provider: "VAULT" # Supported: VAULT provider: "VAULT" # Supported: VAULT
format: "##.##" # The cost of a local (Player) head.
cost: localCost: 100
# Default cost for head.
defaultCost: 100
# Default category cost.
defaultCategoryCost: 100
# Cost of categories.
categoryCost:
alphabet: 100 alphabet: 100
animals: 100 animals: 100
blocks: 100 blocks: 100
@ -25,70 +20,36 @@ economy:
miscellaneous: 100 miscellaneous: 100
monsters: 100 monsters: 100
plants: 100 plants: 100
# Cost of individual heads by either number ID or texture value.
cost:
# Example.
# The key(-2) would be the ID or TEXTURE VALUE of the head.
# The value(0) would be the cost of the head.
#-2: 0
# Command Configuration. Supports PlaceholderAPI. # Command Configuration. Supports PlaceholderAPI.
commands: commands:
# Commands to run ONLY if the purchase is successful. # Commands to run ONLY if the purchase is successful.
# They are run as CONSOLE after the player has receiver the head in their inventory. # They are run as CONSOLE after the player has received the head in their inventory.
purchase: purchase:
- "" - ""
# Graphical User Interface customization # Block heads from showing up in the menu.
gui: blockedHeads:
main: - ""
fill:
material: "BLACK_STAINED_GLASS_PANE" # If enabled categories will require a permission to be used.
name: "" # Permission: headdb.category.<category>
lore: [] # Local Heads permission: headdb.category.local.* OR headdb.category.local.<PLAYER UUID>
# Categories are set in the slots 20-24 & 29 - 33. You can add specific ones here to relocate them. requireCategoryPermission: false
# Note: that the slots start from 0
category: # If the menu should show more info on the head such as: category, contributors, collections, publish date
alphabet: 20 moreInfo: true
animals: 21
blocks: 22 # If the lore (ID,Tags) should be included in the inventory item given to players.
decoration: 23 includeLore: false
food-drinks: 24
humans: 29
humanoid: 30
miscellaneous: 31
monsters: 32
plants: 33
meta:
favorites:
slot: 39
item:
material: "BOOK"
name: "&6Favorites"
lore:
- "&7Click to view your favorite heads."
search:
slot: 40
item:
material: "DARK_OAK_SIGN"
name: "&6Search"
lore:
- "&7Click to search for a specific head."
local:
slot: 41
item:
material: "COMPASS"
name: "&6Local Heads"
lore:
- "&7Click to view Local heads."
# If the original fetching fails and this is enabled, # If the original fetching fails and this is enabled,
# the plugin will attempt to fetch the heads from an archive. # the plugin will attempt to fetch the heads from an archive.
# The archive is static so some heads may be missing, this will only be used when all else fails. # The archive is static so some heads may be missing, this will only be used when all else fails.
fallback: true fallback: true
# Shows more plugin information. (/hdb info)
showAdvancedPluginInfo: true
# Block heads from all database instances.
blockedHeads:
# List of head ids to block
ids:
- -1
# Debug Mode
debug: false

Datei anzeigen

@ -1,68 +1,36 @@
noConsole: "&cOnly for in-game players!" error: "&cAn error occurred! Please contact an administrator."
noConsole: "&cThis command can not be used by non players!"
noPermission: "&cNo permission!" noPermission: "&cNo permission!"
invalidSubCommand: "&cInvalid sub-command! Run &e/hdb help&c for help." invalidSubCommand: "&cInvalid sub-command! Run &e/hdb help&c for help."
giveCommandInvalid: "&cInvalid head: &e%name%"
invalidArguments: "&cInvalid Arguments! Use &e/hdb help&c for help." invalidArguments: "&cInvalid Arguments! Use &e/hdb help&c for help."
invalidTarget: "&cInvalid target: &e%name%" invalidTarget: "&cInvalid target: &e%name%"
invalidCategory: "&cInvalid Category!" invalidCategory: "&cInvalid category!"
invalidNumber: "&e%name% &cis not a number!" invalidNumber: "&e%name% &cis not a number!"
invalidPageIndex: "&cThat page is out of bounds! Max: %pages%" invalidPageIndex: "&cThat page is out of bounds! Max: %pages%"
noAccessFavorites: "&cYou do not have access to favorites!" noAccessFavorites: "&cYou do not have access to favorites!"
openDatabase: "" # Intentionally empty. Sent when the main gui is opened command:
updateDatabase: "&7Updating..." search:
updateDatabaseDone: "&7Done! Total Heads: &6%size%" wait: "&7Searching for: &6%name%&7! Please wait..."
reloadCommand: "&7Reloading, please wait before using the plugin..." done: "&7Found &6%size% &7heads!"
reloadCommandDone: "&7Reload Complete!" invalid: "&cNo heads matching: &e%name%"
searchCommand: "&7Searching for heads matching: &6%query%" give:
searchCommandResults: "&7Found &6%size% &7matches!" invalid: "&cInvalid head: &e%name%"
giveCommand: "&7Gave &6x%size% %name% &7to &6%receiver%" done: "&7Gave &a%amount%&7x &6%name% &7to &6%player%"
itemTexture: "&7Texture: &6%texture%"
itemNoTexture: "&cThis item does not have a texture!" openDatabase: "" # Intentionally empty by default. Sent when the main gui is opened.
copyTexture: "&6Click to copy texture!" notReadyDatabase: "&cPlease wait a few seconds..."
updateStarted: "&7Updating..."
updateFinished: "&7Done! Total Heads: &6%size%"
addedFavorite: "&7Added &6%name% &7to your favorites!" addedFavorite: "&7Added &6%name% &7to your favorites!"
removedFavorite: "&7Removed &6%name% &7from your favorites!" removedFavorite: "&7Removed &6%name% &7from your favorites!"
noFavorites: "&cYou have no favorite heads!"
# Only shown if economy is enabled # Only used if economy is enabled.
processPayment: "&7Purchasing &6%name% &7for &6%cost%&7! Please wait..." processPayment: "&7Purchasing &a%amount% &7x &6%name% &7for &6%cost%&7..."
completePayment: "&7Received &6%name% &7for &6%cost%" completePayment: "&7Bought &a%amount%&7x &6%name% &7for &6%cost%"
invalidFunds: "&cYou do not have enough to buy &6%name%&c!" invalidFunds: "&cYou cannot afford &6%name%&c!"
invalidLanguage: "&cInvalid Language! Available: &e%languages%" invalidLanguage: "&cInvalid Language! Available: &e%languages%"
languageChanged: "&7Your language was set to: &6%language%" languageChanged: "&7Your language was set to: &6%language%"
menu:
main:
title: "&cHeadDB &7(%size%)"
category:
name: "&6&l%category%"
page:
name: "&7Go to specific page"
lore:
- "&7Left-Click to open"
- "&7Right-Click to open specific page"
favorites:
name: "&6Favorites"
search:
name: "&6Search"
local:
name: "&6Local Heads"
category:
name: "&cHeadDB &7- &6%category% &7(%size%)"
head:
name: "&6%name%"
search:
name: "&cHeadDB &7- &6Search: %query% &7(%size%)"
page:
name: "&cHeadDB &7- &6Enter page number"
settings:
name: "&cHeadDB &7- &6Settings"
language:
# Setting name
name: "&cLanguage"
# Available languages lore
available: "&7Languages Available: &6%size%"
# Title of the language selection inventory
title: "&cHeadDB &7- &6Languages &7(%size%)"
# Language item name
format: "&6%language%"

Datei anzeigen

@ -1,11 +1,15 @@
# noinspection YAMLSchemaValidation
name: ${project.name} name: ${project.name}
description: ${project.description} description: ${project.description}
author: TheSilentPro (Silent)
main: tsp.headdb.HeadDB main: tsp.headdb.HeadDB
version: ${project.version} version: ${project.version}
softdepend: ["Vault"] softdepend: ["PlaceholderAPI", "Vault"]
api-version: 1.19 api-version: 1.19
author: TheSilentPro (Silent) libraries:
- "org.xerial:sqlite-jdbc:3.47.0.0"
spigot-id: 84967 spigot-id: 84967
commands: commands:
@ -24,11 +28,11 @@ permissions:
headdb.command.update: true headdb.command.update: true
headdb.command.reload: true headdb.command.reload: true
headdb.command.language: true headdb.command.language: true
headdb.command.settings: true
headdb.command.texture: true headdb.command.texture: true
headdb.favorites: true headdb.favorites: true
headdb.local: true headdb.category.local.*: true
headdb.category.*: true headdb.category.*: true
headdb.category.all: true
headdb.command.open: headdb.command.open:
default: op default: op
headdb.command.search: headdb.command.search:
@ -41,13 +45,11 @@ permissions:
default: op default: op
headdb.command.language: headdb.command.language:
default: op default: op
headdb.command.settings:
default: op
headdb.command.texture: headdb.command.texture:
default: op default: op
headdb.favorites: headdb.favorites:
default: op default: op
headdb.local: headdb.category.local.*:
default: op default: op
headdb.category.*: headdb.category.*:
default: op default: op