Mirror von
https://github.com/TheSilentPro/HeadDB.git
synchronisiert 2024-12-27 11:20:05 +01:00
Commit
ef96d1a196
79
pom.xml
79
pom.xml
@ -14,8 +14,8 @@
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<maven.compiler.source>17</maven.compiler.source>
|
||||
<maven.compiler.target>17</maven.compiler.target>
|
||||
<maven.compiler.source>21</maven.compiler.source>
|
||||
<maven.compiler.target>21</maven.compiler.target>
|
||||
<build.author>TheSilentPro (Silent)</build.author>
|
||||
<maven.build.timestamp.format>dd-MM-yyyy HH:mm:ss</maven.build.timestamp.format>
|
||||
<build.timestamp>${maven.build.timestamp}</build.timestamp>
|
||||
@ -55,42 +55,33 @@
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.20.2-R0.1-SNAPSHOT</version>
|
||||
<version>1.21.1-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mojang</groupId>
|
||||
<artifactId>authlib</artifactId>
|
||||
<version>4.0.43</version>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>2.0.16</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<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>
|
||||
</dependency>
|
||||
|
||||
<!-- Hard Dependencies (Shaded) -->
|
||||
<dependency>
|
||||
<groupId>com.github.TheSilentPro</groupId>
|
||||
<artifactId>NexusLib</artifactId>
|
||||
<version>45b4813899</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>
|
||||
<artifactId>InvLib</artifactId>
|
||||
<version>c184c31f6e</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Soft Dependencies -->
|
||||
@ -124,18 +115,18 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.10.1</version>
|
||||
<version>3.13.0</version>
|
||||
<configuration>
|
||||
<source>${maven.compiler.source}</source>
|
||||
<target>${maven.compiler.target}</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Shade -->
|
||||
<!-- Shade Plugin Configuration -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>3.4.1</version>
|
||||
<version>3.6.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
@ -159,41 +150,19 @@
|
||||
<artifact>*:*</artifact>
|
||||
<excludeDefaults>false</excludeDefaults>
|
||||
<includes>
|
||||
<include>tsp.headdb.core.util.anvilgui</include>
|
||||
<include>tsp/headdb/core/util/anvilgui/**</include>
|
||||
</includes>
|
||||
<excludes>
|
||||
<exclude>org/yaml/**</exclude>
|
||||
<exclude>META-INF/maven/org.yaml</exclude>
|
||||
<exclude>javax/**</exclude>
|
||||
</excludes>
|
||||
</filter>
|
||||
</filters>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</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>
|
||||
</build>
|
||||
|
||||
|
@ -1,227 +1,167 @@
|
||||
package tsp.headdb;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.PluginCommand;
|
||||
import tsp.headdb.core.command.*;
|
||||
import tsp.headdb.core.economy.BasicEconomyProvider;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
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.api.model.Head;
|
||||
import tsp.headdb.core.storage.Storage;
|
||||
import tsp.headdb.core.task.UpdateTask;
|
||||
import tsp.headdb.core.util.HeadDBLogger;
|
||||
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 tsp.headdb.core.util.Localization;
|
||||
import tsp.invlib.InvLib;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Optional;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
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 HeadDBLogger logger;
|
||||
private TranslatableLocalization localization;
|
||||
private ConfigData config;
|
||||
private EconomyProvider economyProvider;
|
||||
private boolean PAPI;
|
||||
private PlayerDatabase playerDatabase;
|
||||
private Localization localization;
|
||||
private Storage storage;
|
||||
private BasicEconomyProvider economyProvider;
|
||||
private CommandManager commandManager;
|
||||
private Task updateTask;
|
||||
|
||||
@Override
|
||||
public void onStart(NexusPlugin nexusPlugin) {
|
||||
public void onEnable() {
|
||||
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();
|
||||
instance.logger = new HeadDBLogger(getConfig().getBoolean("debug"));
|
||||
instance.logger.info("Loading HeadDB - " + Utils.getVersion().orElse(getDescription().getVersion() + " (UNKNOWN SEMVER)"));
|
||||
InvLib.init(this);
|
||||
|
||||
instance.logger.info("Loaded " + loadLocalization() + " languages!");
|
||||
this.PAPI = Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI");
|
||||
|
||||
instance.initStorage();
|
||||
instance.initEconomy();
|
||||
if (this.config.isEconomyEnabled()) {
|
||||
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
|
||||
instance.commandManager = new CommandManager();
|
||||
loadCommands();
|
||||
|
||||
initMetrics();
|
||||
ensureLatestVersion();
|
||||
instance.logger.info("Done!");
|
||||
this.commandManager = new CommandManager().init();
|
||||
PluginCommand mainCommand = getCommand("headdb");
|
||||
if (mainCommand == null) {
|
||||
LOGGER.error("Failed to get main /headdb command!");
|
||||
this.setEnabled(false);
|
||||
return;
|
||||
}
|
||||
mainCommand.setExecutor(commandManager);
|
||||
mainCommand.setTabCompleter(commandManager);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (storage != null) {
|
||||
storage.getPlayerStorage().suspend();
|
||||
File langFile = new File(getDataFolder(), "langs.data");
|
||||
if (!langFile.exists()) {
|
||||
try {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
langFile.createNewFile();
|
||||
localization.saveLanguages(langFile);
|
||||
} catch (IOException ex) {
|
||||
logger.error("Failed to save receiver langauges!");
|
||||
ex.printStackTrace();
|
||||
// Save language data
|
||||
if (playerDatabase != null) {
|
||||
playerDatabase.save();
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
long preloadStart = System.currentTimeMillis();
|
||||
int total = result.size();
|
||||
int index = 0;
|
||||
|
||||
LOGGER.info("Preloading {} heads...", total);
|
||||
// Milestone percentages we want to print
|
||||
int[] milestones = {25, 50, 75, 100};
|
||||
int nextMilestoneIndex = 0;
|
||||
|
||||
for (Head head : result) {
|
||||
// Simulate processing each head
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateTask.stop();
|
||||
}
|
||||
|
||||
private void startUpdateTask() {
|
||||
updateTask = Schedulers.builder()
|
||||
.async()
|
||||
.every(getConfig().getLong("refresh", 86400L), TimeUnit.SECONDS)
|
||||
.run(new UpdateTask());
|
||||
}
|
||||
|
||||
private void ensureLatestVersion() {
|
||||
Promise.start().thenApplyAsync(a -> {
|
||||
try {
|
||||
URLConnection connection = new URL("https://api.spigotmc.org/legacy/update.php?resource=" + 84967).openConnection();
|
||||
connection.setConnectTimeout(5000);
|
||||
connection.setRequestProperty("User-Agent", this.getName() + "-VersionChecker");
|
||||
|
||||
return new BufferedReader(new InputStreamReader(connection.getInputStream())).readLine().equals(Utils.getVersion().orElse(getDescription().getVersion()));
|
||||
} catch (IOException ex) {
|
||||
return false;
|
||||
}
|
||||
}).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() {
|
||||
instance.localization = new TranslatableLocalization(this, "messages");
|
||||
instance.localization = new Localization(this, "messages");
|
||||
try {
|
||||
instance.localization.createDefaults();
|
||||
int count = instance.localization.load();
|
||||
File langFile = new File(getDataFolder(), "langs.data");
|
||||
if (langFile.exists()) {
|
||||
localization.loadLanguages(langFile);
|
||||
}
|
||||
|
||||
return count;
|
||||
return instance.localization.load();
|
||||
} catch (URISyntaxException | IOException ex) {
|
||||
instance.logger.error("Failed to load localization!");
|
||||
ex.printStackTrace();
|
||||
LOGGER.error("Failed to load localization!", ex);
|
||||
this.setEnabled(false);
|
||||
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() {
|
||||
return commandManager;
|
||||
}
|
||||
|
||||
public Optional<BasicEconomyProvider> getEconomyProvider() {
|
||||
return Optional.ofNullable(economyProvider);
|
||||
public Storage getStorage() {
|
||||
return storage;
|
||||
}
|
||||
|
||||
@SuppressWarnings("DataFlowIssue")
|
||||
private DecimalFormat decimalFormat = new DecimalFormat(getConfig().getString("economy.format"));
|
||||
|
||||
public DecimalFormat getDecimalFormat() {
|
||||
return decimalFormat != null ? decimalFormat : (decimalFormat = new DecimalFormat("##.##"));
|
||||
public PlayerDatabase getPlayerDatabase() {
|
||||
return playerDatabase;
|
||||
}
|
||||
|
||||
public TranslatableLocalization getLocalization() {
|
||||
public Localization getLocalization() {
|
||||
return localization;
|
||||
}
|
||||
|
||||
public HeadDBLogger getLog() {
|
||||
return logger;
|
||||
public boolean isPAPI() {
|
||||
return PAPI;
|
||||
}
|
||||
|
||||
public EconomyProvider getEconomyProvider() {
|
||||
return economyProvider;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ConfigData getCfg() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public static HeadDB getInstance() {
|
||||
return instance;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -22,13 +22,27 @@ import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
|
||||
@SuppressWarnings({"all", "deprecation"}) // Class is from bstats, can't modify it.
|
||||
@SuppressWarnings("all")
|
||||
class Metrics {
|
||||
|
||||
private final Plugin plugin;
|
||||
|
||||
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.
|
||||
*
|
||||
@ -36,7 +50,7 @@ class Metrics {
|
||||
* @param serviceId The id of the service. It can be found at <a
|
||||
* href="https://bstats.org/what-is-my-plugin-id">What is my plugin id?</a>
|
||||
*/
|
||||
public Metrics(JavaPlugin plugin, int serviceId) {
|
||||
private Metrics(JavaPlugin plugin, int serviceId) {
|
||||
this.plugin = plugin;
|
||||
// Get the config file
|
||||
File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
|
||||
@ -108,7 +122,7 @@ class Metrics {
|
||||
}
|
||||
|
||||
private void appendServiceData(JsonObjectBuilder builder) {
|
||||
builder.appendField("pluginVersion", Utils.getVersion().orElse("Unknown"));
|
||||
builder.appendField("pluginVersion", Utils.getUserAgent());
|
||||
}
|
||||
|
||||
private int getPlayerAmount() {
|
||||
|
333
src/main/java/tsp/headdb/api/HeadAPI.java
Normale Datei
333
src/main/java/tsp/headdb/api/HeadAPI.java
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
62
src/main/java/tsp/headdb/api/HeadDatabase.java
Normale Datei
62
src/main/java/tsp/headdb/api/HeadDatabase.java
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
101
src/main/java/tsp/headdb/api/model/Category.java
Normale Datei
101
src/main/java/tsp/headdb/api/model/Category.java
Normale Datei
@ -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();
|
||||
}
|
||||
|
||||
}
|
95
src/main/java/tsp/headdb/api/model/Head.java
Normale Datei
95
src/main/java/tsp/headdb/api/model/Head.java
Normale Datei
@ -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);
|
||||
}
|
||||
|
||||
}
|
58
src/main/java/tsp/headdb/api/model/LocalHead.java
Normale Datei
58
src/main/java/tsp/headdb/api/model/LocalHead.java
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
118
src/main/java/tsp/headdb/api/provider/HeadDataProvider.java
Normale Datei
118
src/main/java/tsp/headdb/api/provider/HeadDataProvider.java
Normale Datei
@ -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);
|
||||
}
|
||||
|
||||
}
|
117
src/main/java/tsp/headdb/api/provider/HeadFileProvider.java
Normale Datei
117
src/main/java/tsp/headdb/api/provider/HeadFileProvider.java
Normale Datei
@ -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);
|
||||
}
|
||||
|
||||
}
|
15
src/main/java/tsp/headdb/api/provider/HeadProvider.java
Normale Datei
15
src/main/java/tsp/headdb/api/provider/HeadProvider.java
Normale Datei
@ -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);
|
||||
|
||||
}
|
92
src/main/java/tsp/headdb/api/provider/HeadStorageProvider.java
Normale Datei
92
src/main/java/tsp/headdb/api/provider/HeadStorageProvider.java
Normale Datei
@ -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()));
|
||||
});
|
||||
}
|
||||
|
||||
}
|
11
src/main/java/tsp/headdb/api/provider/ProviderResponse.java
Normale Datei
11
src/main/java/tsp/headdb/api/provider/ProviderResponse.java
Normale Datei
@ -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) {}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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"));
|
||||
}
|
||||
|
||||
}
|
@ -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))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -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<===============================================================>");
|
||||
}
|
||||
|
||||
}
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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"));
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
106
src/main/java/tsp/headdb/core/commands/CommandGive.java
Normale Datei
106
src/main/java/tsp/headdb/core/commands/CommandGive.java
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
52
src/main/java/tsp/headdb/core/commands/CommandHelp.java
Normale Datei
52
src/main/java/tsp/headdb/core/commands/CommandHelp.java
Normale Datei
@ -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 + "<===============================================================>");
|
||||
}
|
||||
|
||||
}
|
31
src/main/java/tsp/headdb/core/commands/CommandInfo.java
Normale Datei
31
src/main/java/tsp/headdb/core/commands/CommandInfo.java
Normale Datei
@ -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);
|
||||
}
|
||||
|
||||
}
|
116
src/main/java/tsp/headdb/core/commands/CommandManager.java
Normale Datei
116
src/main/java/tsp/headdb/core/commands/CommandManager.java
Normale Datei
@ -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);
|
||||
}
|
||||
|
||||
}
|
51
src/main/java/tsp/headdb/core/commands/CommandOpen.java
Normale Datei
51
src/main/java/tsp/headdb/core/commands/CommandOpen.java
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
100
src/main/java/tsp/headdb/core/commands/CommandSearch.java
Normale Datei
100
src/main/java/tsp/headdb/core/commands/CommandSearch.java
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
28
src/main/java/tsp/headdb/core/commands/CommandUpdate.java
Normale Datei
28
src/main/java/tsp/headdb/core/commands/CommandUpdate.java
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
110
src/main/java/tsp/headdb/core/commands/HDBCommand.java
Normale Datei
110
src/main/java/tsp/headdb/core/commands/HDBCommand.java
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
115
src/main/java/tsp/headdb/core/config/ConfigData.java
Normale Datei
115
src/main/java/tsp/headdb/core/config/ConfigData.java
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
28
src/main/java/tsp/headdb/core/economy/EconomyProvider.java
Normale Datei
28
src/main/java/tsp/headdb/core/economy/EconomyProvider.java
Normale Datei
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -4,42 +4,43 @@ import net.milkbowl.vault.economy.Economy;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.RegisteredServiceProvider;
|
||||
import tsp.headdb.HeadDB;
|
||||
import tsp.helperlite.scheduler.promise.Promise;
|
||||
import org.slf4j.Logger;
|
||||
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;
|
||||
|
||||
@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
|
||||
public void init() {
|
||||
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;
|
||||
}
|
||||
|
||||
RegisteredServiceProvider<Economy> economyProvider = Bukkit.getServer().getServicesManager().getRegistration(net.milkbowl.vault.economy.Economy.class);
|
||||
if (economyProvider == null) {
|
||||
HeadDB.getInstance().getLog().error("Could not find vault economy provider!");
|
||||
LOGGER.error("Could not find vault economy provider!");
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
package tsp.headdb.core.hook;
|
||||
|
||||
public record PluginHook(boolean enabled) {}
|
65
src/main/java/tsp/headdb/core/player/PlayerData.java
Normale Datei
65
src/main/java/tsp/headdb/core/player/PlayerData.java
Normale Datei
@ -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);
|
||||
}
|
||||
|
||||
}
|
85
src/main/java/tsp/headdb/core/player/PlayerDatabase.java
Normale Datei
85
src/main/java/tsp/headdb/core/player/PlayerDatabase.java
Normale Datei
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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 {}
|
@ -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!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
103
src/main/java/tsp/headdb/core/storage/SQLStatement.java
Normale Datei
103
src/main/java/tsp/headdb/core/storage/SQLStatement.java
Normale Datei
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,33 +1,117 @@
|
||||
package tsp.headdb.core.storage;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import tsp.headdb.HeadDB;
|
||||
import tsp.helperlite.Schedulers;
|
||||
import tsp.headdb.core.player.PlayerData;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.io.IOException;
|
||||
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 {
|
||||
|
||||
private final Executor executor;
|
||||
private final PlayerStorage playerStorage;
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Storage.class);
|
||||
private final ExecutorService executor;
|
||||
private Connection connection;
|
||||
|
||||
public Storage() {
|
||||
executor = Schedulers.async();
|
||||
validateDataDirectory();
|
||||
playerStorage = new PlayerStorage(HeadDB.getInstance(), this);
|
||||
this.executor = Executors.newSingleThreadExecutor(r -> new Thread(r, "HeadDB Storage"));
|
||||
}
|
||||
|
||||
public PlayerStorage getPlayerStorage() {
|
||||
return playerStorage;
|
||||
public Storage init() {
|
||||
createTables();
|
||||
return this;
|
||||
}
|
||||
|
||||
public Executor getExecutor() {
|
||||
return executor;
|
||||
public CompletableFuture<ResultSet> selectPlayers() {
|
||||
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() {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
new File(HeadDB.getInstance().getDataFolder(), "data").mkdir();
|
||||
public CompletableFuture<Void> insertPlayer(UUID id, String lang, boolean soundEnabled, String favorites) {
|
||||
return connect().thenAcceptAsync(conn -> {
|
||||
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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
463
src/main/java/tsp/headdb/core/util/Localization.java
Normale Datei
463
src/main/java/tsp/headdb/core/util/Localization.java
Normale Datei
@ -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);
|
||||
}
|
||||
|
||||
}
|
303
src/main/java/tsp/headdb/core/util/MenuSetup.java
Normale Datei
303
src/main/java/tsp/headdb/core/util/MenuSetup.java
Normale Datei
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
47
src/main/java/tsp/headdb/core/util/PaginationBuilder.java
Normale Datei
47
src/main/java/tsp/headdb/core/util/PaginationBuilder.java
Normale Datei
@ -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();
|
||||
}
|
||||
|
||||
}
|
36
src/main/java/tsp/headdb/core/util/Sounds.java
Normale Datei
36
src/main/java/tsp/headdb/core/util/Sounds.java
Normale Datei
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -1,357 +1,198 @@
|
||||
package tsp.headdb.core.util;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import me.clip.placeholderapi.PlaceholderAPI;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.configuration.ConfigurationSection;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import org.bukkit.inventory.meta.SkullMeta;
|
||||
import org.bukkit.profile.PlayerProfile;
|
||||
import org.bukkit.profile.PlayerTextures;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import tsp.headdb.HeadDB;
|
||||
import tsp.headdb.core.api.HeadAPI;
|
||||
import tsp.headdb.core.economy.BasicEconomyProvider;
|
||||
import tsp.headdb.core.hook.Hooks;
|
||||
import tsp.headdb.implementation.category.Category;
|
||||
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 tsp.headdb.core.config.ConfigData;
|
||||
import tsp.headdb.core.economy.EconomyProvider;
|
||||
import tsp.headdb.api.model.Head;
|
||||
import tsp.headdb.api.model.LocalHead;
|
||||
|
||||
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.URL;
|
||||
import java.net.URI;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author TheSilentPro (Silent)
|
||||
*/
|
||||
public class Utils {
|
||||
|
||||
private static final HeadDB instance = HeadDB.getInstance();
|
||||
private static Properties properties = null;
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(Utils.class);
|
||||
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() {
|
||||
if (properties == null) {
|
||||
InputStream is = instance.getResource("build.properties");
|
||||
if (is == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
@SuppressWarnings("DataFlowIssue")
|
||||
public static ItemStack asItem(Head head) {
|
||||
ItemStack item = new ItemStack(Material.PLAYER_HEAD);
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
meta.setDisplayName(ChatColor.GOLD + head.getName());
|
||||
|
||||
try {
|
||||
properties = new Properties();
|
||||
properties.load(is);
|
||||
} catch (IOException ex) {
|
||||
instance.getLog().debug("Failed to load build properties: " + ex.getMessage());
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
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)));
|
||||
|
||||
return Optional.ofNullable(properties.getProperty("version"));
|
||||
}
|
||||
|
||||
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 {
|
||||
return Optional.of(UUID.fromString(raw));
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
public static String translateTitle(String raw, int size, String category, @Nullable String query) {
|
||||
return StringUtils.colorize(raw)
|
||||
.replace("%size%", String.valueOf(size))
|
||||
.replace("%category%", category)
|
||||
.replace("%query%", (query != null ? query : "%query%"));
|
||||
}
|
||||
|
||||
@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();
|
||||
//noinspection DataFlowIssue
|
||||
meta.setDisplayName("");
|
||||
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);
|
||||
}
|
||||
}));
|
||||
if (config.shouldIncludeMoreInfo()) {
|
||||
head.getCategory().ifPresent(category -> {
|
||||
if (category.isEmpty()) {
|
||||
lore.add(ChatColor.GRAY + "Category » " + ChatColor.GOLD + category);
|
||||
}
|
||||
|
||||
main.open(player);
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
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 {
|
||||
profile = Bukkit.createPlayerProfile(null, head.getName());
|
||||
} catch (IllegalArgumentException ex) {
|
||||
// Head may contain special characters(@,!,<,>) that are not allowed in a PlayerProfile.
|
||||
// Additionally, spaces are also removed as the profile name should not be visible to players.
|
||||
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);
|
||||
}
|
||||
|
||||
PlayerTextures textures = profile.getTextures();
|
||||
String url = new String(Base64.getDecoder().decode(head.getTexture().orElseThrow(() -> new IllegalArgumentException("Head texture must not be null!"))));
|
||||
try {
|
||||
textures.setSkin(URI.create(url.substring("{\"textures\":{\"SKIN\":{\"url\":\"".length(), url.length() - "\"}}}".length())).toURL());
|
||||
} catch (MalformedURLException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
profile.setTextures(textures);
|
||||
|
||||
if (meta instanceof SkullMeta skullMeta) {
|
||||
skullMeta.setOwnerProfile(profile);
|
||||
item.setItemMeta(skullMeta);
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
@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);
|
||||
public static void purchaseHead(Player player, Head head, int amount) {
|
||||
EconomyProvider economyProvider = HeadDB.getInstance().getEconomyProvider();
|
||||
if (economyProvider == null) {
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
if (category != null && instance.getConfig().getBoolean("requireCategoryPermission") && !player.hasPermission("headdb.category." + category.getName())) {
|
||||
instance.getLocalization().sendMessage(player.getUniqueId(), "noPermission");
|
||||
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 (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()));
|
||||
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 -> {
|
||||
if (command.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (HeadDB.getInstance().isPAPI()) {
|
||||
command = PlaceholderAPI.setPlaceholders(player, command);
|
||||
}
|
||||
|
||||
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
|
||||
});
|
||||
} else {
|
||||
HeadDB.getInstance().getLocalization().sendMessage(player, "invalidFunds", msg -> msg.replace("%name%", head.getName()));
|
||||
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)));
|
||||
}
|
||||
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);
|
||||
HeadDB.getInstance().getConfig().getStringList("commands.purchase").forEach(command -> {
|
||||
if (command.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
if (Hooks.PAPI.enabled()) {
|
||||
command = PlaceholderAPI.setPlaceholders(player, command);
|
||||
}
|
||||
|
||||
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static Optional<String> getTexture(ItemStack head) {
|
||||
ItemMeta meta = head.getItemMeta();
|
||||
if (meta == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
try {
|
||||
Field profileField = meta.getClass().getDeclaredField("profile");
|
||||
profileField.setAccessible(true);
|
||||
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 double getHeadCost(Head head) {
|
||||
if (head instanceof LocalHead) { // Local heads have only one cost
|
||||
return config.getLocalCost();
|
||||
} 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
|
||||
return config.getCategoryCosts().get(head.getCategory().orElse("?"));
|
||||
} else { // Get the default cost for the head.
|
||||
return config.getDefaultCost();
|
||||
}
|
||||
}
|
||||
|
||||
public static ItemStack asItem(UUID receiver, Head head) {
|
||||
TranslatableLocalization localization = HeadDB.getInstance().getLocalization();
|
||||
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;
|
||||
public static String colorize(String s) {
|
||||
return ChatColor.translateAlternateColorCodes('&', s);
|
||||
}
|
||||
|
||||
public static int resolveInt(String raw) {
|
||||
try {
|
||||
return Integer.parseInt(raw);
|
||||
} catch (NumberFormatException nfe) {
|
||||
return 1;
|
||||
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 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;
|
||||
public static boolean matches(String provided, String query) {
|
||||
return ChatColor.stripColor(provided.toLowerCase()).contains(query.toLowerCase());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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) {}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
package tsp.headdb.implementation.requester;
|
||||
|
||||
public record Response(String response, int code, String date) {}
|
@ -1 +0,0 @@
|
||||
version=${project.version}
|
@ -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:
|
||||
enabled: false
|
||||
provider: "VAULT" # Supported: VAULT
|
||||
format: "##.##"
|
||||
cost:
|
||||
# The cost of a local (Player) head.
|
||||
localCost: 100
|
||||
# Default cost for head.
|
||||
defaultCost: 100
|
||||
# Default category cost.
|
||||
defaultCategoryCost: 100
|
||||
# Cost of categories.
|
||||
categoryCost:
|
||||
alphabet: 100
|
||||
animals: 100
|
||||
blocks: 100
|
||||
@ -25,70 +20,36 @@ economy:
|
||||
miscellaneous: 100
|
||||
monsters: 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.
|
||||
commands:
|
||||
# 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:
|
||||
- ""
|
||||
|
||||
# Graphical User Interface customization
|
||||
gui:
|
||||
main:
|
||||
fill:
|
||||
material: "BLACK_STAINED_GLASS_PANE"
|
||||
name: ""
|
||||
lore: []
|
||||
# Categories are set in the slots 20-24 & 29 - 33. You can add specific ones here to relocate them.
|
||||
# Note: that the slots start from 0
|
||||
category:
|
||||
alphabet: 20
|
||||
animals: 21
|
||||
blocks: 22
|
||||
decoration: 23
|
||||
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."
|
||||
# Block heads from showing up in the menu.
|
||||
blockedHeads:
|
||||
- ""
|
||||
|
||||
# If enabled categories will require a permission to be used.
|
||||
# Permission: headdb.category.<category>
|
||||
# Local Heads permission: headdb.category.local.* OR headdb.category.local.<PLAYER UUID>
|
||||
requireCategoryPermission: false
|
||||
|
||||
# If the menu should show more info on the head such as: category, contributors, collections, publish date
|
||||
moreInfo: true
|
||||
|
||||
# If the lore (ID,Tags) should be included in the inventory item given to players.
|
||||
includeLore: false
|
||||
|
||||
# If the original fetching fails and this is enabled,
|
||||
# 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.
|
||||
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
|
||||
fallback: true
|
@ -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!"
|
||||
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."
|
||||
invalidTarget: "&cInvalid target: &e%name%"
|
||||
invalidCategory: "&cInvalid Category!"
|
||||
invalidCategory: "&cInvalid category!"
|
||||
invalidNumber: "&e%name% &cis not a number!"
|
||||
invalidPageIndex: "&cThat page is out of bounds! Max: %pages%"
|
||||
noAccessFavorites: "&cYou do not have access to favorites!"
|
||||
|
||||
openDatabase: "" # Intentionally empty. Sent when the main gui is opened
|
||||
updateDatabase: "&7Updating..."
|
||||
updateDatabaseDone: "&7Done! Total Heads: &6%size%"
|
||||
reloadCommand: "&7Reloading, please wait before using the plugin..."
|
||||
reloadCommandDone: "&7Reload Complete!"
|
||||
searchCommand: "&7Searching for heads matching: &6%query%"
|
||||
searchCommandResults: "&7Found &6%size% &7matches!"
|
||||
giveCommand: "&7Gave &6x%size% %name% &7to &6%receiver%"
|
||||
itemTexture: "&7Texture: &6%texture%"
|
||||
itemNoTexture: "&cThis item does not have a texture!"
|
||||
copyTexture: "&6Click to copy texture!"
|
||||
command:
|
||||
search:
|
||||
wait: "&7Searching for: &6%name%&7! Please wait..."
|
||||
done: "&7Found &6%size% &7heads!"
|
||||
invalid: "&cNo heads matching: &e%name%"
|
||||
give:
|
||||
invalid: "&cInvalid head: &e%name%"
|
||||
done: "&7Gave &a%amount%&7x &6%name% &7to &6%player%"
|
||||
|
||||
openDatabase: "" # Intentionally empty by default. Sent when the main gui is opened.
|
||||
notReadyDatabase: "&cPlease wait a few seconds..."
|
||||
updateStarted: "&7Updating..."
|
||||
updateFinished: "&7Done! Total Heads: &6%size%"
|
||||
|
||||
addedFavorite: "&7Added &6%name% &7to your favorites!"
|
||||
removedFavorite: "&7Removed &6%name% &7from your favorites!"
|
||||
noFavorites: "&cYou have no favorite heads!"
|
||||
|
||||
# Only shown if economy is enabled
|
||||
processPayment: "&7Purchasing &6%name% &7for &6%cost%&7! Please wait..."
|
||||
completePayment: "&7Received &6%name% &7for &6%cost%"
|
||||
invalidFunds: "&cYou do not have enough to buy &6%name%&c!"
|
||||
# Only used if economy is enabled.
|
||||
processPayment: "&7Purchasing &a%amount% &7x &6%name% &7for &6%cost%&7..."
|
||||
completePayment: "&7Bought &a%amount%&7x &6%name% &7for &6%cost%"
|
||||
invalidFunds: "&cYou cannot afford &6%name%&c!"
|
||||
|
||||
invalidLanguage: "&cInvalid Language! Available: &e%languages%"
|
||||
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%"
|
||||
languageChanged: "&7Your language was set to: &6%language%"
|
@ -1,11 +1,15 @@
|
||||
# noinspection YAMLSchemaValidation
|
||||
name: ${project.name}
|
||||
description: ${project.description}
|
||||
author: TheSilentPro (Silent)
|
||||
|
||||
main: tsp.headdb.HeadDB
|
||||
version: ${project.version}
|
||||
softdepend: ["Vault"]
|
||||
softdepend: ["PlaceholderAPI", "Vault"]
|
||||
api-version: 1.19
|
||||
author: TheSilentPro (Silent)
|
||||
libraries:
|
||||
- "org.xerial:sqlite-jdbc:3.47.0.0"
|
||||
|
||||
spigot-id: 84967
|
||||
|
||||
commands:
|
||||
@ -24,11 +28,11 @@ permissions:
|
||||
headdb.command.update: true
|
||||
headdb.command.reload: true
|
||||
headdb.command.language: true
|
||||
headdb.command.settings: true
|
||||
headdb.command.texture: true
|
||||
headdb.favorites: true
|
||||
headdb.local: true
|
||||
headdb.category.local.*: true
|
||||
headdb.category.*: true
|
||||
headdb.category.all: true
|
||||
headdb.command.open:
|
||||
default: op
|
||||
headdb.command.search:
|
||||
@ -41,13 +45,11 @@ permissions:
|
||||
default: op
|
||||
headdb.command.language:
|
||||
default: op
|
||||
headdb.command.settings:
|
||||
default: op
|
||||
headdb.command.texture:
|
||||
default: op
|
||||
headdb.favorites:
|
||||
default: op
|
||||
headdb.local:
|
||||
headdb.category.local.*:
|
||||
default: op
|
||||
headdb.category.*:
|
||||
default: op
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren