3
0
Mirror von https://github.com/TheSilentPro/HeadDB.git synchronisiert 2024-12-27 11:20:05 +01:00

lots of refactoring (untested)

Dieser Commit ist enthalten in:
Silent 2022-11-09 03:32:30 +01:00
Ursprung a57a61c904
Commit 1762d6a787
32 geänderte Dateien mit 647 neuen und 295 gelöschten Zeilen

45
pom.xml
Datei anzeigen

@ -75,13 +75,18 @@
<dependency> <dependency>
<groupId>com.github.TheSilentPro</groupId> <groupId>com.github.TheSilentPro</groupId>
<artifactId>SmartPlugin</artifactId> <artifactId>SmartPlugin</artifactId>
<version>e565e96a72</version> <version>1ade8879c3</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.github.TheSilentPro</groupId> <groupId>com.github.TheSilentPro</groupId>
<artifactId>Warehouse</artifactId> <artifactId>Warehouse</artifactId>
<version>f40f72cb19</version> <version>f40f72cb19</version>
</dependency> </dependency>
<dependency>
<groupId>net.wesjd</groupId>
<artifactId>anvilgui</artifactId>
<version>1.5.3-SNAPSHOT</version>
</dependency>
<!-- Soft Dependencies --> <!-- Soft Dependencies -->
<dependency> <dependency>
@ -90,12 +95,6 @@
<version>1.7</version> <version>1.7</version>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>me.lokka30</groupId>
<artifactId>treasury-api</artifactId>
<version>1.1.0</version>
<scope>provided</scope>
</dependency>
<dependency> <dependency>
<groupId>me.clip</groupId> <groupId>me.clip</groupId>
<artifactId>placeholderapi</artifactId> <artifactId>placeholderapi</artifactId>
@ -126,38 +125,6 @@
</configuration> </configuration>
</plugin> </plugin>
<!-- Buildnumber -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>buildnumber-maven-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<id>build-properties</id>
<phase>generate-resources</phase>
<goals>
<goal>create-metadata</goal>
</goals>
</execution>
</executions>
<configuration>
<doCheck>true</doCheck>
<doUpdate>true</doUpdate>
<buildNumberPropertyName>build.number</buildNumberPropertyName>
<revisionPropertyName>build.revision</revisionPropertyName>
<scmBranchPropertyName>build.branch</scmBranchPropertyName>
<useLastCommittedRevision>false</useLastCommittedRevision>
<shortRevisionLength>7</shortRevisionLength>
<revisionOnScmFailure>0</revisionOnScmFailure>
<items>
<item>buildNumber</item>
<item>scmVersion</item>
</items>
</configuration>
</plugin>
<!-- Shade --> <!-- Shade -->
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>

Datei anzeigen

@ -1,6 +1,7 @@
package tsp.headdb; package tsp.headdb;
import tsp.headdb.core.command.CommandCategory; import tsp.headdb.core.command.CommandCategory;
import tsp.headdb.core.command.CommandGive;
import tsp.headdb.core.command.CommandHelp; import tsp.headdb.core.command.CommandHelp;
import tsp.headdb.core.command.CommandInfo; import tsp.headdb.core.command.CommandInfo;
import tsp.headdb.core.command.CommandLanguage; import tsp.headdb.core.command.CommandLanguage;
@ -11,6 +12,8 @@ import tsp.headdb.core.command.CommandSearch;
import tsp.headdb.core.command.CommandSettings; import tsp.headdb.core.command.CommandSettings;
import tsp.headdb.core.command.CommandTexture; import tsp.headdb.core.command.CommandTexture;
import tsp.headdb.core.command.CommandUpdate; import tsp.headdb.core.command.CommandUpdate;
import tsp.headdb.core.economy.BasicEconomyProvider;
import tsp.headdb.core.economy.VaultProvider;
import tsp.headdb.core.listener.PlayerJoinListener; import tsp.headdb.core.listener.PlayerJoinListener;
import tsp.headdb.core.storage.Storage; import tsp.headdb.core.storage.Storage;
import tsp.headdb.core.task.UpdateTask; import tsp.headdb.core.task.UpdateTask;
@ -23,6 +26,7 @@ import tsp.smartplugin.logger.PluginLogger;
import java.io.IOException; import java.io.IOException;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.Optional;
public class HeadDB extends SmartPlugin { public class HeadDB extends SmartPlugin {
@ -30,20 +34,23 @@ public class HeadDB extends SmartPlugin {
private PluginLogger logger; private PluginLogger logger;
private BuildProperties buildProperties; private BuildProperties buildProperties;
private TranslatableLocalization localization; private TranslatableLocalization localization;
private BasicEconomyProvider economyProvider;
private CommandManager commandManager; private CommandManager commandManager;
private Storage storage; private Storage storage;
@Override @Override
public void onStart() { public void onStart() {
instance = this; instance = this;
instance.saveDefaultConfig();
instance.logger = new PluginLogger(this, getConfig().getBoolean("debug")); instance.logger = new PluginLogger(this, getConfig().getBoolean("debug"));
instance.logger.info("Loading HeadDB - " + instance.getDescription().getVersion()); instance.logger.info("Loading HeadDB - " + instance.getDescription().getVersion());
instance.buildProperties = new BuildProperties(this); instance.buildProperties = new BuildProperties(this);
instance.saveDefaultConfig();
new UpdateTask(getConfig().getLong("refresh", 3600L)).schedule(this); new UpdateTask(getConfig().getLong("refresh", 86400L)).schedule(this);
instance.logger.info("Loaded " + loadLocalization() + " languages!"); instance.logger.info("Loaded " + loadLocalization() + " languages!");
instance.initEconomy();
instance.storage = new Storage(); instance.storage = new Storage();
//instance.storage.load(); //instance.storage.load();
@ -77,10 +84,27 @@ public class HeadDB extends SmartPlugin {
} }
} }
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() { private void loadCommands() {
new CommandHelp().register(); new CommandHelp().register();
new CommandCategory().register(); new CommandCategory().register();
new CommandSearch().register(); new CommandSearch().register();
new CommandGive().register();
new CommandUpdate().register(); new CommandUpdate().register();
new CommandTexture().register(); new CommandTexture().register();
new CommandLanguage().register(); new CommandLanguage().register();
@ -96,6 +120,10 @@ public class HeadDB extends SmartPlugin {
return commandManager; return commandManager;
} }
public Optional<BasicEconomyProvider> getEconomyProvider() {
return Optional.ofNullable(economyProvider);
}
public TranslatableLocalization getLocalization() { public TranslatableLocalization getLocalization() {
return localization; return localization;
} }

Datei anzeigen

@ -1,31 +1,116 @@
package tsp.headdb.core.api; package tsp.headdb.core.api;
import org.bukkit.Bukkit;
import tsp.headdb.HeadDB; import tsp.headdb.HeadDB;
import tsp.headdb.core.util.Utils;
import tsp.headdb.implementation.category.Category; import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.head.Head; import tsp.headdb.implementation.head.Head;
import tsp.headdb.implementation.head.HeadDatabase; import tsp.headdb.implementation.head.HeadDatabase;
import tsp.headdb.implementation.head.LocalHead;
import tsp.headdb.implementation.requester.HeadProvider; import tsp.headdb.implementation.requester.HeadProvider;
import javax.annotation.Nonnull;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Head API for interacting with the main {@link HeadDatabase}.
*
* @author TheSilentPro (Silent)
* @see HeadDatabase
*/
public final class HeadAPI { public final class HeadAPI {
/**
* Utility class. No initialization nor extension.
*/
private HeadAPI() {} private HeadAPI() {}
/**
* The main {@link HeadDatabase}.
*/
private static final HeadDatabase database = new HeadDatabase(HeadDB.getInstance(), HeadProvider.HEAD_STORAGE); private static final HeadDatabase database = new HeadDatabase(HeadDB.getInstance(), HeadProvider.HEAD_STORAGE);
public static synchronized Optional<Head> getHeadById(int id) { /**
* 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(); return getHeads().stream().filter(head -> head.getId() == id).findAny();
} }
public static synchronized Optional<Head> getHeadByTexture(String texture) { /**
* 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(); 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() { public static List<Head> getHeads() {
List<Head> result = new ArrayList<>(); List<Head> result = new ArrayList<>();
for (Category category : getHeadsMap().keySet()) { for (Category category : getHeadsMap().keySet()) {
@ -35,18 +120,53 @@ public final class HeadAPI {
return result; 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) { public static List<Head> getHeads(Category category) {
return getHeadsMap().get(category); return getHeadsMap().get(category);
} }
public static synchronized Map<Category, List<Head>> getHeadsMap() { /**
* 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()); 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() { public static int getTotalHeads() {
return getHeads().size(); return getHeads().size();
} }
/**
* Retrieve a {@link Set} of local heads.
* Note that this calculates the heads on every try.
*
* @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 the main {@link HeadDatabase} used by the plugin.
*
* @return {@link HeadDatabase Database}
*/
@Nonnull
public static HeadDatabase getDatabase() { public static HeadDatabase getDatabase() {
return database; return database;
} }

Datei anzeigen

@ -0,0 +1,81 @@
package tsp.headdb.core.api.event;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.jetbrains.annotations.NotNull;
import tsp.headdb.implementation.head.Head;
import java.math.BigDecimal;
/**
* Called <strong>BEFORE</strong> a head is purchased but <strong>AFTER</strong> the transaction is complete.
* This gives you the chance to cancel and refund the money.
* <strong>This event is fired asynchronously!</strong>
*
* @author TheSilentPro (Silent)
* @see tsp.headdb.core.util.Utils#processPayment(Player, Head)
* @see Event#isAsynchronous()
*/
public class HeadPurchaseEvent extends Event implements Cancellable {
private static final HandlerList HANDLER_LIST = new HandlerList();
private Player player;
private Head head;
private BigDecimal cost;
private final boolean success;
private boolean cancelled;
public HeadPurchaseEvent(Player player, Head head, BigDecimal cost, boolean success) {
super(true);
this.player = player;
this.head = head;
this.cost = cost;
this.success = success;
}
public boolean isSuccessful() {
return success;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancel) {
this.cancelled = cancel;
}
@NotNull
@Override
public HandlerList getHandlers() {
return HANDLER_LIST;
}
public Player getPlayer() {
return player;
}
public void setPlayer(Player player) {
this.player = player;
}
public Head getHead() {
return head;
}
public void setHead(Head head) {
this.head = head;
}
public BigDecimal getCost() {
return cost;
}
public void setCost(BigDecimal cost) {
this.cost = cost;
}
}

Datei anzeigen

@ -9,12 +9,14 @@ import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.head.Head; import tsp.headdb.implementation.head.Head;
import tsp.smartplugin.inventory.PagedPane; import tsp.smartplugin.inventory.PagedPane;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
public class CommandCategory extends SubCommand { public class CommandCategory extends SubCommand {
public CommandCategory() { public CommandCategory() {
super("open", new String[]{"o"}); super("open", Arrays.stream(Category.VALUES).map(Category::getName).collect(Collectors.toList()), "o");
} }
@Override @Override
@ -24,18 +26,23 @@ public class CommandCategory extends SubCommand {
return; return;
} }
if (args.length < 2) {
getLocalization().sendMessage(player.getUniqueId(), "invalidArguments");
return;
}
Category.getByName(args[1]).ifPresentOrElse(category -> { Category.getByName(args[1]).ifPresentOrElse(category -> {
boolean requirePermission = HeadDB.getInstance().getConfig().getBoolean("requireCategoryPermission"); boolean requirePermission = HeadDB.getInstance().getConfig().getBoolean("requireCategoryPermission");
if (requirePermission && !player.hasPermission("headdb.category." + category.getName())) { if (requirePermission
&& !player.hasPermission("headdb.category." + category.getName())
&& !player.hasPermission("headdb.category.*")) {
getLocalization().sendMessage(player.getUniqueId(), "noPermission"); getLocalization().sendMessage(player.getUniqueId(), "noPermission");
return; return;
} }
int page = 0; int page = 0;
if (args.length >= 3) { if (args.length >= 3) {
try { page = Utils.resolveInt(args[2]) - 1;
page = Integer.parseInt(args[2]) - 1;
} catch (NumberFormatException ignored) {}
} }
List<Head> heads = HeadAPI.getHeads(category); List<Head> heads = HeadAPI.getHeads(category);

Datei anzeigen

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

Datei anzeigen

@ -6,7 +6,7 @@ import tsp.smartplugin.player.PlayerUtils;
public class CommandHelp extends SubCommand { public class CommandHelp extends SubCommand {
public CommandHelp() { public CommandHelp() {
super("help", new String[]{"h"}); super("help", "h");
} }
@Override @Override
@ -18,6 +18,7 @@ public class CommandHelp extends SubCommand {
PlayerUtils.sendMessage(sender, "&7/hdb &9info(i) &7- Show plugin information."); 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 &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 &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 &9update(u) &7- Manually update the database.");
PlayerUtils.sendMessage(sender, "&7/hdb &9language(l) &7- Change your language."); 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 &9settings(st) &7- Open the settings menu.");

Datei anzeigen

@ -8,14 +8,14 @@ import tsp.smartplugin.player.PlayerUtils;
public class CommandInfo extends SubCommand { public class CommandInfo extends SubCommand {
public CommandInfo() { public CommandInfo() {
super("info", new String[]{"i"}); super("info", "i");
} }
@Override @Override
public void handle(CommandSender sender, String[] args) { public void handle(CommandSender sender, String[] args) {
if (HeadDB.getInstance().getConfig().getBoolean("showAdvancedPluginInfo")) { if (HeadDB.getInstance().getConfig().getBoolean("showAdvancedPluginInfo")) {
BuildProperties build = HeadDB.getInstance().getBuildProperties(); BuildProperties build = HeadDB.getInstance().getBuildProperties();
PlayerUtils.sendMessage(sender, "&7Running &6HeadDB - " + build.getVersion() + " &7(&6Build " + build.getBuildNumber() + "&7)"); PlayerUtils.sendMessage(sender, "&7Running &6HeadDB - " + build.getVersion());
PlayerUtils.sendMessage(sender, "&7Created by &6" + HeadDB.getInstance().getDescription().getAuthors()); PlayerUtils.sendMessage(sender, "&7Created by &6" + HeadDB.getInstance().getDescription().getAuthors());
PlayerUtils.sendMessage(sender, "&7Compiled on &6" + build.getTimestamp() + " &7by &6" + build.getAuthor()); PlayerUtils.sendMessage(sender, "&7Compiled on &6" + build.getTimestamp() + " &7by &6" + build.getAuthor());
} else { } else {

Datei anzeigen

@ -2,32 +2,26 @@ package tsp.headdb.core.command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import tsp.smartplugin.localization.Message; import tsp.headdb.HeadDB;
import java.util.Set; import java.util.Set;
public class CommandLanguage extends SubCommand { public class CommandLanguage extends SubCommand {
public CommandLanguage() { public CommandLanguage() {
super("language", new String[]{"l", "lang"}); super("language", HeadDB.getInstance().getLocalization().getData().keySet(), "l", "lang");
} }
@Override @Override
public void handle(CommandSender sender, String[] args) { public void handle(CommandSender sender, String[] args) {
String lang = "en"; if (args.length < 2) {
if (args.length < 1) { getLocalization().sendMessage(sender, "invalidArguments");
getLocalization().sendMessage(new Message()
.receiver(sender)
.text("invalidArguments")
);
return; return;
} }
String lang = args[1];
if (!getLocalization().getData().containsKey(lang)) { if (!getLocalization().getData().containsKey(lang)) {
getLocalization().sendMessage(new Message() getLocalization().sendMessage(sender, "invalidLanguage", msg -> msg.replace("%languages%", toString(getLocalization().getData().keySet())));
.receiver(sender)
.text("invalidLanguage")
.function(msg -> msg.replace("%languages%", toString(getLocalization().getData().keySet()))));
return; return;
} }
@ -37,7 +31,7 @@ public class CommandLanguage extends SubCommand {
getLocalization().setLanguage(player.getUniqueId(), lang); getLocalization().setLanguage(player.getUniqueId(), lang);
} }
getLocalization().sendMessage(new Message().receiver(sender).text("languageChanged").function(msg -> msg.replace("%language%", lang))); getLocalization().sendMessage(sender, "languageChanged", msg -> msg.replace("%language%", lang));
} }
private String toString(Set<String> set) { private String toString(Set<String> set) {

Datei anzeigen

@ -1,30 +1,42 @@
package tsp.headdb.core.command; package tsp.headdb.core.command;
import net.wesjd.anvilgui.AnvilGUI;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender; import org.bukkit.command.PluginCommand;
import org.bukkit.command.RemoteConsoleCommandSender; import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import tsp.headdb.HeadDB; import tsp.headdb.HeadDB;
import tsp.headdb.core.api.HeadAPI; import tsp.headdb.core.api.HeadAPI;
import tsp.headdb.core.util.Utils; import tsp.headdb.core.util.Utils;
import tsp.headdb.implementation.category.Category; import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.head.Head;
import tsp.smartplugin.inventory.Button; import tsp.smartplugin.inventory.Button;
import tsp.smartplugin.inventory.PagedPane;
import tsp.smartplugin.inventory.Pane; import tsp.smartplugin.inventory.Pane;
import tsp.smartplugin.localization.TranslatableLocalization; import tsp.smartplugin.localization.TranslatableLocalization;
import tsp.smartplugin.utils.Validate;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault; import javax.annotation.ParametersAreNonnullByDefault;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
public class CommandMain extends HeadDBCommand implements CommandExecutor { public class CommandMain extends HeadDBCommand implements CommandExecutor, TabCompleter {
private final TranslatableLocalization localization = HeadDB.getInstance().getLocalization(); private final TranslatableLocalization localization = HeadDB.getInstance().getLocalization();
public CommandMain() { public CommandMain() {
super("headdb", "headdb.command.open"); super(
"headdb",
"headdb.command.open",
HeadDB.getInstance().getCommandManager().getCommandsMap().values().stream().map(HeadDBCommand::getName).collect(Collectors.toList())
);
} }
@Override @Override
@ -37,7 +49,7 @@ public class CommandMain extends HeadDBCommand implements CommandExecutor {
} }
if (!player.hasPermission(getPermission())) { if (!player.hasPermission(getPermission())) {
sendMessage(sender, "noPermission"); localization.sendMessage(sender, "noPermission");
return; return;
} }
localization.sendMessage(player.getUniqueId(), "openDatabase"); localization.sendMessage(player.getUniqueId(), "openDatabase");
@ -45,13 +57,30 @@ public class CommandMain extends HeadDBCommand implements CommandExecutor {
Pane pane = new Pane(6, Utils.translateTitle(localization.getMessage(player.getUniqueId(), "menu.main.title").orElse("&cHeadDB &7(" + HeadAPI.getTotalHeads() + ")"), HeadAPI.getTotalHeads(), "Main")); Pane pane = new Pane(6, Utils.translateTitle(localization.getMessage(player.getUniqueId(), "menu.main.title").orElse("&cHeadDB &7(" + HeadAPI.getTotalHeads() + ")"), HeadAPI.getTotalHeads(), "Main"));
for (Category category : Category.VALUES) { for (Category category : Category.VALUES) {
pane.addButton(new Button(category.getItem(player.getUniqueId()), e -> { pane.addButton(new Button(category.getItem(player.getUniqueId()), e -> {
e.setCancelled(true);
if (e.isLeftClick()) { if (e.isLeftClick()) {
Bukkit.dispatchCommand(e.getWhoClicked(), "hdb open " + category.getName()); Bukkit.dispatchCommand(e.getWhoClicked(), "hdb open " + category.getName());
} else if (e.isRightClick()) { } else if (e.isRightClick()) {
// todo: specific page new AnvilGUI.Builder()
.onComplete((p, text) -> {
try {
int page = Integer.parseInt(text);
// Remove when AnvilGUI adds option to return a void response
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 AnvilGUI.Response.openInventory(main.getInventory());
} catch (NumberFormatException nfe) {
return AnvilGUI.Response.text("Invalid number...");
}
})
.text("1")
.title(localization.getMessage(player.getUniqueId(), "menu.main.category.page.name").orElse("Enter page"))
.plugin(HeadDB.getInstance())
.open(player);
} }
e.setCancelled(true);
})); }));
} }
@ -66,19 +95,7 @@ public class CommandMain extends HeadDBCommand implements CommandExecutor {
} }
command.handle(sender, args); command.handle(sender, args);
}, () -> sendMessage(sender, "invalidSubCommand")); }, () -> localization.sendMessage(sender, "invalidSubCommand"));
}
@ParametersAreNonnullByDefault
private void sendMessage(CommandSender sender, String key) {
Validate.notNull(sender, "Message sender can not be null!");
Validate.notNull(key, "Key can not be null!");
if (sender instanceof Player player) {
localization.sendMessage(player.getUniqueId(), key);
} else if (sender instanceof ConsoleCommandSender || sender instanceof RemoteConsoleCommandSender){
localization.sendConsoleMessage(key);
}
} }
@Override @Override
@ -88,4 +105,24 @@ public class CommandMain extends HeadDBCommand implements CommandExecutor {
return true; return true;
} }
@Nullable
@Override
public List<String> onTabComplete(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
PluginCommand provided = HeadDB.getInstance().getCommand(label);
if (provided == null || !provided.getName().equalsIgnoreCase(getName())) {
return new ArrayList<>(); // not this command
}
if (args.length == 0) {
return new ArrayList<>(getCompletions());
} else {
Optional<SubCommand> sub = HeadDB.getInstance().getCommandManager().getCommand(args[1]);
if (sub.isPresent()) {
return new ArrayList<>(sub.get().getCompletions());
}
}
return new ArrayList<>();
}
} }

Datei anzeigen

@ -13,7 +13,7 @@ import java.util.List;
public class CommandSearch extends SubCommand { public class CommandSearch extends SubCommand {
public CommandSearch() { public CommandSearch() {
super("search", new String[]{"s"}); super("search", "s");
} }
@Override @Override
@ -23,6 +23,11 @@ public class CommandSearch extends SubCommand {
return; return;
} }
if (args.length < 2) {
getLocalization().sendMessage(player, "invalidArguments");
return;
}
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
for (int i = 1; i < args.length; i++) { for (int i = 1; i < args.length; i++) {
builder.append(args[i]); builder.append(args[i]);

Datei anzeigen

@ -16,7 +16,7 @@ import java.util.Set;
public class CommandSettings extends SubCommand { public class CommandSettings extends SubCommand {
public CommandSettings() { public CommandSettings() {
super("settings", new String[]{"st"}); super("settings", "st");
} }
@Override @Override
@ -32,16 +32,22 @@ public class CommandSettings extends SubCommand {
.name(getLocalization().getMessage(player.getUniqueId(), "menu.settings.language.name").orElse("&cLanguage")) .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(langs.size()))) .setLore(getLocalization().getMessage(player.getUniqueId(), "menu.settings.language.available").orElse("&7Languages Available: &e%size%").replace("%size%", String.valueOf(langs.size())))
.build(), e -> { .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%"), langs.size(), "Selector: Language")); PagedPane langPane = new PagedPane(4, 6, Utils.translateTitle(getLocalization().getMessage(player.getUniqueId(), "menu.settings.language.title").orElse("&cHeadDB &7- &eSelect Language").replace("%languages%", "%size%"), langs.size(), "Selector: Language"));
for (String lang : langs) { for (String lang : langs) {
langPane.addButton(new Button(new ItemBuilder(Material.PAPER) langPane.addButton(new Button(new ItemBuilder(Material.PAPER)
.name(getLocalization().getMessage(player.getUniqueId(), "menu.settings.language.format").orElse(ChatColor.YELLOW + lang).replace("%language%", lang)) .name(getLocalization().getMessage(player.getUniqueId(), "menu.settings.language.format").orElse(ChatColor.YELLOW + lang).replace("%language%", lang))
.build(), langEvent -> { .build(), langEvent -> {
e.setCancelled(true);
getLocalization().setLanguage(player.getUniqueId(), lang); getLocalization().setLanguage(player.getUniqueId(), lang);
getLocalization().sendMessage(player.getUniqueId(), "languageChanged", msg -> msg.replace("%language%", lang)); getLocalization().sendMessage(player.getUniqueId(), "languageChanged", msg -> msg.replace("%language%", lang));
})); }));
} }
langPane.open(player);
})); }));
pane.open(player);
} }
} }

Datei anzeigen

@ -11,7 +11,7 @@ import tsp.headdb.core.util.Utils;
public class CommandTexture extends SubCommand { public class CommandTexture extends SubCommand {
public CommandTexture() { public CommandTexture() {
super("texture", new String[]{"t"}); super("texture", "t");
} }
@Override @Override
@ -26,7 +26,7 @@ public class CommandTexture extends SubCommand {
component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(getLocalization().getMessage(player.getUniqueId(), "copyTexture").orElse("Click to copy!")))); component.setHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new Text(getLocalization().getMessage(player.getUniqueId(), "copyTexture").orElse("Click to copy!"))));
component.setClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, texture)); component.setClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, texture));
player.spigot().sendMessage(component); player.spigot().sendMessage(component);
}), () -> getLocalization().sendMessage("itemNoTexture")); }), () -> getLocalization().sendMessage(sender,"itemNoTexture"));
} }
} }

Datei anzeigen

@ -1,27 +1,19 @@
package tsp.headdb.core.command; package tsp.headdb.core.command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import tsp.headdb.HeadDB;
import tsp.headdb.core.api.HeadAPI; import tsp.headdb.core.api.HeadAPI;
import tsp.smartplugin.localization.Message;
public class CommandUpdate extends SubCommand { public class CommandUpdate extends SubCommand {
public CommandUpdate() { public CommandUpdate() {
super("update", new String[]{"u"}); super("update", "u");
} }
@Override @Override
public void handle(CommandSender sender, String[] args) { public void handle(CommandSender sender, String[] args) {
getLocalization().sendMessage(new Message() getLocalization().sendMessage(sender, "updateDatabase");
.receiver(sender) HeadAPI.getDatabase().update((time, result) -> HeadDB.getInstance().getLog().debug("Database Updated! Heads: " + result.values().size() + " | Took: " + time + "ms"));
.text("updateDatabase")
);
HeadAPI.getDatabase().update((time, heads) -> getLocalization().sendMessage(new Message()
.receiver(sender)
.text("updateDatabaseDone")
.function(msg -> msg.replace("%size%", String.valueOf(heads.size()).replace("%time%", String.valueOf(time)))))
);
} }
} }

Datei anzeigen

@ -3,20 +3,43 @@ package tsp.headdb.core.command;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import tsp.headdb.HeadDB; import tsp.headdb.HeadDB;
import tsp.smartplugin.localization.TranslatableLocalization; import tsp.smartplugin.localization.TranslatableLocalization;
import tsp.smartplugin.utils.Validate;
import javax.annotation.ParametersAreNonnullByDefault;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public abstract class HeadDBCommand { public abstract class HeadDBCommand {
private static final TranslatableLocalization localization = HeadDB.getInstance().getLocalization(); private static final TranslatableLocalization localization = HeadDB.getInstance().getLocalization();
private final String name; private final String name;
private final String permission; 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!");
public HeadDBCommand(String name, String permission) {
this.name = name; this.name = name;
this.permission = permission; 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 abstract void handle(CommandSender sender, String[] args);
public Collection<String> getCompletions() {
return completions;
}
public String getName() { public String getName() {
return name; return name;
} }

Datei anzeigen

@ -3,6 +3,8 @@ package tsp.headdb.core.command;
import tsp.headdb.HeadDB; import tsp.headdb.HeadDB;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Optional; import java.util.Optional;
// args[0] = sub-command | args[1+] = params // args[0] = sub-command | args[1+] = params
@ -10,13 +12,17 @@ public abstract class SubCommand extends HeadDBCommand {
private final String[] aliases; private final String[] aliases;
public SubCommand(String name, @Nullable String[] aliases) { public SubCommand(String name, Collection<String> completions, @Nullable String... aliases) {
super(name, "headdb.command." + name); super(name, "headdb.command." + name, completions);
this.aliases = aliases; this.aliases = aliases;
} }
public SubCommand(String name, String... aliases) {
this(name, new ArrayList<>(), aliases);
}
public SubCommand(String name) { public SubCommand(String name) {
this(name, null); this(name, (String[]) null);
} }
public Optional<String[]> getAliases() { public Optional<String[]> getAliases() {

Datei anzeigen

@ -0,0 +1,20 @@
package tsp.headdb.core.economy;
import org.bukkit.entity.Player;
import java.math.BigDecimal;
import java.util.concurrent.CompletableFuture;
public interface BasicEconomyProvider {
CompletableFuture<Boolean> canPurchase(Player player, BigDecimal cost);
CompletableFuture<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));
}
void init();
}

Datei anzeigen

@ -0,0 +1,45 @@
package tsp.headdb.core.economy;
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 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) {
double effectiveCost = cost.doubleValue();
return CompletableFuture.supplyAsync(() -> economy.has(player, effectiveCost >= 0 ? effectiveCost : 0)); // Vault is really old...
}
@Override
public CompletableFuture<Boolean> withdraw(Player player, BigDecimal amount) {
double effectiveCost = amount.doubleValue();
return CompletableFuture.supplyAsync(() -> economy.withdrawPlayer(player, effectiveCost >= 0 ? effectiveCost : 0).transactionSuccess());
}
@Override
public void init() {
if (!Bukkit.getServer().getPluginManager().isPluginEnabled("Vault")) {
HeadDB.getInstance().getLog().error("Vault is not installed!");
return;
}
RegisteredServiceProvider<Economy> economyProvider = Bukkit.getServer().getServicesManager().getRegistration(Economy.class);
if (economyProvider == null) {
HeadDB.getInstance().getLog().error("Could not find vault economy provider!");
return;
}
economy = economyProvider.getProvider();
}
}

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -2,6 +2,7 @@ package tsp.headdb.core.storage;
import tsp.headdb.HeadDB; import tsp.headdb.HeadDB;
import tsp.headdb.core.util.Utils; import tsp.headdb.core.util.Utils;
import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.head.Head; import tsp.headdb.implementation.head.Head;
import tsp.warehouse.storage.sql.SQLiteDataManager; import tsp.warehouse.storage.sql.SQLiteDataManager;
@ -32,7 +33,8 @@ public class HeadStorage extends SQLiteDataManager<Collection<Head>> {
set.getString("name"), set.getString("name"),
set.getString("texture"), set.getString("texture"),
set.getString("tags"), set.getString("tags"),
set.getString("updated") set.getString("updated"),
Category.valueOf(set.getString("category"))
)); ));
} }
@ -51,13 +53,14 @@ public class HeadStorage extends SQLiteDataManager<Collection<Head>> {
StringBuilder builder = new StringBuilder(); StringBuilder builder = new StringBuilder();
for (Head head : data) { for (Head head : data) {
builder.append(String.format("(%d, %s, %s, %s, %s, %s),", builder.append(String.format("(%d, %s, %s, %s, %s, %s, %s),",
head.getId(), head.getId(),
head.getUniqueId().toString(), head.getUniqueId().toString(),
head.getName(), head.getName(),
head.getTexture(), head.getTexture(),
head.getTags(), head.getTags(),
head.getUpdated() head.getUpdated(),
head.getCategory().name()
)); ));
} }

Datei anzeigen

@ -14,9 +14,6 @@ import java.io.InputStreamReader;
public class BuildProperties { public class BuildProperties {
private String version = "unknown"; private String version = "unknown";
private int buildNumber = 0;
private String revision = "unknown";
private String branch = "unknown";
private String timestamp = "unknown"; private String timestamp = "unknown";
private String author = "unknown"; private String author = "unknown";
@ -29,9 +26,6 @@ public class BuildProperties {
YamlConfiguration data = YamlConfiguration.loadConfiguration(new InputStreamReader(in)); YamlConfiguration data = YamlConfiguration.loadConfiguration(new InputStreamReader(in));
this.version = data.getString("version", "unknown"); this.version = data.getString("version", "unknown");
this.buildNumber = data.getInt("build", 0);
this.revision = data.getString("buildRevision", "unknown");
this.branch = data.getString("buildBranch", "unknown");
this.timestamp = data.getString("buildTimestamp", "unknown"); this.timestamp = data.getString("buildTimestamp", "unknown");
this.author = data.getString("buildAuthor", "unknown"); this.author = data.getString("buildAuthor", "unknown");
} }
@ -40,18 +34,6 @@ public class BuildProperties {
return version; return version;
} }
public int getBuildNumber() {
return buildNumber;
}
public String getRevision() {
return revision;
}
public String getBranch() {
return branch;
}
public String getTimestamp() { public String getTimestamp() {
return timestamp; return timestamp;
} }

Datei anzeigen

@ -2,12 +2,17 @@ package tsp.headdb.core.util;
import com.mojang.authlib.GameProfile; import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property; import com.mojang.authlib.properties.Property;
import me.clip.placeholderapi.PlaceholderAPI;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import tsp.headdb.HeadDB; import tsp.headdb.HeadDB;
import tsp.headdb.core.api.HeadAPI; import tsp.headdb.core.api.HeadAPI;
import tsp.headdb.core.api.event.HeadPurchaseEvent;
import tsp.headdb.core.economy.BasicEconomyProvider;
import tsp.headdb.core.hook.Hooks;
import tsp.headdb.implementation.category.Category; import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.head.Head; import tsp.headdb.implementation.head.Head;
import tsp.smartplugin.inventory.Button; import tsp.smartplugin.inventory.Button;
@ -18,10 +23,12 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault; import javax.annotation.ParametersAreNonnullByDefault;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.Collection; import java.util.Collection;
import java.util.Locale; import java.util.Locale;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class Utils { public class Utils {
@ -59,16 +66,16 @@ public class Utils {
public static PagedPane createPaged(Player player, String title) { public static PagedPane createPaged(Player player, String title) {
PagedPane main = new PagedPane(4, 6, title); PagedPane main = new PagedPane(4, 6, title);
main.getInventory().clear();
HeadAPI.getHeadByTexture("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODY1MmUyYjkzNmNhODAyNmJkMjg2NTFkN2M5ZjI4MTlkMmU5MjM2OTc3MzRkMThkZmRiMTM1NTBmOGZkYWQ1ZiJ9fX0=").ifPresent(head -> main.setBackItem(head.getItem(player.getUniqueId()))); HeadAPI.getHeadByTexture("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODY1MmUyYjkzNmNhODAyNmJkMjg2NTFkN2M5ZjI4MTlkMmU5MjM2OTc3MzRkMThkZmRiMTM1NTBmOGZkYWQ1ZiJ9fX0=").ifPresent(head -> main.setBackItem(head.getItem(player.getUniqueId())));
HeadAPI.getHeadByTexture("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2Q5MWY1MTI2NmVkZGM2MjA3ZjEyYWU4ZDdhNDljNWRiMDQxNWFkYTA0ZGFiOTJiYjc2ODZhZmRiMTdmNGQ0ZSJ9fX0=").ifPresent(head -> main.setCurrentItem(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()))); HeadAPI.getHeadByTexture("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmEzYjhmNjgxZGFhZDhiZjQzNmNhZThkYTNmZTgxMzFmNjJhMTYyYWI4MWFmNjM5YzNlMDY0NGFhNmFiYWMyZiJ9fX0=").ifPresent(head -> main.setNextItem(head.getItem(player.getUniqueId())));
main.setControlCurrent(new Button(main.getCurrentItem(), e -> Bukkit.dispatchCommand(player, "hdb")));
return main; return main;
} }
@ParametersAreNonnullByDefault @ParametersAreNonnullByDefault
public static void addHeads(Player player, @Nullable Category category, PagedPane pane, Collection<Head> heads) { public static void addHeads(Player player, @Nullable Category category, PagedPane pane, Collection<Head> heads) {
pane.getInventory().clear();
for (Head head : heads) { for (Head head : heads) {
ItemStack item = head.getItem(player.getUniqueId()); ItemStack item = head.getItem(player.getUniqueId());
pane.addButton(new Button(item, e -> { pane.addButton(new Button(item, e -> {
@ -92,6 +99,42 @@ public class Utils {
} }
} }
public static CompletableFuture<Boolean> processPayment(Player player, Head head) {
BigDecimal cost = BigDecimal.valueOf(HeadDB.getInstance().getConfig().getDouble("economy.cost." + head.getCategory().getName()));
Optional<BasicEconomyProvider> optional = HeadDB.getInstance().getEconomyProvider();
if (optional.isEmpty()) {
return CompletableFuture.completedFuture(false);
} else {
return optional.get().purchase(player, cost).thenApply(success -> {
HeadPurchaseEvent event = new HeadPurchaseEvent(player, head, cost, success);
Bukkit.getPluginManager().callEvent(event);
return !event.isCancelled() && success;
});
}
}
public static void purchase(Player player, Head head) {
processPayment(player, head).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. Needs testing not sure if this can be ran async.
Bukkit.getScheduler().runTask(HeadDB.getInstance(), () -> {
player.getInventory().addItem(head.getItem(player.getUniqueId()));
HeadDB.getInstance().getConfig().getStringList("commands.purchase").forEach(command -> {
if (Hooks.PAPI.enabled()) {
command = PlaceholderAPI.setPlaceholders(player, command);
}
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
});
});
}
});
}
public static Optional<String> getTexture(ItemStack head) { public static Optional<String> getTexture(ItemStack head) {
ItemMeta meta = head.getItemMeta(); ItemMeta meta = head.getItemMeta();
if (meta == null) { if (meta == null) {
@ -107,12 +150,21 @@ public class Utils {
} }
return profile.getProperties().get("textures").stream() return profile.getProperties().get("textures").stream()
.filter(p -> p.getValue().equals("textures")) .filter(p -> p.getName().equals("textures"))
.findAny() .findAny()
.map(Property::getName); .map(Property::getValue);
} catch (NoSuchFieldException | SecurityException | IllegalAccessException e ) { } catch (NoSuchFieldException | SecurityException | IllegalAccessException e ) {
e.printStackTrace();
return Optional.empty(); return Optional.empty();
} }
} }
public static int resolveInt(String raw) {
try {
return Integer.parseInt(raw);
} catch (NumberFormatException nfe) {
return 1;
}
}
} }

Datei anzeigen

@ -2,15 +2,19 @@ package tsp.headdb.implementation.category;
import org.bukkit.Material; import org.bukkit.Material;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import tsp.headdb.HeadDB; import tsp.headdb.HeadDB;
import tsp.headdb.core.api.HeadAPI; import tsp.headdb.core.api.HeadAPI;
import tsp.headdb.core.util.Utils; import tsp.headdb.core.util.Utils;
import tsp.smartplugin.builder.item.ItemBuilder; import tsp.smartplugin.builder.item.ItemBuilder;
import tsp.smartplugin.utils.StringUtils;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.stream.Collectors;
public enum Category { public enum Category {
@ -52,14 +56,25 @@ public enum Category {
public ItemStack getItem(UUID receiver) { public ItemStack getItem(UUID receiver) {
if (item == null) { if (item == null) {
HeadAPI.getHeads(this).stream().findFirst() HeadAPI.getHeads(this).stream().findFirst()
.ifPresentOrElse(head -> item = new ItemBuilder(head.getItem(receiver)) .ifPresentOrElse(head -> {
.name(Utils.translateTitle(HeadDB.getInstance().getLocalization().getMessage(receiver, "menu.category.name").orElse("&e" + getName()), HeadAPI.getHeads(this).size(), getName().toUpperCase(Locale.ROOT))) ItemStack retrieved = new ItemStack(head.getItem(receiver));
.setLore((String[]) null) ItemMeta meta = retrieved.getItemMeta();
.build(), 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()); () -> item = new ItemBuilder(Material.PLAYER_HEAD).name(getName().toUpperCase(Locale.ROOT)).build());
} }
return item; return item; // Return clone that changes are not reflected
} }
} }

Datei anzeigen

@ -6,6 +6,7 @@ import org.bukkit.Material;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import tsp.headdb.HeadDB; import tsp.headdb.HeadDB;
import tsp.headdb.implementation.category.Category;
import tsp.smartplugin.builder.item.ItemBuilder; import tsp.smartplugin.builder.item.ItemBuilder;
import tsp.smartplugin.localization.TranslatableLocalization; import tsp.smartplugin.localization.TranslatableLocalization;
import tsp.smartplugin.utils.Validate; import tsp.smartplugin.utils.Validate;
@ -23,15 +24,17 @@ public class Head {
private final String texture; private final String texture;
private final String tags; private final String tags;
private final String updated; private final String updated;
private final Category category;
private ItemStack item; private ItemStack item;
@ParametersAreNonnullByDefault @ParametersAreNonnullByDefault
public Head(int id, UUID uniqueId, String name, String texture, String tags, String updated) { 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(uniqueId, "Unique id can not be null!");
Validate.notNull(name, "Name can not be null!"); Validate.notNull(name, "Name can not be null!");
Validate.notNull(texture, "Texture can not be null!"); Validate.notNull(texture, "Texture can not be null!");
Validate.notNull(tags, "Tags can not be null!"); Validate.notNull(tags, "Tags can not be null!");
Validate.notNull(updated, "Updated can not be null!"); Validate.notNull(updated, "Updated can not be null!");
Validate.notNull(category, "Category can not be null!");
this.id = id; this.id = id;
this.uniqueId = uniqueId; this.uniqueId = uniqueId;
@ -39,6 +42,7 @@ public class Head {
this.texture = texture; this.texture = texture;
this.tags = tags; this.tags = tags;
this.updated = updated; this.updated = updated;
this.category = category;
} }
public ItemStack getItem(UUID receiver) { public ItemStack getItem(UUID receiver) {
@ -65,7 +69,7 @@ public class Head {
item.setItemMeta(meta); item.setItemMeta(meta);
} }
return item; return item.clone(); // Return clone that changes are not reflected
} }
public int getId() { public int getId() {
@ -92,4 +96,8 @@ public class Head {
return updated; return updated;
} }
public Category getCategory() {
return category;
}
} }

Datei anzeigen

@ -2,19 +2,14 @@ package tsp.headdb.implementation.head;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitScheduler; import org.bukkit.scheduler.BukkitScheduler;
import tsp.headdb.HeadDB;
import tsp.headdb.implementation.category.Category; import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.requester.HeadProvider; import tsp.headdb.implementation.requester.HeadProvider;
import tsp.headdb.implementation.requester.Requester; import tsp.headdb.implementation.requester.Requester;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Consumer;
public class HeadDatabase { public class HeadDatabase {
@ -28,7 +23,7 @@ public class HeadDatabase {
this.plugin = plugin; this.plugin = plugin;
this.scheduler = plugin.getServer().getScheduler(); this.scheduler = plugin.getServer().getScheduler();
this.requester = new Requester(plugin, provider); this.requester = new Requester(plugin, provider);
this.heads = Collections.synchronizedMap(new EnumMap<>(Category.class)); this.heads = new HashMap<>();
} }
public Map<Category, List<Head>> getHeads() { public Map<Category, List<Head>> getHeads() {
@ -49,9 +44,7 @@ public class HeadDatabase {
public void update(BiConsumer<Long, Map<Category, List<Head>>> fetched) { public void update(BiConsumer<Long, Map<Category, List<Head>>> fetched) {
getHeadsNoCache((elapsed, result) -> { getHeadsNoCache((elapsed, result) -> {
synchronized (heads) {
heads.putAll(result); heads.putAll(result);
}
timestamp = System.currentTimeMillis(); timestamp = System.currentTimeMillis();
fetched.accept(elapsed, result); fetched.accept(elapsed, result);
}); });

Datei anzeigen

@ -0,0 +1,5 @@
package tsp.headdb.implementation.head;
import java.util.UUID;
public record LocalHead(UUID uniqueId, String name) {}

Datei anzeigen

@ -4,9 +4,9 @@ import tsp.headdb.implementation.category.Category;
public enum HeadProvider { 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_STORAGE("https://raw.githubusercontent.com/TheSilentPro/HeadStorage/master/storage/%s.json"),
HEAD_WORKER(""), // TODO: implement HEAD_WORKER(""), // Unimplemented yet.
HEAD_API("https://minecraft-heads.com/scripts/api.php?cat=%s&tags=true"),
HEAD_ARCHIVE("https://heads.pages.dev/archive/%s.json"); HEAD_ARCHIVE("https://heads.pages.dev/archive/%s.json");
private final String url; private final String url;

Datei anzeigen

@ -48,7 +48,8 @@ public class Requester {
obj.get("name").getAsString(), obj.get("name").getAsString(),
obj.get("value").getAsString(), obj.get("value").getAsString(),
obj.get("tags").getAsString(), obj.get("tags").getAsString(),
response.date() response.date(),
category
)); ));
} }
@ -56,10 +57,12 @@ public class Requester {
}); });
} catch (IOException ex) { } catch (IOException ex) {
HeadDB.getInstance().getLog().debug("Failed to load from provider: " + provider.name()); 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; provider = HeadProvider.HEAD_ARCHIVE;
fetchAndResolve(category, heads); fetchAndResolve(category, heads);
} }
} }
}
public void fetch(Category category, Consumer<Response> response) throws IOException { public void fetch(Category category, Consumer<Response> response) throws IOException {
HttpURLConnection connection = (HttpURLConnection) new URL(provider.getFormattedUrl(category)).openConnection(); HttpURLConnection connection = (HttpURLConnection) new URL(provider.getFormattedUrl(category)).openConnection();

Datei anzeigen

@ -1,5 +1,5 @@
# How often the database should be updated in seconds. # How often the database should be updated in seconds.
refresh: 3600 refresh: 86400
# If local heads should be enabled. Only starts keeping track of joined players when enabled! # If local heads should be enabled. Only starts keeping track of joined players when enabled!
localHeads: true localHeads: true
@ -11,27 +11,10 @@ requireCategoryPermission: false
# If enabled, the menu will close after purchasing a head (even if the purchase fails) # If enabled, the menu will close after purchasing a head (even if the purchase fails)
closeOnPurchase: false closeOnPurchase: false
# Hidden heads from the menu # Economy Options
hidden:
enabled: false
# If enabled it will also hide any heads matching these tags in the favorites menu
hideFavorites: true
# If the head name matches any of the listen words it will be hidden (case-sensitive)
names:
- ""
# If the head has one of the listed tags it will be hidden
tags:
- ""
# Economy options
economy: economy:
enable: false enable: false
# Supported: VAULT, TREASURY provider: "VAULT" # Supported: VAULT
provider: "VAULT"
# Providers like treasury support multiple currencies
# Set the ID of the one used for head purchasing below.
# Leave empty to use the primary currency or if the provider does not support multiple currencies
currency: ""
cost: cost:
alphabet: 100 alphabet: 100
animals: 100 animals: 100
@ -43,112 +26,14 @@ economy:
miscellaneous: 100 miscellaneous: 100
monsters: 100 monsters: 100
plants: 100 plants: 100
local: 1000
# UI customization options.
ui:
category:
# Head categories. You can use item: instead of head: here, but AIR is not supported.
alphabet:
location: 20
head: 1788
animals:
location: 21
head: 5741
blocks:
location: 22
head: 8624
decoration:
location: 23
head: 11046
food-drinks:
location: 24
head: 17442
humans:
location: 29
head: 19361
humanoid:
location: 30
head: 28320
miscellaneous:
location: 31
head: 32746
monsters:
location: 32
head: 34819
plants:
location: 33
head: 37278
# Meta categories, used for UI elements. AIR is not supported. You can use head: instead of item: here.
favorites:
location: 39
item: BOOK
search:
location: 40
item: DARK_OAK_SIGN
local:
location: 41
item: COMPASS
# Item used to fill unused slots in the categories' menu. AIR is supported. You can use head: instead of item: here.
fill:
item: BLACK_STAINED_GLASS_PANE
sound:
# Whether the sounds should be played
enabled: true
# Played when a head is taken with no economy plugin
noEconomy:
# The name of the sound.
# Must be one from: https://hub.spigotmc.org/javadocs/bukkit/org/bukkit/Sound.html
name: ENTITY_EXPERIENCE_ORB_PICKUP
volume: 1
pitch: 1
# Played when the player doesn't have permission for the menu
noPermission:
name: BLOCK_ANVIL_BREAK
volume: 1
pitch: 1
# Played when they purchase a head
paid:
name: ENTITY_EXPERIENCE_ORB_PICKUP
volume: 1
pitch: 1
# Played when the head is free
free:
name: ENTITY_EXPERIENCE_ORB_PICKUP
volume: 1
pitch: 1
# Played when the player does not have enough funds for the head
unavailable:
name: BLOCK_ANVIL_BREAK
volume: 1
pitch: 1
# Played when a category/menu is opened
open:
name: ENTITY_EXPERIENCE_ORB_PICKUP
volume: 1
pitch: 1
# Played when a head is added to the favorites list
addFavorite:
name: ENTITY_EXPERIENCE_ORB_PICKUP
volume: 1
pitch: 1
# Played when a head is removed to from favorites list
removeFavorite:
name: BLOCK_ANVIL_BREAK
volume: 1
pitch: 1
# Command Configuration # Command Configuration
commands: commands:
# Commands to run ONLY if the purchase is successful. # Commands to run ONLY if the purchase is successful.
# They are run as CONSOLE after the player has receiver the head in their inventory.
purchase: purchase:
- "" - ""
# The head provider used.
# Don't touch this if you don't know what you are doing!
headProvider: "HEAD_STORAGE"
# If the original fetching fails and this is enabled, # If the original fetching fails and this is enabled,
# the plugin will attempt to fetch the heads from an archive. # the plugin will attempt to fetch the heads from an archive.
# The archive is static (manually updated) so some heads may be missing, this will only be used when all else fails. # The archive is static (manually updated) so some heads may be missing, this will only be used when all else fails.

Datei anzeigen

@ -2,14 +2,17 @@ noConsole: "&cOnly for in-game players!"
noPermission: "&cNo permission!" noPermission: "&cNo permission!"
invalidSubCommand: "&cInvalid sub-command! Run &e/hdb help&c for help." invalidSubCommand: "&cInvalid sub-command! Run &e/hdb help&c for help."
invalidArguments: "&cInvalid Arguments! Use &e/hdb help&c for help." invalidArguments: "&cInvalid Arguments! Use &e/hdb help&c for help."
invalidTarget: "&cInvalid target: &e%name%"
invalidCategory: "&cInvalid Category!" invalidCategory: "&cInvalid Category!"
invalidNumber: "&e%name% &cis not a number!"
invalidPageIndex: "&cThat page is out of bounds! Max: %pages%" invalidPageIndex: "&cThat page is out of bounds! Max: %pages%"
openDatabase: "&7Opening &cHeadDatabase&7..." openDatabase: "" # Intentionally empty. Sent when the main gui is opened
updateDatabase: "&7Updating..." updateDatabase: "&7Updating..."
updateDatabaseDone: "&7Database was updated! Heads: &6%size% &7| Took: &a%time%"
searchCommand: "&7Searching for heads matching: &e%query%" searchCommand: "&7Searching for heads matching: &e%query%"
searchCommandResults: "&7Found &e%size% &7matches!" searchCommandResults: "&7Found &e%size% &7matches!"
giveCommand: "&7Gave &ex%size% %name% &7to &e%receiver%"
giveCommandInvalid: "&cInvalid head: &e%name%"
itemTexture: "&7Texture: %texture%" itemTexture: "&7Texture: %texture%"
itemNoTexture: "&cThis item does not have a texture!" itemNoTexture: "&cThis item does not have a texture!"
copyTexture: "&6Click to copy texture!" copyTexture: "&6Click to copy texture!"
@ -22,14 +25,21 @@ menu:
title: "&cHeadDB &7(%size%)" title: "&cHeadDB &7(%size%)"
category: category:
name: "&e&l%category%" name: "&e&l%category%"
page:
name: "&eGo to specific page"
lore:
- "&7Left-Click to open"
- "&7Right-Click to open specific page"
search: search:
name: "&eSearch" name: "&eSearch"
category: category:
name: "&e%category% &7(%size%)" name: "&cHeadDB &7- &e%category% &7(%size%)"
head: head:
name: "&e%name%" name: "&e%name%"
search: search:
name: "&cHeadDB &7- &eSearch: %query% &7(%size%)" name: "&cHeadDB &7- &eSearch: %query% &7(%size%)"
page:
name: "&cHeadDB &7- &eEnter page number"
settings: settings:
name: "&cHeadDB &7- &eSettings" name: "&cHeadDB &7- &eSettings"
language: language:

Datei anzeigen

@ -8,18 +8,16 @@ api-version: 1.19
author: TheSilentPro (Silent) author: TheSilentPro (Silent)
spigot-id: 84967 spigot-id: 84967
build: ${build.number}
buildRevision: ${build.revision}
buildBranch: ${build.branch}
buildTimestamp: ${build.timestamp} buildTimestamp: ${build.timestamp}
buildAuthor: ${build.author} buildAuthor: ${build.author}
libraries: # Although not up-to-date, spigot already includes gson. This is here just in case.
- "com.google.code.gson:gson:2.10" #libraries:
# - "com.google.code.gson:gson:2.10"
commands: commands:
headdb: headdb:
usage: /headdb <arguments> usage: /headdb help
description: Open the database description: Open the database
aliases: [hdb, headdatabase, headmenu] aliases: [hdb, headdatabase, headmenu]
@ -27,27 +25,24 @@ permissions:
headdb.admin: headdb.admin:
default: op default: op
children: children:
headdb.open: true headdb.command.open: true
headdb.search: true headdb.command.search: true
headdb.give: true headdb.command.give: true
headdb.command.update: true
headdb.favorites: true headdb.favorites: true
headdb.local: true headdb.local: true
headdb.tagsearch: true headdb.category.*: true
headdb.update: true headdb.command.open:
headdb.reload: true
headdb.open:
default: op default: op
headdb.search: headdb.command.search:
default: op default: op
headdb.give: headdb.command.give:
default: op
headdb.command.update:
default: op default: op
headdb.favorites: headdb.favorites:
default: op default: op
headdb.local: headdb.local:
default: op default: op
headdb.tagsearch: headdb.category.*:
default: op
headdb.update:
default: op
headdb.reload:
default: op default: op