Mirror von
https://github.com/TheSilentPro/HeadDB.git
synchronisiert 2024-12-26 02:50:07 +01:00
lots of improvements and bugfixes.
Dieser Commit ist enthalten in:
Ursprung
7ef5e30694
Commit
e4691102ae
22
pom.xml
22
pom.xml
@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>tsp.headdb</groupId>
|
||||
<artifactId>HeadDB</artifactId>
|
||||
<version>5.0.0-rc.7</version>
|
||||
<version>5.0.0-rc.8</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>HeadDB</name>
|
||||
@ -55,13 +55,13 @@
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.20.1-R0.1-SNAPSHOT</version>
|
||||
<version>1.20.2-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.mojang</groupId>
|
||||
<artifactId>authlib</artifactId>
|
||||
<version>1.5.21</version>
|
||||
<version>4.0.43</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
@ -75,20 +75,26 @@
|
||||
<dependency>
|
||||
<groupId>com.github.TheSilentPro</groupId>
|
||||
<artifactId>NexusLib</artifactId>
|
||||
<version>c01a0a0a7d</version>
|
||||
<version>9d8b16b770</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.TheSilentPro</groupId>
|
||||
<artifactId>Warehouse</artifactId>
|
||||
<version>882b42fc75</version>
|
||||
<version>b228e4f8b1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.wesjd</groupId>
|
||||
<artifactId>anvilgui</artifactId>
|
||||
<version>1.7.0-SNAPSHOT</version>
|
||||
<version>1.9.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.github.TheSilentPro</groupId>
|
||||
<artifactId>HelperLite</artifactId>
|
||||
<version>775582f23b</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Soft Dependencies -->
|
||||
<!--suppress VulnerableLibrariesLocal -->
|
||||
<dependency>
|
||||
<groupId>com.github.MilkBowl</groupId>
|
||||
<artifactId>VaultAPI</artifactId>
|
||||
@ -98,7 +104,7 @@
|
||||
<dependency>
|
||||
<groupId>me.clip</groupId>
|
||||
<artifactId>placeholderapi</artifactId>
|
||||
<version>2.11.1</version>
|
||||
<version>2.11.5</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
@ -183,7 +189,7 @@
|
||||
<additionalDependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.20.1-R0.1-SNAPSHOT</version>
|
||||
<version>1.20.2-R0.1-SNAPSHOT</version>
|
||||
</additionalDependency>
|
||||
</additionalDependencies>
|
||||
</configuration>
|
||||
|
@ -7,17 +7,25 @@ import tsp.headdb.core.economy.VaultProvider;
|
||||
import tsp.headdb.core.storage.Storage;
|
||||
import tsp.headdb.core.task.UpdateTask;
|
||||
import tsp.headdb.core.util.HeadDBLogger;
|
||||
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.nexuslib.util.PluginUtils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLConnection;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class HeadDB extends NexusPlugin {
|
||||
|
||||
@ -27,22 +35,27 @@ public class HeadDB extends NexusPlugin {
|
||||
private Storage storage;
|
||||
private BasicEconomyProvider economyProvider;
|
||||
private CommandManager commandManager;
|
||||
private Task updateTask;
|
||||
|
||||
@Override
|
||||
public void onStart(NexusPlugin nexusPlugin) {
|
||||
instance = this;
|
||||
HelperLite.init(this);
|
||||
|
||||
instance.saveDefaultConfig();
|
||||
instance.logger = new HeadDBLogger(getConfig().getBoolean("debug"));
|
||||
instance.logger.info("Loading HeadDB - " + instance.getDescription().getVersion());
|
||||
|
||||
new UpdateTask(getConfig().getLong("refresh", 86400L)).schedule(this);
|
||||
instance.logger.info("Loaded " + loadLocalization() + " languages!");
|
||||
|
||||
instance.initStorage();
|
||||
instance.initEconomy();
|
||||
|
||||
startUpdateTask();
|
||||
|
||||
new PaneListener(this);
|
||||
|
||||
// TODO: Commands helperlite
|
||||
instance.commandManager = new CommandManager();
|
||||
loadCommands();
|
||||
|
||||
@ -66,8 +79,38 @@ public class HeadDB extends NexusPlugin {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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(this.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");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Loaders
|
||||
|
||||
private void initMetrics() {
|
||||
Metrics metrics = new Metrics(this, 9152);
|
||||
|
||||
@ -80,19 +123,8 @@ public class HeadDB extends NexusPlugin {
|
||||
}));
|
||||
}
|
||||
|
||||
private void ensureLatestVersion() {
|
||||
PluginUtils.isLatestVersion(this, 84967, latest -> {
|
||||
if (Boolean.FALSE.equals(latest)) {
|
||||
instance.logger.warning("There is a new update available for HeadDB on spigot!");
|
||||
instance.logger.warning("Download: https://www.spigotmc.org/resources/84967");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Loaders
|
||||
|
||||
private void initStorage() {
|
||||
storage = new Storage(getConfig().getInt("storage.threads"));
|
||||
storage = new Storage();
|
||||
storage.getPlayerStorage().init();
|
||||
}
|
||||
|
||||
@ -156,6 +188,10 @@ public class HeadDB extends NexusPlugin {
|
||||
|
||||
// Getters
|
||||
|
||||
public Optional<Task> getUpdateTask() {
|
||||
return Optional.ofNullable(updateTask);
|
||||
}
|
||||
|
||||
public Storage getStorage() {
|
||||
return storage;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import tsp.headdb.core.storage.HeadDBThreadFactory;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import java.io.*;
|
||||
@ -132,8 +133,7 @@ class Metrics {
|
||||
/** The version of the Metrics class. */
|
||||
public static final String METRICS_VERSION = "3.0.0";
|
||||
|
||||
private static final ScheduledExecutorService scheduler =
|
||||
Executors.newScheduledThreadPool(1, task -> new Thread(task, "bStats-Metrics"));
|
||||
private static final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(HeadDBThreadFactory.FACTORY);
|
||||
|
||||
private static final String REPORT_URL = "https://bStats.org/api/v2/data/%s";
|
||||
|
||||
|
@ -9,6 +9,7 @@ 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.*;
|
||||
@ -163,14 +164,16 @@ public final class HeadAPI {
|
||||
* @return {@link Set<Head> Favorite Heads}
|
||||
*/
|
||||
@Nonnull
|
||||
public static List<Head> getFavoriteHeads(UUID player) {
|
||||
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;
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,8 +1,6 @@
|
||||
package tsp.headdb.core.command;
|
||||
|
||||
import java.util.Arrays;
|
||||
import net.wesjd.anvilgui.AnvilGUI;
|
||||
import net.wesjd.anvilgui.AnvilGUI.Builder;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.command.Command;
|
||||
@ -24,10 +22,7 @@ import tsp.nexuslib.util.StringUtils;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class CommandMain extends HeadDBCommand implements CommandExecutor, TabCompleter {
|
||||
|
@ -1,9 +1,10 @@
|
||||
package tsp.headdb.core.command;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
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 {
|
||||
|
||||
@ -14,10 +15,14 @@ public class CommandUpdate extends SubCommand {
|
||||
@Override
|
||||
public void handle(CommandSender sender, String[] args) {
|
||||
getLocalization().sendMessage(sender, "updateDatabase");
|
||||
HeadAPI.getDatabase().update((time, result) -> {
|
||||
HeadDB.getInstance().getLog().debug("Database Updated! Heads: " + result.values().size() + " | Took: " + time + "ms");
|
||||
Bukkit.getScheduler().runTask(HeadDB.getInstance(), () -> getLocalization().sendMessage(sender, "updateDatabaseDone", msg -> msg.replace("%size%", String.valueOf(result.values().size()))));
|
||||
});
|
||||
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,18 +1,18 @@
|
||||
package tsp.headdb.core.economy;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import tsp.helperlite.scheduler.promise.Promise;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public interface BasicEconomyProvider {
|
||||
|
||||
CompletableFuture<Boolean> canPurchase(Player player, BigDecimal cost);
|
||||
Promise<Boolean> canPurchase(Player player, BigDecimal cost);
|
||||
|
||||
CompletableFuture<Boolean> withdraw(Player player, BigDecimal amount);
|
||||
Promise<Boolean> withdraw(Player player, BigDecimal amount);
|
||||
|
||||
default CompletableFuture<Boolean> purchase(Player player, BigDecimal amount) {
|
||||
return canPurchase(player, amount).thenCompose(result -> result ? withdraw(player, amount) : CompletableFuture.completedFuture(false));
|
||||
default Promise<Boolean> purchase(Player player, BigDecimal amount) {
|
||||
return canPurchase(player, amount).thenComposeAsync(result -> result ? withdraw(player, amount) : Promise.completed(false));
|
||||
}
|
||||
|
||||
void init();
|
||||
|
@ -5,24 +5,24 @@ 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 java.math.BigDecimal;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class VaultProvider implements BasicEconomyProvider {
|
||||
|
||||
private Economy economy;
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> canPurchase(Player player, BigDecimal cost) {
|
||||
public Promise<Boolean> canPurchase(Player player, BigDecimal cost) {
|
||||
double effectiveCost = cost.doubleValue();
|
||||
return CompletableFuture.supplyAsync(() -> economy.has(player, effectiveCost >= 0 ? effectiveCost : 0));
|
||||
return Promise.supplyingAsync(() -> economy.has(player, effectiveCost >= 0 ? effectiveCost : 0));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> withdraw(Player player, BigDecimal amount) {
|
||||
public Promise<Boolean> withdraw(Player player, BigDecimal amount) {
|
||||
double effectiveCost = amount.doubleValue();
|
||||
return CompletableFuture.supplyAsync(() -> economy.withdrawPlayer(player, effectiveCost >= 0 ? effectiveCost : 0).transactionSuccess());
|
||||
return Promise.supplyingAsync(() -> economy.withdrawPlayer(player, effectiveCost >= 0 ? effectiveCost : 0).transactionSuccess());
|
||||
}
|
||||
|
||||
|
||||
|
@ -44,7 +44,7 @@ public class PlayerStorage extends SerializableFileDataManager<HashSet<PlayerDat
|
||||
|
||||
public void init() {
|
||||
load().whenComplete((data, ex) -> {
|
||||
for (PlayerData entry : data) {
|
||||
for (PlayerData entry : data.orElse(new HashSet<>())) {
|
||||
players.put(entry.uniqueId(), entry);
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package tsp.headdb.core.storage;
|
||||
|
||||
import tsp.headdb.HeadDB;
|
||||
import tsp.helperlite.Schedulers;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.concurrent.Executor;
|
||||
@ -11,8 +12,8 @@ public class Storage {
|
||||
private final Executor executor;
|
||||
private final PlayerStorage playerStorage;
|
||||
|
||||
public Storage(int threads) {
|
||||
executor = Executors.newFixedThreadPool(threads, HeadDBThreadFactory.FACTORY);
|
||||
public Storage() {
|
||||
executor = Schedulers.async();
|
||||
validateDataDirectory();
|
||||
playerStorage = new PlayerStorage(HeadDB.getInstance(), this);
|
||||
}
|
||||
|
@ -4,51 +4,44 @@ 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 tsp.nexuslib.task.Task;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class UpdateTask implements Task {
|
||||
|
||||
private final long interval;
|
||||
|
||||
public UpdateTask(long interval) {
|
||||
this.interval = interval;
|
||||
}
|
||||
public class UpdateTask implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
HeadAPI.getDatabase().update((time, heads) -> {
|
||||
int size = 0;
|
||||
for (List<Head> list : heads.values()) {
|
||||
for (Head ignored : list) {
|
||||
size++;
|
||||
}
|
||||
}
|
||||
|
||||
HeadAPI.getDatabase().update().thenAcceptAsync(result -> {
|
||||
HeadDB instance = HeadDB.getInstance();
|
||||
String providerName = HeadAPI.getDatabase().getRequester().getProvider().name();
|
||||
|
||||
HeadDB.getInstance().getLog().debug("Fetched: " + size + " Heads | Provider: " + providerName + " | Time: " + time + "ms (" + TimeUnit.MILLISECONDS.toSeconds(time) + "s)");
|
||||
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(
|
||||
heads,
|
||||
result.heads(),
|
||||
providerName,
|
||||
time));
|
||||
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!"));
|
||||
});
|
||||
HeadDB.getInstance().getStorage().getPlayerStorage().backup();
|
||||
HeadDB.getInstance().getLog().debug("UpdateTask finished!");
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getRepeatInterval() {
|
||||
return interval;
|
||||
}
|
||||
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++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAsync() {
|
||||
return true;
|
||||
return n;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,63 +1,12 @@
|
||||
package tsp.headdb.core.util;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import tsp.nexuslib.util.StringUtils;
|
||||
import tsp.nexuslib.logger.NexusLogger;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class HeadDBLogger {
|
||||
|
||||
private final boolean debug;
|
||||
public class HeadDBLogger extends NexusLogger {
|
||||
|
||||
public HeadDBLogger(boolean debug) {
|
||||
this.debug = debug;
|
||||
}
|
||||
public void info(String message) {
|
||||
this.log(LogLevel.INFO, message);
|
||||
}
|
||||
|
||||
public void warning(String message) {
|
||||
this.log(LogLevel.WARNING, message);
|
||||
}
|
||||
|
||||
public void error(String message) {
|
||||
this.log(LogLevel.ERROR, message);
|
||||
}
|
||||
|
||||
public void debug(String message) {
|
||||
this.log(LogLevel.DEBUG, message);
|
||||
}
|
||||
|
||||
public void trace(String message) {
|
||||
this.log(LogLevel.TRACE, message);
|
||||
}
|
||||
|
||||
public void log(LogLevel level, String message) {
|
||||
if ((level == LogLevel.DEBUG || level == LogLevel.TRACE) && !debug) {
|
||||
return;
|
||||
}
|
||||
Bukkit.getConsoleSender().sendMessage(StringUtils.colorize("&cHeadDB &8>> " + level.getColor() + "[" + level.name() + "]: " + message));
|
||||
}
|
||||
|
||||
public boolean isDebug() {
|
||||
return this.debug;
|
||||
}
|
||||
|
||||
public enum LogLevel {
|
||||
INFO,
|
||||
WARNING,
|
||||
ERROR,
|
||||
DEBUG,
|
||||
TRACE;
|
||||
|
||||
public String getColor() {
|
||||
return switch (this) {
|
||||
case INFO -> "\u001b[32m";
|
||||
case WARNING -> "\u001b[33m";
|
||||
case ERROR -> "\u001b[31m";
|
||||
case DEBUG -> "\u001b[36m";
|
||||
case TRACE -> "\u001b[35m";
|
||||
};
|
||||
}
|
||||
super("HeadDB", debug);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,15 +10,22 @@ 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 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;
|
||||
|
||||
@ -27,8 +34,9 @@ import javax.annotation.Nullable;
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import java.lang.reflect.Field;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class Utils {
|
||||
|
||||
@ -108,31 +116,36 @@ public class Utils {
|
||||
}
|
||||
|
||||
public static void openFavoritesMenu(Player player) {
|
||||
List<Head> heads = HeadAPI.getFavoriteHeads(player.getUniqueId());
|
||||
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;
|
||||
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 (fe.isLeftClick()) {
|
||||
int amount = 1;
|
||||
if (fe.isShiftClick()) {
|
||||
amount = 64;
|
||||
}
|
||||
|
||||
Utils.purchase(player, head, amount);
|
||||
} else if (fe.isRightClick()) {
|
||||
HeadDB.getInstance().getStorage().getPlayerStorage().removeFavorite(player.getUniqueId(), head.getTexture());
|
||||
HeadDB.getInstance().getLocalization().sendMessage(player, "removedFavorite", msg -> msg.replace("%name%", head.getName()));
|
||||
openFavoritesMenu(player);
|
||||
}
|
||||
}));
|
||||
main.open(player);
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
main.open(player);
|
||||
}
|
||||
|
||||
@ParametersAreNonnullByDefault
|
||||
@ -166,10 +179,10 @@ public class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
private static CompletableFuture<Boolean> processPayment(Player player, Head head, int amount) {
|
||||
private static Promise<Boolean> processPayment(Player player, Head head, int amount) {
|
||||
Optional<BasicEconomyProvider> optional = HeadDB.getInstance().getEconomyProvider();
|
||||
if (optional.isEmpty()) {
|
||||
return CompletableFuture.completedFuture(true); // No economy, the head is free
|
||||
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
|
||||
@ -177,8 +190,10 @@ public class Utils {
|
||||
.replace("%amount%", String.valueOf(amount))
|
||||
.replace("%cost%", HeadDB.getInstance().getDecimalFormat().format(cost))
|
||||
);
|
||||
return optional.get().purchase(player, cost).thenApply(success -> {
|
||||
Bukkit.getScheduler().runTask(HeadDB.getInstance(), () -> {
|
||||
|
||||
try (Promise<Boolean> economyPromise = optional.get().purchase(player, cost)) {
|
||||
// TODO: Might not need to be sync.
|
||||
return economyPromise.thenApplySync((success) -> {
|
||||
if (success) {
|
||||
HeadDB.getInstance().getLocalization().sendMessage(player, "completePayment", msg -> msg
|
||||
.replace("%name%", head.getName())
|
||||
@ -186,46 +201,38 @@ public class Utils {
|
||||
} else {
|
||||
HeadDB.getInstance().getLocalization().sendMessage(player, "invalidFunds", msg -> msg.replace("%name%", head.getName()));
|
||||
}
|
||||
return success;
|
||||
});
|
||||
return success;
|
||||
|
||||
/* Note: Issues caused by sync call to async event but when run async above method fucks up.
|
||||
Bukkit.getScheduler().runTaskAsynchronously(HeadDB.getInstance(), () -> {
|
||||
HeadPurchaseEvent event = new HeadPurchaseEvent(player, head, cost, success);
|
||||
Bukkit.getPluginManager().callEvent(event);
|
||||
});
|
||||
return true;
|
||||
*/
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
HeadDB.getInstance().getLog().severe("Failed to process payment: " + ex.getMessage());
|
||||
return Promise.exceptionally(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void purchase(Player player, Head head, int amount) {
|
||||
processPayment(player, head, amount).whenComplete((success, ex) -> {
|
||||
if (ex != null) {
|
||||
HeadDB.getInstance().getLog().error("Failed to purchase head '" + head.getName() + "' for player: " + player.getName());
|
||||
ex.printStackTrace();
|
||||
} else {
|
||||
// Bukkit API, therefore task is ran sync.
|
||||
Bukkit.getScheduler().runTask(HeadDB.getInstance(), () -> {
|
||||
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);
|
||||
});
|
||||
// Bukkit API - Has to be sync.
|
||||
try (Promise<Boolean> paymentPromise = processPayment(player, head, amount)) {
|
||||
paymentPromise.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);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static Optional<String> getTexture(ItemStack head) {
|
||||
@ -252,6 +259,51 @@ public class Utils {
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
if (ServerVersion.getVersion().orElse(ServerVersion.v_1_20_1).isOlderThan(ServerVersion.v_1_20_2)) {
|
||||
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 int resolveInt(String raw) {
|
||||
try {
|
||||
return Integer.parseInt(raw);
|
||||
|
@ -1,19 +1,11 @@
|
||||
package tsp.headdb.implementation.head;
|
||||
|
||||
import com.mojang.authlib.GameProfile;
|
||||
import com.mojang.authlib.properties.Property;
|
||||
import org.bukkit.Material;
|
||||
import org.bukkit.inventory.ItemStack;
|
||||
import org.bukkit.inventory.meta.ItemMeta;
|
||||
import tsp.headdb.HeadDB;
|
||||
import tsp.headdb.core.util.Utils;
|
||||
import tsp.headdb.implementation.category.Category;
|
||||
import tsp.nexuslib.builder.ItemBuilder;
|
||||
import tsp.nexuslib.localization.TranslatableLocalization;
|
||||
import tsp.nexuslib.util.Validate;
|
||||
|
||||
import javax.annotation.ParametersAreNonnullByDefault;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
public class Head {
|
||||
@ -47,26 +39,7 @@ public class Head {
|
||||
|
||||
public ItemStack getItem(UUID receiver) {
|
||||
if (item == null) {
|
||||
TranslatableLocalization localization = HeadDB.getInstance().getLocalization();
|
||||
item = new ItemBuilder(Material.PLAYER_HEAD)
|
||||
.name(localization.getMessage(receiver, "menu.head.name").orElse("&e" + name.toUpperCase(Locale.ROOT)).replace("%name%", name))
|
||||
.setLore("&cID: " + id, "&7Tags: &e" + tags)
|
||||
.build();
|
||||
|
||||
ItemMeta meta = item.getItemMeta();
|
||||
GameProfile profile = new GameProfile(uniqueId, name);
|
||||
profile.getProperties().put("textures", new Property("textures", texture));
|
||||
try {
|
||||
//noinspection DataFlowIssue
|
||||
Field profileField = meta.getClass().getDeclaredField("profile");
|
||||
profileField.setAccessible(true);
|
||||
profileField.set(meta, profile);
|
||||
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) {
|
||||
//Log.error("Could not set skull owner for " + uuid.toString() + " | Stack Trace:");
|
||||
ex.printStackTrace();
|
||||
}
|
||||
|
||||
item.setItemMeta(meta);
|
||||
item = Utils.asItem(receiver, this);
|
||||
}
|
||||
|
||||
return item.clone(); // Return clone that changes are not reflected
|
||||
|
@ -1,53 +1,59 @@
|
||||
package tsp.headdb.implementation.head;
|
||||
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
import org.bukkit.scheduler.BukkitScheduler;
|
||||
import tsp.headdb.implementation.category.Category;
|
||||
import tsp.headdb.implementation.requester.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;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
public class HeadDatabase {
|
||||
|
||||
private final JavaPlugin plugin;
|
||||
private final BukkitScheduler scheduler;
|
||||
private final Requester requester;
|
||||
private final ConcurrentHashMap<Category, List<Head>> heads;
|
||||
private long timestamp;
|
||||
|
||||
public HeadDatabase(JavaPlugin plugin, HeadProvider provider) {
|
||||
this.plugin = plugin;
|
||||
this.scheduler = plugin.getServer().getScheduler();
|
||||
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 void getHeadsNoCache(BiConsumer<Long, Map<Category, List<Head>>> heads) {
|
||||
getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
public Promise<HeadResult> getHeadsNoCache() {
|
||||
return Promise.supplyingAsync(() -> {
|
||||
long start = System.currentTimeMillis();
|
||||
Map<Category, List<Head>> result = new HashMap<>();
|
||||
for (Category category : Category.VALUES) {
|
||||
requester.fetchAndResolve(category, response -> result.put(category, response));
|
||||
result.put(category, requester.fetchAndResolve(category));
|
||||
}
|
||||
|
||||
heads.accept(System.currentTimeMillis() - start, result);
|
||||
return new HeadResult(System.currentTimeMillis() - start, result);
|
||||
});
|
||||
}
|
||||
|
||||
public void update(BiConsumer<Long, Map<Category, List<Head>>> fetched) {
|
||||
getHeadsNoCache((elapsed, result) -> {
|
||||
heads.putAll(result);
|
||||
timestamp = System.currentTimeMillis();
|
||||
fetched.accept(elapsed, result);
|
||||
public Promise<HeadResult> update() {
|
||||
return Promise.start()
|
||||
.thenComposeAsync(compose -> getHeadsNoCache())
|
||||
.thenApplyAsync(result -> {
|
||||
heads.clear();
|
||||
heads.putAll(result.heads());
|
||||
timestamp = System.currentTimeMillis();
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
@ -59,10 +65,6 @@ public class HeadDatabase {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
public BukkitScheduler getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public Requester getRequester() {
|
||||
return requester;
|
||||
}
|
||||
|
11
src/main/java/tsp/headdb/implementation/head/HeadResult.java
Normale Datei
11
src/main/java/tsp/headdb/implementation/head/HeadResult.java
Normale Datei
@ -0,0 +1,11 @@
|
||||
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) {}
|
@ -18,8 +18,14 @@ import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
@ -30,51 +36,52 @@ public class Requester {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public void fetchAndResolve(Category category, Consumer<List<Head>> heads) {
|
||||
public List<Head> fetchAndResolve(Category category) {
|
||||
try {
|
||||
fetch(category, response -> {
|
||||
List<Head> result = new ArrayList<>();
|
||||
if (response.code() != 200) {
|
||||
heads.accept(result);
|
||||
return;
|
||||
}
|
||||
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();
|
||||
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;
|
||||
}
|
||||
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
|
||||
));
|
||||
}
|
||||
|
||||
heads.accept(result);
|
||||
});
|
||||
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;
|
||||
fetchAndResolve(category, heads);
|
||||
return fetchAndResolve(category);
|
||||
} else {
|
||||
HeadDB.getInstance().getLog().error("Could not fetch heads from any provider!");
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void fetch(Category category, Consumer<Response> response) throws IOException {
|
||||
public Response fetch(Category category) throws IOException {
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(provider.getFormattedUrl(category)).openConnection();
|
||||
connection.setConnectTimeout(5000);
|
||||
connection.setRequestMethod("GET");
|
||||
@ -88,10 +95,9 @@ public class Requester {
|
||||
builder.append(line);
|
||||
}
|
||||
|
||||
response.accept(new Response(builder.toString(), connection.getResponseCode(), connection.getHeaderField("date")));
|
||||
connection.disconnect();
|
||||
return new Response(builder.toString(), connection.getResponseCode(), connection.getHeaderField("date"));
|
||||
}
|
||||
|
||||
connection.disconnect();
|
||||
}
|
||||
|
||||
public HeadProvider getProvider() {
|
||||
|
@ -90,10 +90,5 @@ blockedHeads:
|
||||
ids:
|
||||
- -1
|
||||
|
||||
# Storage Options
|
||||
storage:
|
||||
# Amount of threads in the executor pool used for storage.
|
||||
threads: 2
|
||||
|
||||
# Debug Mode
|
||||
debug: false
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren