3
0
Mirror von https://github.com/TheSilentPro/HeadDB.git synchronisiert 2024-12-26 19:02:39 +01:00

Merge pull request #39 from TheSilentPro/v5

v5.0.0 RC-1
Dieser Commit ist enthalten in:
Silent 2022-11-13 15:18:21 +01:00 committet von GitHub
Commit d8f4dc23e3
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 4AEE18F83AFDEB23
75 geänderte Dateien mit 2340 neuen und 3248 gelöschten Zeilen

2
.gitignore vendored
Datei anzeigen

@ -3,6 +3,8 @@ target/
src/test/
dependency-reduced-pom.xml
build.properties
build/
.classpath
.project

Datei anzeigen

@ -14,7 +14,7 @@ copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE

Datei anzeigen

@ -10,4 +10,4 @@ You may report issues on the [Issue Tracker](https://github.com/TheSilentPro/Hea
# API
All API methods can be found in the [HeadAPI](https://github.com/TheSilentPro/HeadDB/blob/master/src/main/java/tsp/headdb/api/HeadAPI.java) class. <br>
Alternatevly you may view the [javadocs](https://javadocs.pages.dev/headdb/4.0.0/tsp/headdb/api/HeadAPI).
Alternatively you may view the [javadocs](https://javadocs.pages.dev/headdb/4.0.0/tsp/headdb/api/HeadAPI).

100
pom.xml
Datei anzeigen

@ -6,38 +6,40 @@
<groupId>tsp.headdb</groupId>
<artifactId>HeadDB</artifactId>
<version>4.4.4</version>
<version>5.0.0-rc.1</version>
<packaging>jar</packaging>
<properties>
<sonar.projectKey>TheSilentPro_HeadDB</sonar.projectKey>
<sonar.organization>silent</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
<sonar.exclusions>src/main/java/tsp/headdb/Metrics.java</sonar.exclusions>
<sonar.coverage.exclusions>*</sonar.coverage.exclusions>
<spigot.id>84967</spigot.id>
</properties>
<name>HeadDB</name>
<description>Database with thousands of heads</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<build.author>TheSilentPro (Silent)</build.author>
<maven.build.timestamp.format>dd-MM-yyyy HH:mm:ss</maven.build.timestamp.format>
<build.timestamp>${maven.build.timestamp}</build.timestamp>
</properties>
<scm>
<connection>scm:git:git@github.com:TheSilentPro/HeadDB.git</connection>
<developerConnection>scm:git:git@github.com:TheSilentPro/HeadDB.git</developerConnection>
<url>git@github.com:TheSilentPro/HeadDB.git</url>
</scm>
<repositories>
<!-- Spigot Repo -->
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<!-- Mojang Repo -->
<repository>
<id>mojang-repo</id>
<url>https://libraries.minecraft.net/</url>
</repository>
<!-- JitPack -->
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
<!-- CodeMC -->
<repository>
<id>CodeMC</id>
<url>https://repo.codemc.org/repository/maven-public</url>
@ -49,40 +51,50 @@
</repositories>
<dependencies>
<!-- Spigot API -->
<!-- Hard Dependencies (Provided) -->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.19-R0.1-SNAPSHOT</version>
<version>1.19.2-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- Mojang Auth Lib -->
<dependency>
<groupId>com.mojang</groupId>
<artifactId>authlib</artifactId>
<version>1.5.21</version>
<scope>provided</scope>
</dependency>
<!-- AnvilGUI -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10</version>
<scope>provided</scope>
</dependency>
<!-- Hard Dependencies (Shaded) -->
<dependency>
<groupId>com.github.TheSilentPro</groupId>
<artifactId>SmartPlugin</artifactId>
<version>737fc7b893</version>
</dependency>
<dependency>
<groupId>com.github.TheSilentPro</groupId>
<artifactId>Warehouse</artifactId>
<version>882b42fc75</version>
</dependency>
<dependency>
<groupId>net.wesjd</groupId>
<artifactId>anvilgui</artifactId>
<version>1.5.3-SNAPSHOT</version>
</dependency>
<!-- Vault -->
<!-- Soft Dependencies -->
<dependency>
<groupId>com.github.MilkBowl</groupId>
<artifactId>VaultAPI</artifactId>
<version>1.7</version>
<scope>provided</scope>
</dependency>
<!-- Treasury -->
<dependency>
<groupId>me.lokka30</groupId>
<artifactId>treasury-api</artifactId>
<version>1.1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>me.clip</groupId>
<artifactId>placeholderapi</artifactId>
@ -92,6 +104,8 @@
</dependencies>
<build>
<finalName>${project.name}</finalName>
<resources>
<resource>
<directory>src/main/resources</directory>
@ -104,10 +118,10 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<version>3.10.1</version>
<configuration>
<source>16</source>
<target>16</target>
<source>${maven.compiler.source}</source>
<target>${maven.compiler.target}</target>
</configuration>
</plugin>
@ -137,16 +151,26 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.3.2</version>
</plugin>
<!-- Sonarcloud -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.9.1.2184</version>
<version>3.4.1</version>
<executions>
<execution>
<id>javadoc-generate</id>
<phase>package</phase>
<goals>
<goal>javadoc</goal>
</goals>
</execution>
</executions>
<configuration>
<goalPrefix>sonar</goalPrefix>
<show>public</show>
<doclint>none</doclint>
<additionalDependencies>
<additionalDependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.19.2-R0.1-SNAPSHOT</version>
</additionalDependency>
</additionalDependencies>
</configuration>
</plugin>
</plugins>

Datei anzeigen

@ -1,126 +1,179 @@
package tsp.headdb;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import tsp.headdb.api.HeadAPI;
import tsp.headdb.command.HeadDBCommand;
import tsp.headdb.economy.TreasuryProvider;
import tsp.headdb.implementation.DataSaveTask;
import tsp.headdb.implementation.DatabaseUpdateTask;
import tsp.headdb.economy.BasicEconomyProvider;
import tsp.headdb.economy.VaultProvider;
import tsp.headdb.listener.JoinListener;
import tsp.headdb.listener.MenuListener;
import tsp.headdb.listener.PagedPaneListener;
import tsp.headdb.storage.PlayerDataFile;
import tsp.headdb.util.Localization;
import tsp.headdb.util.Log;
import tsp.headdb.util.Utils;
import org.bukkit.command.PluginCommand;
import tsp.headdb.core.command.CommandCategory;
import tsp.headdb.core.command.CommandGive;
import tsp.headdb.core.command.CommandHelp;
import tsp.headdb.core.command.CommandInfo;
import tsp.headdb.core.command.CommandLanguage;
import tsp.headdb.core.command.CommandMain;
import tsp.headdb.core.command.CommandManager;
import tsp.headdb.core.command.CommandSearch;
import javax.annotation.Nullable;
import java.io.File;
import tsp.headdb.core.command.CommandSettings;
import tsp.headdb.core.command.CommandTexture;
import tsp.headdb.core.command.CommandUpdate;
import tsp.headdb.core.economy.BasicEconomyProvider;
import tsp.headdb.core.economy.VaultProvider;
import tsp.headdb.core.storage.Storage;
import tsp.headdb.core.task.UpdateTask;
/**
* Main class of HeadDB
*/
public class HeadDB extends JavaPlugin {
import tsp.headdb.core.util.BuildProperties;
import tsp.smartplugin.SmartPlugin;
import tsp.smartplugin.inventory.PaneListener;
import tsp.smartplugin.localization.TranslatableLocalization;
import tsp.smartplugin.logger.PluginLogger;
import tsp.smartplugin.utils.PluginUtils;
import java.io.IOException;
import java.net.URISyntaxException;
import java.text.DecimalFormat;
import java.util.Optional;
public class HeadDB extends SmartPlugin {
private static HeadDB instance;
private PluginLogger logger;
private BuildProperties buildProperties;
private TranslatableLocalization localization;
private Storage storage;
private BasicEconomyProvider economyProvider;
private PlayerDataFile playerData;
private Localization localization;
private CommandManager commandManager;
@Override
public void onEnable() {
public void onStart() {
instance = this;
Log.info("Loading HeadDB - " + getDescription().getVersion());
saveDefaultConfig();
createLocalizationFile();
instance.saveDefaultConfig();
instance.logger = new PluginLogger(this, getConfig().getBoolean("debug"));
instance.logger.info("Loading HeadDB - " + instance.getDescription().getVersion());
instance.buildProperties = new BuildProperties(this);
this.playerData = new PlayerDataFile("player_data.json");
this.playerData.load();
new UpdateTask(getConfig().getLong("refresh", 86400L)).schedule(this);
instance.logger.info("Loaded " + loadLocalization() + " languages!");
if (getConfig().getBoolean("economy.enable")) {
String rawProvider = getConfig().getString("economy.provider", "VAULT");
Log.debug("Starting economy with provider: " + rawProvider);
if (rawProvider.equalsIgnoreCase("vault")) {
economyProvider = new VaultProvider();
economyProvider.initProvider();
} else if (rawProvider.equalsIgnoreCase("treasury")) {
economyProvider = new TreasuryProvider();
economyProvider.initProvider();
}
}
instance.initStorage();
instance.initEconomy();
long refresh = getConfig().getLong("refresh") * 20;
HeadAPI.getDatabase().setRefresh(refresh);
Bukkit.getScheduler().runTaskTimerAsynchronously(this, new DatabaseUpdateTask(), 0, refresh);
Bukkit.getScheduler().runTaskTimerAsynchronously(this, new DataSaveTask(), refresh, refresh);
new PaneListener(this);
//new PlayerJoinListener();
new JoinListener(this);
new MenuListener(this);
new PagedPaneListener(this);
instance.commandManager = new CommandManager();
loadCommands();
getCommand("headdb").setExecutor(new HeadDBCommand());
Log.debug("Starting metrics...");
initMetrics();
Utils.isLatestVersion(this, 84967, latest -> {
if (!Boolean.TRUE.equals(latest)) {
Log.warning("There is a new update available for HeadDB on spigot!");
Log.warning("Download: https://www.spigotmc.org/resources/84967");
}
});
Log.info("Done!");
new Metrics(this, 9152);
ensureLatestVersion();
instance.logger.info("Done!");
}
@Override
public void onDisable() {
Bukkit.getScheduler().cancelTasks(this);
this.playerData.save();
if (storage != null) {
storage.getPlayerStorage().suspend();
}
}
public Localization getLocalization() {
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.getPlayerStorage().init();
}
private int loadLocalization() {
instance.localization = new TranslatableLocalization(this, "messages");
try {
instance.localization.createDefaults();
return instance.localization.load();
} catch (URISyntaxException | IOException ex) {
instance.logger.error("Failed to load localization!");
ex.printStackTrace();
this.setEnabled(false);
return 0;
}
}
private void initEconomy() {
if (!getConfig().getBoolean("economy.enabled")) {
instance.logger.debug("Economy disabled by config.yml!");
instance.economyProvider = null;
return;
}
String raw = getConfig().getString("economy.provider", "VAULT");
if (raw.equalsIgnoreCase("VAULT")) {
economyProvider = new VaultProvider();
}
economyProvider.init();
instance.logger.info("Economy Provider: " + raw);
}
private void loadCommands() {
PluginCommand main = getCommand("headdb");
if (main != null) {
main.setExecutor(new CommandMain());
main.setTabCompleter(new CommandMain());
} else {
instance.logger.error("Could not find main 'headdb' command!");
this.setEnabled(false);
return;
}
new CommandHelp().register();
new CommandCategory().register();
new CommandSearch().register();
new CommandGive().register();
new CommandUpdate().register();
new CommandTexture().register();
new CommandLanguage().register();
new CommandSettings().register();
new CommandInfo().register();
}
// Getters
public Storage getStorage() {
return storage;
}
public CommandManager getCommandManager() {
return commandManager;
}
public Optional<BasicEconomyProvider> getEconomyProvider() {
return Optional.ofNullable(economyProvider);
}
@SuppressWarnings("ConstantConditions")
private DecimalFormat decimalFormat = new DecimalFormat(getConfig().getString("economy.format"));
public DecimalFormat getDecimalFormat() {
return decimalFormat != null ? decimalFormat : (decimalFormat = new DecimalFormat("##.##"));
}
public TranslatableLocalization getLocalization() {
return localization;
}
public PlayerDataFile getPlayerData() {
return playerData;
public BuildProperties getBuildProperties() {
return buildProperties;
}
@Nullable
public BasicEconomyProvider getEconomyProvider() {
return economyProvider;
public PluginLogger getLog() {
return logger;
}
public static HeadDB getInstance() {
return instance;
}
private void createLocalizationFile() {
File messagesFile = new File(getDataFolder().getAbsolutePath() + "/messages.yml");
if (!messagesFile.exists()) {
saveResource("messages.yml", false);
Log.debug("Localization loaded from jar file.");
}
this.localization = new Localization(messagesFile);
this.localization.load();
}
private void initMetrics() {
Metrics metrics = new Metrics(this, 9152);
metrics.addCustomChart(new Metrics.SimplePie("economy_provider", () -> {
if (this.getEconomyProvider() != null) {
return this.getConfig().getString("economy.provider");
}
return "None";
}));
}
}

Datei anzeigen

@ -1,17 +1,3 @@
/*
* This Metrics class was auto-generated and can be copied into your project if you are
* not using a build tool like Gradle or Maven for dependency management.
*
* IMPORTANT: You are not allowed to modify this class, except changing the package.
*
* Unallowed modifications include but are not limited to:
* - Remove the option for users to opt-out
* - Change the frequency for data submission
* - Obfuscate the code (every obfucator should allow you to make an exception for specific files)
* - Reformat the code (if you use a linter, add an exception)
*
* Violations will result in a ban of your plugin and account from bStats.
*/
package tsp.headdb;
import java.io.BufferedReader;
@ -47,7 +33,8 @@ import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
public class Metrics {
@SuppressWarnings({"all", "deprecation"}) // Class is from bstats, can't modify it.
class Metrics {
private final Plugin plugin;

Datei anzeigen

@ -1,259 +0,0 @@
package tsp.headdb.api;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import tsp.headdb.HeadDB;
import tsp.headdb.implementation.Category;
import tsp.headdb.implementation.Head;
import tsp.headdb.implementation.HeadDatabase;
import tsp.headdb.implementation.LocalHead;
import tsp.headdb.inventory.InventoryUtils;
import tsp.headdb.storage.PlayerDataFile;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
/**
* This class provides simple methods
* for interacting with the HeadDB plugin
*
* @author TheSilentPro
*/
// TODO: Optional instead of null.
// TODO: Remove stream, use loop.
public final class HeadAPI {
private HeadAPI() {}
private static final String VALIDATION_PLAYER_NULL = "Player can not be null!";
private static final String VALIDATION_CATEGORY_NULL = "Category can not be null!";
private static final String VALIDATION_UUID_NULL = "UUID can not be null!";
private static final String VALIDATION_VALUE_NULL = "Value can not be null!";
/**
* Main {@link HeadDatabase} that he HeadDB plugin uses.
*/
private static final HeadDatabase database = new HeadDatabase(HeadDB.getInstance());
/**
* Retrieves the main {@link HeadDatabase}
*
* @return Head Database
*/
public static HeadDatabase getDatabase() {
return database;
}
/**
* Opens the database for a player
*
* @param player Target player
*/
public static void openDatabase(@Nonnull Player player) {
Validate.notNull(player, VALIDATION_PLAYER_NULL);
InventoryUtils.openDatabase(player);
}
/**
* Opens a specific category of the database for a player
*
* @param player Target player
* @param category Category to open
*/
public static void openCategoryDatabase(@Nonnull Player player, @Nonnull Category category) {
Validate.notNull(player, VALIDATION_PLAYER_NULL);
Validate.notNull(category, VALIDATION_CATEGORY_NULL);
InventoryUtils.openCategoryDatabase(player, category);
}
/**
* Opens the database with results of a specific search term
*
* @param player Target player
* @param search Search term
*/
public static void openSearchDatabase(@Nonnull Player player, @Nonnull String search) {
Validate.notNull(player, VALIDATION_PLAYER_NULL);
Validate.notNull(search, "Search can not be null!");
InventoryUtils.openSearchDatabase(player, search);
}
/**
* Opens the database with results of a specific tag search term
*
* @param player Target player
* @param tag Tag search term
*/
public static void openTagSearchDatabase(@Nonnull Player player, @Nonnull String tag) {
Validate.notNull(player, VALIDATION_PLAYER_NULL);
Validate.notNull(tag, "Tag can not be null!");
InventoryUtils.openTagSearchDatabase(player, tag);
}
/**
* Retrieve a {@link Head} by it's ID
*
* @param id The ID of the head
* @return The head
*/
@Nullable
public static Head getHeadByID(int id) {
return database.getHeadByID(id);
}
/**
* Retrieve a {@link Head} by it's UUID
*
* @param uuid The UUID of the head
* @return The head
*/
@Nullable
public static Head getHeadByUniqueId(@Nonnull UUID uuid) {
Validate.notNull(uuid, VALIDATION_UUID_NULL);
return database.getHeadByUniqueId(uuid);
}
/**
* Retrieve a {@link List} of {@link Head}'s by their tag
*
* @param tag The tag
* @return List of heads
*/
@Nonnull
public static List<Head> getHeadsByTag(@Nonnull String tag) {
Validate.notNull(tag, "Tag can not be null!");
return database.getHeadsByTag(tag);
}
/**
* Retrieves a {@link List} of {@link Head}'s matching a name
*
* @param name The name to match for
* @return List of heads
*/
@Nonnull
public static List<Head> getHeadsByName(@Nonnull String name) {
Validate.notNull(name, "Name can not be null!");
return database.getHeadsByName(name);
}
/**
* Retrieves a {@link List} of {@link Head}'s in a {@link Category} matching a name
*
* @param category The category to search in
* @param name The name to match for
* @return List of heads
*/
@Nonnull
public static List<Head> getHeadsByName(@Nonnull Category category, @Nonnull String name) {
Validate.notNull(category, VALIDATION_CATEGORY_NULL);
Validate.notNull(name, "Name can not be null!");
return database.getHeadsByName(category, name);
}
/**
* Retrieve a {@link Head} by it's value
*
* @param value The texture value
* @return The head
*/
@Nullable
public static Head getHeadByValue(@Nonnull String value) {
Validate.notNull(value, VALIDATION_VALUE_NULL);
return database.getHeadByValue(value);
}
/**
* Retrieve a {@link List} of {@link Head}'s in a specific {@link Category}
*
* @param category The category to search in
* @return List of heads
*/
@Nonnull
public static List<Head> getHeads(@Nonnull Category category) {
Validate.notNull(category, VALIDATION_CATEGORY_NULL);
return database.getHeads(category);
}
/**
* Retrieve a {@link List} of all {@link Head}'s
*
* @return List of all heads
*/
@Nonnull
public static List<Head> getHeads() {
return database.getHeads();
}
/**
* Add a favorite {@link Head} to the player
*
* @param uuid The player's unique id
* @param textureValue The head's texture value
*/
public static void addFavoriteHead(@Nonnull UUID uuid, @Nonnull String textureValue) {
Validate.notNull(uuid, VALIDATION_UUID_NULL);
Validate.notNull(textureValue, VALIDATION_VALUE_NULL);
HeadDB.getInstance().getPlayerData().modifyFavorite(uuid, textureValue, PlayerDataFile.ModificationType.SET);
}
/**
* Remove a favorite {@link Head} from the player
*
* @param uuid The player's unique id
* @param textureValue The head's texture value
*/
public static void removeFavoriteHead(@Nonnull UUID uuid, @Nonnull String textureValue) {
Validate.notNull(uuid, VALIDATION_UUID_NULL);
Validate.notNull(textureValue, VALIDATION_VALUE_NULL);
HeadDB.getInstance().getPlayerData().modifyFavorite(uuid, textureValue, PlayerDataFile.ModificationType.REMOVE);
}
/**
* Retrieve a {@link List} of favorite {@link Head}'s for the player
*
* @param uuid The player's unique id
* @return List of favorite {@link Head}'s for the player
*/
@Nonnull
public static List<Head> getFavoriteHeads(@Nonnull UUID uuid) {
Validate.notNull(uuid, VALIDATION_UUID_NULL);
return HeadDB.getInstance().getPlayerData().getFavoriteHeadsByTexture(uuid).stream()
.map(HeadAPI::getHeadByValue)
.filter(Objects::nonNull)
.toList();
}
/**
* Retrieve a list of {@link LocalHead}'s.
* These are heads from players that have joined the server at least once.
* Requires config option localHeads = true
*
* @return List of {@link LocalHead}'s
*/
@Nonnull
public static List<LocalHead> getLocalHeads() {
return HeadDB.getInstance().getPlayerData().getEntries().stream()
.map(entry -> Bukkit.getOfflinePlayer(UUID.fromString(entry)))
.map(player -> new LocalHead(player.getUniqueId()).name(player.getName()))
.toList();
}
}

Datei anzeigen

@ -1,54 +0,0 @@
package tsp.headdb.api.event;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import tsp.headdb.implementation.Head;
import tsp.headdb.implementation.Category;
import tsp.headdb.implementation.HeadDatabase;
import java.util.List;
import java.util.Map;
/**
* This event is called AFTER a {@link HeadDatabase} updates.
* The event is called asynchronously and can not be cancelled.
*
* @author TheSilentPro
*/
public class DatabaseUpdateEvent extends Event {
private final HandlerList handlerList = new HandlerList();
private final HeadDatabase database;
private final Map<Category, List<Head>> heads;
public DatabaseUpdateEvent(HeadDatabase database, Map<Category, List<Head>> heads) {
super(true);
this.database = database;
this.heads = heads;
}
/**
* Retrieve the {@link HeadDatabase} associated with this event
*
* @return The database
*/
public HeadDatabase getDatabase() {
return database;
}
/**
* Retrieve the result of the update
*
* @return The heads fetched from the update
*/
public Map<Category, List<Head>> getHeads() {
return heads;
}
@Override
public HandlerList getHandlers() {
return handlerList;
}
}

Datei anzeigen

@ -1,69 +0,0 @@
package tsp.headdb.api.event;
import org.bukkit.entity.Player;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import tsp.headdb.implementation.Head;
/**
* This event is called when a player purchases a {@link Head}
*
* @author TheSilentPro
* @see tsp.headdb.inventory.InventoryUtils#purchaseHead(Player, Head, int, String, String)
*/
public class PlayerHeadPurchaseEvent extends Event implements Cancellable {
private final HandlerList handlerList = new HandlerList();
private boolean cancelled;
private Player player;
private Head head;
private double cost;
public PlayerHeadPurchaseEvent(Player player, Head head, double cost) {
super(true);
this.player = player;
this.head = head;
this.cost = cost;
}
public Player getPlayer() {
return player;
}
public Head getHead() {
return head;
}
public double getCost() {
return cost;
}
public void setPlayer(Player player) {
this.player = player;
}
public void setHead(Head head) {
this.head = head;
}
public void setCost(double cost) {
this.cost = cost;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean b) {
this.cancelled = b;
}
@Override
public HandlerList getHandlers() {
return handlerList;
}
}

Datei anzeigen

@ -1,53 +0,0 @@
package tsp.headdb.command;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import tsp.headdb.api.HeadAPI;
import tsp.headdb.implementation.Head;
import tsp.headdb.util.Utils;
public class GiveCommand implements HeadSubCommand {
@Override
public void handle(CommandSender sender, String[] args) {
if (!sender.hasPermission("headdb.give")) {
Utils.sendMessage(sender, getLocalization().getMessage("noPermission"));
return;
}
if (args.length < 3) {
Utils.sendMessage(sender, "&c/hdb give <id> <player> &6[amount]");
return;
}
try {
int id = Integer.parseInt(args[1]);
Player target = Bukkit.getPlayer(args[2]);
if (target == null) {
Utils.sendMessage(sender, getLocalization().getMessage("invalidPlayer"));
return;
}
int amount = 1;
if (args.length > 3) {
amount = Integer.parseInt(args[3]);
}
Head head = HeadAPI.getHeadByID(id);
if (head == null) {
Utils.sendMessage(sender, "&cCould not find head with id &e" + id);
return;
}
ItemStack item = head.getMenuItem();
item.setAmount(amount);
target.getInventory().addItem(item);
Utils.sendMessage(sender, "&7Gave &6" + target.getName() + " &ex" + amount + " " + head.getName());
} catch (NumberFormatException nfe) {
Utils.sendMessage(sender, "&cID/Amount must be a number!");
}
}
}

Datei anzeigen

@ -1,71 +0,0 @@
package tsp.headdb.command;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import tsp.headdb.HeadDB;
import tsp.headdb.api.HeadAPI;
import tsp.headdb.util.Localization;
import tsp.headdb.util.Utils;
public class HeadDBCommand implements CommandExecutor {
private void handle(CommandSender sender, String[] args) {
Localization localization = HeadDB.getInstance().getLocalization();
if (args.length == 0) {
if (!sender.hasPermission("headdb.open")) {
Utils.sendMessage(sender, localization.getMessage("noPermission"));
return;
}
if (!(sender instanceof Player player)) {
Utils.sendMessage(sender, localization.getMessage("onlyPlayers"));
return;
}
Utils.sendMessage(player, localization.getMessage("databaseOpen"));
HeadAPI.openDatabase(player);
return;
}
String sub = args[0];
HeadSubCommand subCommand = null;
if (sub.equalsIgnoreCase("info") || sub.equalsIgnoreCase("i")) {
subCommand = new InfoCommand();
} else if (sub.equalsIgnoreCase("reload") || sub.equalsIgnoreCase("r")) {
subCommand = new ReloadCommand();
} else if (sub.equalsIgnoreCase("search") || sub.equalsIgnoreCase("s")) {
subCommand = new SearchCommand();
} else if (sub.equalsIgnoreCase("tagsearch") || sub.equalsIgnoreCase("ts")) {
subCommand = new TagSearchCommand();
} else if (sub.equalsIgnoreCase("give") || sub.equalsIgnoreCase("g")) {
subCommand = new GiveCommand();
} else if (sub.equalsIgnoreCase("update") || sub.equalsIgnoreCase("u")) {
subCommand = new UpdateCommand();
}
if (subCommand != null) {
subCommand.handle(sender, args);
return;
}
Utils.sendMessage(sender, " ");
Utils.sendMessage(sender, "&c&lHeadDB &c- &5Commands");
Utils.sendMessage(sender, "&7&oParameters:&c command &9(aliases)&c arguments... &7- Description");
Utils.sendMessage(sender, "&7 > &c/hdb &7- Opens the database");
Utils.sendMessage(sender, "&7 > &c/hdb info &9(i) &7- Plugin Information");
Utils.sendMessage(sender, "&7 > &c/hdb reload &9(r) &7- Reloads the messages file");
Utils.sendMessage(sender, "&7 > &c/hdb search &9(s) &c<name> &7- Search for heads matching a name");
Utils.sendMessage(sender, "&7 > &c/hdb tagsearch &9(ts) &c<tag> &7- Search for heads matching a tag");
Utils.sendMessage(sender, "&7 > &c/hdb update &9(u) &7- Forcefully update the database");
Utils.sendMessage(sender, "&7 > &c/hdb give &9(g) &c<id> <player> &6[amount] &7- Give player a head");
Utils.sendMessage(sender, " ");
}
@Override
public boolean onCommand(CommandSender sender, Command command, String s, String[] args) {
handle(sender, args);
return true;
}
}

Datei anzeigen

@ -1,21 +0,0 @@
package tsp.headdb.command;
import org.bukkit.command.CommandSender;
import tsp.headdb.HeadDB;
import tsp.headdb.util.Localization;
/**
* An interface for a HeadDB sub-command
*
* @author TheSilentPro
* @since 4.0.0
*/
public interface HeadSubCommand {
void handle(CommandSender sender, String[] args);
default Localization getLocalization() {
return HeadDB.getInstance().getLocalization();
}
}

Datei anzeigen

@ -1,18 +0,0 @@
package tsp.headdb.command;
import org.bukkit.command.CommandSender;
import tsp.headdb.HeadDB;
import tsp.headdb.api.HeadAPI;
import tsp.headdb.util.Utils;
public class InfoCommand implements HeadSubCommand {
@Override
public void handle(CommandSender sender, String[] args) {
// These should not be customizable
Utils.sendMessage(sender, "&7Running &cHeadDB - " + HeadDB.getInstance().getDescription().getVersion());
Utils.sendMessage(sender, "&7Created by &c" + HeadDB.getInstance().getDescription().getAuthors());
Utils.sendMessage(sender, "&7There are currently &c" + HeadAPI.getHeads().size() + " &7heads in the database.");
}
}

Datei anzeigen

@ -1,20 +0,0 @@
package tsp.headdb.command;
import org.bukkit.command.CommandSender;
import tsp.headdb.HeadDB;
import tsp.headdb.util.Utils;
public class ReloadCommand implements HeadSubCommand {
@Override
public void handle(CommandSender sender, String[] args) {
if (!sender.hasPermission("headdb.reload")) {
Utils.sendMessage(sender, getLocalization().getMessage("noPermission"));
return;
}
HeadDB.getInstance().getLocalization().load();
Utils.sendMessage(sender, getLocalization().getMessage("reloadMessages"));
}
}

Datei anzeigen

@ -1,38 +0,0 @@
package tsp.headdb.command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import tsp.headdb.api.HeadAPI;
import tsp.headdb.util.Utils;
public class SearchCommand implements HeadSubCommand {
@Override
public void handle(CommandSender sender, String[] args) {
if (!sender.hasPermission("headdb.search")) {
Utils.sendMessage(sender, getLocalization().getMessage("noPermission"));
return;
}
if (!(sender instanceof Player player)) {
Utils.sendMessage(sender, getLocalization().getMessage("onlyPlayers"));
return;
}
if (args.length < 2) {
Utils.sendMessage(player, "&c/hdb search <name>");
return;
}
StringBuilder builder = new StringBuilder();
for (int i = 1; i < args.length; i++) {
builder.append(args[i]);
if (i != args.length - 1) {
builder.append(" ");
}
}
String name = builder.toString();
Utils.sendMessage(sender, "&7Searching for &e" + name);
HeadAPI.openSearchDatabase(player, name);
}
}

Datei anzeigen

@ -1,32 +0,0 @@
package tsp.headdb.command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import tsp.headdb.api.HeadAPI;
import tsp.headdb.util.Utils;
public class TagSearchCommand implements HeadSubCommand {
@Override
public void handle(CommandSender sender, String[] args) {
if (!sender.hasPermission("headdb.tagsearch")) {
Utils.sendMessage(sender, getLocalization().getMessage("noPermission"));
return;
}
if (args.length < 2) {
Utils.sendMessage(sender, "&c/hdb tagsearch <tag>");
return;
}
if (!(sender instanceof Player player)) {
Utils.sendMessage(sender, getLocalization().getMessage("onlyPlayers"));
return;
}
String tag = args[1];
Utils.sendMessage(sender, "&7Searching for heads with tag &e" + tag);
HeadAPI.openTagSearchDatabase(player, tag);
}
}

Datei anzeigen

@ -1,26 +0,0 @@
package tsp.headdb.command;
import org.bukkit.command.CommandSender;
import tsp.headdb.api.HeadAPI;
import tsp.headdb.util.Utils;
import java.util.concurrent.TimeUnit;
public class UpdateCommand implements HeadSubCommand {
@Override
public void handle(CommandSender sender, String[] args) {
if (!sender.hasPermission("headdb.update")) {
Utils.sendMessage(sender, getLocalization().getMessage("noPermission"));
return;
}
Utils.sendMessage(sender, "&7Updating...");
long start = System.currentTimeMillis();
HeadAPI.getDatabase().update(heads -> {
Utils.sendMessage(sender, "&7Done! Took: &a" + TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis() - start) + " &7seconds");
Utils.sendMessage(sender, "&7There are &a" + HeadAPI.getHeads().size() + " &7heads in the database!");
});
}
}

Datei anzeigen

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

Datei anzeigen

@ -0,0 +1,80 @@
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 added to the inventory 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 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

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

Datei anzeigen

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

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

Datei anzeigen

@ -0,0 +1,27 @@
package tsp.headdb.core.command;
import org.bukkit.command.CommandSender;
import tsp.headdb.HeadDB;
import tsp.headdb.core.util.BuildProperties;
import tsp.smartplugin.player.PlayerUtils;
public class CommandInfo extends SubCommand {
public CommandInfo() {
super("info", "i");
}
@Override
public void handle(CommandSender sender, String[] args) {
if (HeadDB.getInstance().getConfig().getBoolean("showAdvancedPluginInfo")) {
BuildProperties build = HeadDB.getInstance().getBuildProperties();
PlayerUtils.sendMessage(sender, "&7Running &6HeadDB - " + build.getVersion());
PlayerUtils.sendMessage(sender, "&7Created by &6" + HeadDB.getInstance().getDescription().getAuthors());
PlayerUtils.sendMessage(sender, "&7Compiled on &6" + build.getTimestamp() + " &7by &6" + build.getAuthor());
} else {
PlayerUtils.sendMessage(sender, "&7Running &6HeadDB &7by &6TheSilentPro (Silent)");
PlayerUtils.sendMessage(sender, "&7GitHub: &6https://github.com/TheSilentPro/HeadDB");
}
}
}

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -0,0 +1,40 @@
package tsp.headdb.core.command;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
public class CommandManager {
private final Map<String, SubCommand> commands = new HashMap<>();
public void register(SubCommand command) {
this.commands.put(command.getName(), command);
}
public Optional<SubCommand> getCommand(String name) {
SubCommand command = commands.get(name);
if (command != null) {
return Optional.of(command);
}
return getCommandByAlias(name);
}
public Optional<SubCommand> getCommandByAlias(String alias) {
for (SubCommand entry : commands.values()) {
if (entry.getAliases().isPresent()) {
if (Arrays.stream(entry.getAliases().get()).anyMatch(name -> name.equalsIgnoreCase(alias))) {
return Optional.of(entry);
}
}
}
return Optional.empty();
}
public Map<String, SubCommand> getCommandsMap() {
return commands;
}
}

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -0,0 +1,19 @@
package tsp.headdb.core.command;
import org.bukkit.command.CommandSender;
import tsp.headdb.HeadDB;
import tsp.headdb.core.api.HeadAPI;
public class CommandUpdate extends SubCommand {
public CommandUpdate() {
super("update", "u");
}
@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"));
}
}

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -0,0 +1,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 final 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

@ -0,0 +1,20 @@
package tsp.headdb.core.listener;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import tsp.headdb.HeadDB;
public final class PlayerJoinListener implements Listener {
public PlayerJoinListener() {
Bukkit.getPluginManager().registerEvents(this, HeadDB.getInstance());
}
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
//HeadDB.getInstance().getStorage().getPlayerStorage().register(new PlayerData(event.getPlayer().getUniqueId(), ""));
}
}

Datei anzeigen

@ -0,0 +1,20 @@
package tsp.headdb.core.storage;
import org.jetbrains.annotations.NotNull;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
public final class HeadDBThreadFactory implements ThreadFactory {
private HeadDBThreadFactory() {}
public static final HeadDBThreadFactory FACTORY = new HeadDBThreadFactory();
private final AtomicInteger ID = new AtomicInteger(1);
@Override
public Thread newThread(@NotNull Runnable r) {
return new Thread(r, "headdb-thread-" + ID.getAndIncrement());
}
}

Datei anzeigen

@ -0,0 +1,12 @@
package tsp.headdb.core.storage;
import java.io.Serializable;
import java.util.Set;
import java.util.UUID;
// Notice that there is no need to add any additional boilerplate in order to make this serializable.
// Specifically, there is no need to add a serialVersionUID field,
// since the serialVersionUID of a record class is 0L unless explicitly declared,
// and the requirement for matching the serialVersionUID value is waived for record classes.
// Source: https://docs.oracle.com/en/java/javase/15/serializable-records/index.html#:~:text=Specifically%2C%20there%20is%20no%20need,is%20waived%20for%20record%20classes.
public record PlayerData(UUID uniqueId, Set<String> favorites) implements Serializable {}

Datei anzeigen

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

Datei anzeigen

@ -0,0 +1,33 @@
package tsp.headdb.core.storage;
import tsp.headdb.HeadDB;
import java.io.File;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
public class Storage {
private final Executor executor;
private final PlayerStorage playerStorage;
public Storage(int threads) {
executor = Executors.newFixedThreadPool(threads, HeadDBThreadFactory.FACTORY);
validateDataDirectory();
playerStorage = new PlayerStorage(HeadDB.getInstance(), this);
}
public PlayerStorage getPlayerStorage() {
return playerStorage;
}
public Executor getExecutor() {
return executor;
}
private void validateDataDirectory() {
//noinspection ResultOfMethodCallIgnored
new File(HeadDB.getInstance().getDataFolder(), "data").mkdir();
}
}

Datei anzeigen

@ -0,0 +1,35 @@
package tsp.headdb.core.task;
import tsp.headdb.HeadDB;
import tsp.headdb.core.api.HeadAPI;
import tsp.smartplugin.tasker.Task;
import java.util.concurrent.TimeUnit;
@SuppressWarnings("ClassCanBeRecord")
public class UpdateTask implements Task {
private final long interval;
public UpdateTask(long interval) {
this.interval = interval;
}
@Override
public void run() {
HeadAPI.getDatabase().update((time, heads) -> HeadDB.getInstance().getLog().debug("Fetched: " + heads.size() + " Heads | Provider: " + HeadAPI.getDatabase().getRequester().getProvider().name() + " | Time: " + time + "ms (" + TimeUnit.MILLISECONDS.toSeconds(time) + "s)"));
HeadDB.getInstance().getStorage().getPlayerStorage().backup();
HeadDB.getInstance().getLog().debug("UpdateTask finished!");
}
@Override
public long getRepeatInterval() {
return interval;
}
@Override
public boolean isAsync() {
return true;
}
}

Datei anzeigen

@ -0,0 +1,45 @@
package tsp.headdb.core.util;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* This class contains the properties of the build.
*
* @author TheSilentPro (Silent)
*/
public class BuildProperties {
private String version = "unknown";
private String timestamp = "unknown";
private String author = "unknown";
public BuildProperties(JavaPlugin plugin) {
InputStream in = plugin.getResource("plugin.yml");
if (in == null) {
plugin.getLogger().severe("Could not get plugin.yml to read build information.");
return;
}
YamlConfiguration data = YamlConfiguration.loadConfiguration(new InputStreamReader(in));
this.version = data.getString("version", "unknown");
this.timestamp = data.getString("buildTimestamp", "unknown");
this.author = data.getString("buildAuthor", "unknown");
}
public String getVersion() {
return version;
}
public String getTimestamp() {
return timestamp;
}
public String getAuthor() {
return author;
}
}

Datei anzeigen

@ -0,0 +1,234 @@
package tsp.headdb.core.util;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import me.clip.placeholderapi.PlaceholderAPI;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import tsp.headdb.HeadDB;
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.head.Head;
import tsp.smartplugin.inventory.Button;
import tsp.smartplugin.inventory.PagedPane;
import tsp.smartplugin.inventory.Pane;
import tsp.smartplugin.utils.StringUtils;
import tsp.smartplugin.utils.Validate;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
public class Utils {
private static final HeadDB instance = HeadDB.getInstance();
public static Optional<UUID> validateUniqueId(@Nonnull String raw) {
try {
return Optional.of(UUID.fromString(raw));
} catch (IllegalArgumentException ignored) {
return Optional.empty();
}
}
@ParametersAreNonnullByDefault
public static String translateTitle(String raw, int size, String category, @Nullable String query) {
return StringUtils.colorize(raw)
.replace("%size%", String.valueOf(size))
.replace("%category%", category)
.replace("%query%", (query != null ? query : "%query%"));
}
@ParametersAreNonnullByDefault
public static String translateTitle(String raw, int size, String category) {
return translateTitle(raw, size, category, null);
}
public static boolean matches(String provided, String query) {
provided = ChatColor.stripColor(provided.toLowerCase(Locale.ROOT));
query = query.toLowerCase(Locale.ROOT);
return provided.equals(query)
|| provided.startsWith(query)
|| provided.contains(query);
//|| provided.endsWith(query);
}
public static void fill(@Nonnull Pane pane, @Nullable ItemStack item) {
Validate.notNull(pane, "Pane can not be null!");
if (item == null) {
item = new ItemStack(Material.BLACK_STAINED_GLASS_PANE);
ItemMeta meta = item.getItemMeta();
//noinspection ConstantConditions
meta.setDisplayName("");
item.setItemMeta(meta);
}
for (int i = 0; i < pane.getInventory().getSize(); i++) {
ItemStack current = pane.getInventory().getItem(i);
if (current == null || current.getType().isAir()) {
pane.setButton(i, new Button(item, e -> e.setCancelled(true)));
}
}
}
@SuppressWarnings("SpellCheckingInspection")
public static PagedPane createPaged(Player player, String title) {
PagedPane main = new PagedPane(4, 6, title);
HeadAPI.getHeadByTexture("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODY1MmUyYjkzNmNhODAyNmJkMjg2NTFkN2M5ZjI4MTlkMmU5MjM2OTc3MzRkMThkZmRiMTM1NTBmOGZkYWQ1ZiJ9fX0=").ifPresent(head -> main.setBackItem(head.getItem(player.getUniqueId())));
HeadAPI.getHeadByTexture("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2Q5MWY1MTI2NmVkZGM2MjA3ZjEyYWU4ZDdhNDljNWRiMDQxNWFkYTA0ZGFiOTJiYjc2ODZhZmRiMTdmNGQ0ZSJ9fX0=").ifPresent(head -> main.setCurrentItem(head.getItem(player.getUniqueId())));
HeadAPI.getHeadByTexture("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmEzYjhmNjgxZGFhZDhiZjQzNmNhZThkYTNmZTgxMzFmNjJhMTYyYWI4MWFmNjM5YzNlMDY0NGFhNmFiYWMyZiJ9fX0=").ifPresent(head -> main.setNextItem(head.getItem(player.getUniqueId())));
main.setControlCurrent(new Button(main.getCurrentItem(), e -> Bukkit.dispatchCommand(player, "hdb")));
return main;
}
@ParametersAreNonnullByDefault
public static void addHeads(Player player, @Nullable Category category, PagedPane pane, Collection<Head> heads) {
for (Head head : heads) {
ItemStack item = head.getItem(player.getUniqueId());
pane.addButton(new Button(item, e -> {
e.setCancelled(true);
if (category != null && instance.getConfig().getBoolean("requireCategoryPermission") && !player.hasPermission("headdb.category." + category.getName())) {
instance.getLocalization().sendMessage(player.getUniqueId(), "noPermission");
return;
}
if (e.isLeftClick()) {
int amount = 1;
if (e.isShiftClick()) {
amount = 64;
}
purchase(player, head, amount);
} else if (e.isRightClick()) {
HeadDB.getInstance().getStorage().getPlayerStorage().addFavorite(player.getUniqueId(), head.getTexture());
}
}));
}
}
private static CompletableFuture<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
} else {
BigDecimal cost = BigDecimal.valueOf(HeadDB.getInstance().getConfig().getDouble("economy.cost." + head.getCategory().getName()) * amount);
HeadDB.getInstance().getLocalization().sendMessage(player.getUniqueId(), "processPayment", msg -> msg
.replace("%name%", head.getName())
.replace("%amount%", String.valueOf(amount))
.replace("%cost%", HeadDB.getInstance().getDecimalFormat().format(cost))
);
return optional.get().purchase(player, cost).thenApply(success -> {
HeadPurchaseEvent event = new HeadPurchaseEvent(player, head, cost, success);
Bukkit.getPluginManager().callEvent(event);
return !event.isCancelled() && success;
});
}
}
private 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(), () -> {
ItemStack item = head.getItem(player.getUniqueId());
item.setAmount(amount);
player.getInventory().addItem(item);
HeadDB.getInstance().getConfig().getStringList("commands.purchase").forEach(command -> {
if (command.isEmpty()) {
return;
}
if (Hooks.PAPI.enabled()) {
command = PlaceholderAPI.setPlaceholders(player, command);
}
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), command);
});
});
}
});
}
public static Optional<String> getTexture(ItemStack head) {
ItemMeta meta = head.getItemMeta();
if (meta == null) {
return Optional.empty();
}
try {
Field profileField = meta.getClass().getDeclaredField("profile");
profileField.setAccessible(true);
GameProfile profile = (GameProfile) profileField.get(meta);
if (profile == null) {
return Optional.empty();
}
return profile.getProperties().get("textures").stream()
.filter(p -> p.getName().equals("textures"))
.findAny()
.map(Property::getValue);
} catch (NoSuchFieldException | SecurityException | IllegalAccessException e ) {
e.printStackTrace();
return Optional.empty();
}
}
public static int resolveInt(String raw) {
try {
return Integer.parseInt(raw);
} catch (NumberFormatException nfe) {
return 1;
}
}
public static ItemStack getItemFromConfig(String path, Material def) {
ConfigurationSection section = HeadDB.getInstance().getConfig().getConfigurationSection(path);
Validate.notNull(section, "Section can not be null!");
Material material = Material.matchMaterial(section.getString("material", def.name()));
if (material == null) {
material = def;
}
ItemStack item = new ItemStack(material);
ItemMeta meta = item.getItemMeta();
if (meta != null) {
//noinspection ConstantConditions
meta.setDisplayName(StringUtils.colorize(section.getString("name")));
List<String> lore = new ArrayList<>();
for (String line : section.getStringList("lore")) {
if (line != null && !line.isEmpty()) {
lore.add(StringUtils.colorize(line));
}
}
meta.setLore(lore);
item.setItemMeta(meta);
}
return item;
}
}

Datei anzeigen

@ -1,48 +0,0 @@
package tsp.headdb.economy;
import org.bukkit.entity.Player;
import java.math.BigDecimal;
import java.util.function.Consumer;
/**
* An interface for generalizing Economy Provider's
*
* @author TheSilentPro
* @since 4.0.0
* @see VaultProvider
* @see TreasuryProvider
*/
public interface BasicEconomyProvider {
/**
* Retrieve if the player can purchase a head using this economy provider
*
* @param player The player
* @param cost The cost
* @param result If the player has enough to purchase
*/
default void canPurchase(Player player, BigDecimal cost, Consumer<Boolean> result) {
result.accept(true);
}
/**
* Charge the player a specific amount using this economy provider
*
* @param player The player
* @param amount The amount
* @param result If the transaction was successful
*/
default void charge(Player player, BigDecimal amount, Consumer<Boolean> result) {
result.accept(true);
}
/**
* Convenience method for initializing economy
*
* @see VaultProvider#initProvider()
* @see TreasuryProvider#initProvider()
*/
void initProvider();
}

Datei anzeigen

@ -1,98 +0,0 @@
package tsp.headdb.economy;
import me.lokka30.treasury.api.common.service.Service;
import me.lokka30.treasury.api.common.service.ServiceRegistry;
import me.lokka30.treasury.api.economy.EconomyProvider;
import me.lokka30.treasury.api.economy.account.PlayerAccount;
import me.lokka30.treasury.api.economy.currency.Currency;
import me.lokka30.treasury.api.economy.response.EconomyException;
import me.lokka30.treasury.api.economy.response.EconomySubscriber;
import me.lokka30.treasury.api.economy.transaction.EconomyTransactionInitiator;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import tsp.headdb.HeadDB;
import tsp.headdb.util.Log;
import java.math.BigDecimal;
import java.util.Optional;
import java.util.function.Consumer;
/**
* A {@link BasicEconomyProvider} for Treasury
*
* @author TheSilentPro
* @since 4.0.0
*/
public class TreasuryProvider implements BasicEconomyProvider {
private EconomyProvider provider;
private EconomyTransactionInitiator<?> transactionInitiator;
private Currency currency;
@Override
public void canPurchase(Player player, BigDecimal cost, Consumer<Boolean> result) {
EconomySubscriber
.<Boolean>asFuture(s -> provider.hasPlayerAccount(player.getUniqueId(), s))
.thenCompose(val -> {
if (Boolean.TRUE.equals(val)) {
return EconomySubscriber.<PlayerAccount>asFuture(s -> provider.retrievePlayerAccount(player.getUniqueId(), s));
} else {
return EconomySubscriber.<PlayerAccount>asFuture(s -> provider.createPlayerAccount(player.getUniqueId(), s));
}
})
.thenCompose(account -> EconomySubscriber.<BigDecimal>asFuture(s -> account.retrieveBalance(currency, s)))
.whenComplete((bal, ex) -> result.accept(bal.compareTo(cost) >= 0));
}
@Override
public void charge(Player player, BigDecimal amount, Consumer<Boolean> result) {
EconomySubscriber
.<Boolean>asFuture(s -> provider.hasPlayerAccount(player.getUniqueId(), s))
.thenCompose(val -> {
if (Boolean.TRUE.equals(val)) {
return EconomySubscriber.<PlayerAccount>asFuture(s -> provider.retrievePlayerAccount(player.getUniqueId(), s));
} else {
return EconomySubscriber.<PlayerAccount>asFuture(s -> provider.createPlayerAccount(player.getUniqueId(), s));
}
}).whenComplete((account, ex) -> account.withdrawBalance(
amount,
transactionInitiator,
currency,
new EconomySubscriber<BigDecimal>() {
@Override
public void succeed(@NotNull BigDecimal bigDecimal) {
result.accept(true);
}
@Override
public void fail(@NotNull EconomyException exception) {
result.accept(false);
Log.error(ex);
}
}));
}
@Override
public void initProvider() {
Optional<Service<EconomyProvider>> service = ServiceRegistry.INSTANCE.serviceFor(EconomyProvider.class);
if(!service.isPresent()) {
Log.error("Unable to find a supported economy plugin for Treasury!");
return;
}
provider = service.get().get();
transactionInitiator = EconomyTransactionInitiator.createInitiator(EconomyTransactionInitiator.Type.PLUGIN, "HeadDB");
String rawCurrency = HeadDB.getInstance().getConfig().getString("economy.currency");
if (rawCurrency == null || rawCurrency.isEmpty()) {
currency = provider.getPrimaryCurrency();
} else {
provider.getCurrencies().stream()
.filter(c -> c.getIdentifier().equalsIgnoreCase(rawCurrency))
.findFirst()
.ifPresentOrElse(c -> currency = c, () -> Log.error("Could not find currency: " + rawCurrency));
}
}
}

Datei anzeigen

@ -1,52 +0,0 @@
package tsp.headdb.economy;
import net.milkbowl.vault.economy.Economy;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.plugin.RegisteredServiceProvider;
import tsp.headdb.util.Log;
import tsp.headdb.util.Utils;
import java.math.BigDecimal;
import java.util.function.Consumer;
/**
* A {@link BasicEconomyProvider} for Vault
*
* @author TheSilentPro
* @since 4.0.0
*/
public class VaultProvider implements BasicEconomyProvider {
private Economy economy;
@Override
public void canPurchase(Player player, BigDecimal cost, Consumer<Boolean> result) {
Utils.async(t -> result.accept(economy.has(player, cost.doubleValue())));
}
@Override
public void charge(Player player, BigDecimal amount, Consumer<Boolean> result) {
Utils.async(t -> result.accept(economy.withdrawPlayer(player, amount.doubleValue()).transactionSuccess()));
}
public void initProvider() {
if (!Bukkit.getServer().getPluginManager().isPluginEnabled("Vault")) {
Log.error("Vault is not installed!");
return;
}
RegisteredServiceProvider<Economy> economyProvider = Bukkit.getServer().getServicesManager().getRegistration(Economy.class);
if (economyProvider == null) {
Log.error("Could not find vault economy provider!");
return;
}
economy = economyProvider.getProvider();
}
public Economy getProvider() {
return economy;
}
}

Datei anzeigen

@ -1,91 +0,0 @@
package tsp.headdb.implementation;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import tsp.headdb.api.HeadAPI;
import javax.annotation.Nullable;
import java.util.Objects;
import java.util.Optional;
/**
* Represents a category for heads
*
* @author TheSilentPro
*/
public enum Category {
ALPHABET("alphabet", ChatColor.YELLOW, 20),
ANIMALS("animals", ChatColor.DARK_AQUA, 21),
BLOCKS("blocks", ChatColor.DARK_GRAY, 22),
DECORATION("decoration", ChatColor.LIGHT_PURPLE, 23),
FOOD_DRINKS("food-drinks", ChatColor.GOLD, 24),
HUMANS("humans", ChatColor.DARK_BLUE, 29),
HUMANOID("humanoid", ChatColor.AQUA, 30),
MISCELLANEOUS("miscellaneous", ChatColor.DARK_GREEN, 31),
MONSTERS("monsters", ChatColor.RED, 32),
PLANTS("plants", ChatColor.GREEN, 33);
private final String name;
private final ChatColor color;
private final int location;
private static final Category[] cache = values();
Category(String name, ChatColor color, int location) {
this.name = name;
this.color = color;
this.location = location;
}
public String getName() {
return name;
}
public ChatColor getColor() {
return color;
}
public int getLocation() {
return location;
}
/**
* Retrieve the first valid head from a category
*
* @return First valid head
*/
public ItemStack getItem() {
Optional<Head> result = HeadAPI.getHeads(this).stream()
.filter(Objects::nonNull)
.findFirst();
if (result.isPresent()) {
return result.get().getMenuItem();
} else {
return new ItemStack(Material.PLAYER_HEAD);
}
}
/**
* Retrieve a {@link Category} by name
*
* @param name The name
* @return The category if it exists. Else it returns null
*/
@Nullable
public static Category getByName(String name) {
for (Category category : cache) {
if (category.getName().equalsIgnoreCase(name)) {
return category;
}
}
return null;
}
public static Category[] getCache() {
return cache;
}
}

Datei anzeigen

@ -1,11 +0,0 @@
package tsp.headdb.implementation;
import tsp.headdb.HeadDB;
public class DataSaveTask implements Runnable {
@Override
public void run() {
HeadDB.getInstance().getPlayerData().save();
}
}

Datei anzeigen

@ -1,16 +0,0 @@
package tsp.headdb.implementation;
import tsp.headdb.api.HeadAPI;
import tsp.headdb.util.Log;
/**
* Task that updates the database on an interval
*/
public class DatabaseUpdateTask implements Runnable {
@Override
public void run() {
HeadAPI.getDatabase().update(heads -> Log.info("Fetched " + HeadAPI.getHeads().size() + " heads!"));
}
}

Datei anzeigen

@ -1,142 +0,0 @@
package tsp.headdb.implementation;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import org.apache.commons.lang.Validate;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
import tsp.headdb.util.Log;
import tsp.headdb.util.Utils;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;
/**
* Represents a Head that a player can obtain via the database
*
* @author TheSilentPro
*/
public class Head {
public static final Pattern SPLIT = Pattern.compile(",");
private String name;
private UUID uuid;
private String value;
private Category category;
private int id;
private List<String> tags;
private ItemStack menuItem;
public Head() {}
public Head(int id) {
this.id = id;
}
public ItemStack getMenuItem() {
if (menuItem == null) {
Validate.notNull(name, "name must not be null!");
Validate.notNull(uuid, "uuid must not be null!");
Validate.notNull(value, "value must not be null!");
ItemStack item = new ItemStack(Material.PLAYER_HEAD);
SkullMeta meta = (SkullMeta) item.getItemMeta();
meta.setDisplayName(Utils.colorize(category != null ? category.getColor() + name : "&8" + name));
// set skull owner
GameProfile profile = new GameProfile(uuid, name);
profile.getProperties().put("textures", new Property("textures", value));
Field profileField;
try {
profileField = meta.getClass().getDeclaredField("profile");
profileField.setAccessible(true);
profileField.set(meta, profile);
} catch (NoSuchFieldException | IllegalArgumentException | IllegalAccessException ex) {
Log.error("Could not set skull owner for " + uuid.toString() + " | Stack Trace:");
Log.error(ex);
}
meta.setLore(Arrays.asList(
Utils.colorize("&cID: " + id),
Utils.colorize("&e" + buildTagLore(tags)),
"",
Utils.colorize("&8Right-Click to add/remove from favorites.")
));
item.setItemMeta(meta);
menuItem = item;
}
return menuItem;
}
public String getName() {
return name;
}
public UUID getUniqueId() {
return uuid;
}
public String getValue() {
return value;
}
public Category getCategory() {
return category;
}
public int getId() {
return id;
}
public List<String> getTags() {
return tags;
}
public Head name(String name) {
this.name = name;
return this;
}
public Head uniqueId(UUID uuid) {
this.uuid = uuid;
return this;
}
public Head value(String value) {
this.value = value;
return this;
}
public Head category(Category category) {
this.category = category;
return this;
}
public Head id(int id) {
this.id = id;
return this;
}
public Head tags(String tags) {
this.tags = Arrays.asList(SPLIT.split(tags));
return this;
}
private String buildTagLore(List<String> tags) {
StringBuilder builder = new StringBuilder();
for (int i = 0; i < tags.size(); i++) {
builder.append(tags.get(i));
if (i != tags.size() - 1) {
builder.append(",");
}
}
return builder.toString();
}
}

Datei anzeigen

@ -1,298 +0,0 @@
package tsp.headdb.implementation;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.plugin.java.JavaPlugin;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import tsp.headdb.HeadDB;
import tsp.headdb.api.event.DatabaseUpdateEvent;
import tsp.headdb.util.Log;
import tsp.headdb.util.Utils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import javax.annotation.Nonnull;
/**
* This is the Database that holds all heads
*
* @author TheSilentPro
*/
// TODO: Optionals instead of null.
public class HeadDatabase {
private final JavaPlugin plugin;
private final EnumMap<Category, List<Head>> heads = new EnumMap<>(Category.class);
private long refresh;
private int timeout;
private long updated;
private int nextId; // Internal only
public HeadDatabase(JavaPlugin plugin) {
this.plugin = plugin;
this.refresh = 3600;
this.timeout = 5000;
}
public Head getHeadByValue(String value) {
List<Head> fetched = getHeads();
for (Head head : fetched) {
if (head.getValue().equals(value)) {
return head;
}
}
return null;
}
public Head getHeadByID(int id) {
List<Head> fetched = getHeads();
for (Head head : fetched) {
if (head.getId() == id) {
return head;
}
}
return null;
}
public Head getHeadByUniqueId(UUID uuid) {
List<Head> fetched = getHeads();
for (Head head : fetched) {
if (head.getUniqueId().equals(uuid)) {
return head;
}
}
return null;
}
public List<Head> getHeadsByTag(String tag) {
List<Head> result = new ArrayList<>();
List<Head> fetched = getHeads();
tag = tag.toLowerCase(Locale.ROOT);
for (Head head : fetched) {
for (String t : head.getTags()) {
if (t.toLowerCase(Locale.ROOT).contains(tag)) {
result.add(head);
}
}
}
return result;
}
public List<Head> getHeadsByName(Category category, String name) {
List<Head> result = new ArrayList<>();
List<Head> fetched = getHeads(category);
for (Head head : fetched) {
String hName = ChatColor.stripColor(head.getName().toLowerCase(Locale.ROOT));
if (hName.contains(ChatColor.stripColor(name.toLowerCase(Locale.ROOT)))) {
result.add(head);
}
}
return result;
}
public List<Head> getHeadsByName(String name) {
List<Head> result = new ArrayList<>();
for (Category category : Category.getCache()) {
result.addAll(getHeadsByName(category, name));
}
return result;
}
@Nonnull
public List<Head> getHeads(Category category) {
List<Head> result = heads.get(category);
return result != null ? Collections.unmodifiableList(result) : Collections.emptyList();
}
/**
* Gets all heads from the cache if available.
*
* @return List containing each head in its category.
*/
@Nonnull
public List<Head> getHeads() {
List<Head> result = new ArrayList<>();
for (Category category : heads.keySet()) {
result.addAll(getHeads(category));
}
return result;
}
public void getHeadsNoCache(Consumer<Map<Category, List<Head>>> resultSet) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, task -> {
Log.debug("[" + plugin.getName() + "] Updating database... ");
EnumMap<Category, List<Head>> result = new EnumMap<>(Category.class);
Category[] categories = Category.getCache();
for (Category category : categories) {
Log.debug("Caching heads from: " + category.getName());
List<Head> results = new ArrayList<>();
try {
// First the original api is fetched
results = gather("https://minecraft-heads.com/scripts/api.php?cat=" + category.getName() + "&tags=true", category);
} catch (ParseException | IOException e) {
Log.debug("[" + plugin.getName() + "] Failed to fetch heads (no-cache) from category " + category.getName() + " | Stack Trace:");
Log.debug(e);
Log.warning("Failed to fetch heads for " + category.getName());
if (HeadDB.getInstance().getConfig().getBoolean("fallback", true)) {
Log.info("Attempting fallback provider for: " + category.getName());
try {
// If the original fails and fallback is enabled, fetch from static archive
results = gather("https://heads.pages.dev/archive/" + category.getName() + ".json", category);
} catch (IOException | ParseException ex) {
Log.error("Failed to fetch heads for " + category.getName() + "! (OF)"); // OF = Original-Fallback, both failed
Log.error(ex);
}
}
}
updated = System.nanoTime();
result.put(category, results);
}
resultSet.accept(result);
});
}
/**
* Fetches and gathers the heads from the url.
* For internal use only!
*
* @param url The url
* @param category The category of the heads
* @return List of heads for that category
* @throws IOException error
* @throws ParseException error
*/
protected List<Head> gather(String url, Category category) throws IOException, ParseException {
long start = System.currentTimeMillis();
List<Head> headList = new ArrayList<>();
// TODO: gson
JSONParser parser = new JSONParser();
JSONArray array = (JSONArray) parser.parse(fetch(url));
for (Object o : array) {
JSONObject obj = (JSONObject) o;
String rawUUID = obj.get("uuid").toString();
UUID uuid;
if (Utils.validateUniqueId(rawUUID)) {
uuid = UUID.fromString(rawUUID);
} else {
uuid = UUID.randomUUID();
}
Head head = new Head(nextId++)
.name(obj.get("name").toString())
.uniqueId(uuid)
.value(obj.get("value").toString())
.tags(obj.get("tags") != null ? obj.get("tags").toString() : "None")
.category(category);
headList.add(head);
}
long elapsed = (System.currentTimeMillis() - start);
Log.debug(category.getName() + " -> Done! Time: " + elapsed + "ms (" + TimeUnit.MILLISECONDS.toSeconds(elapsed) + "s)");
return headList;
}
/**
* Fetches heads from the url.
* For internal use only!
*
* @param url The url
* @return JSON-string response
* @throws IOException error
*/
protected String fetch(String url) throws IOException {
String line;
StringBuilder response = new StringBuilder();
URLConnection connection = new URL(url).openConnection();
connection.setConnectTimeout(timeout);
connection.setRequestProperty("User-Agent", plugin.getName() + "-DatabaseUpdater");
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
while ((line = in.readLine()) != null) {
response.append(line);
}
return response.toString();
}
public void update(Consumer<Map<Category, List<Head>>> result) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, task -> getHeadsNoCache(headsList -> {
if (headsList == null) {
Log.error("[" + plugin.getName() + "] Failed to update database! Check above for any errors.");
result.accept(null);
return;
}
heads.clear();
heads.putAll(headsList);
result.accept(heads);
Bukkit.getPluginManager().callEvent(new DatabaseUpdateEvent(this, heads));
}));
}
/**
* Get the last time the database was updated.
*
* @return Last update in seconds
*/
public long getLastUpdate() {
long now = System.nanoTime();
long elapsed = now - updated;
return TimeUnit.NANOSECONDS.toSeconds(elapsed);
}
/**
* Checks if the update is past the refresh time
*
* @return Whether the update is old
*/
public boolean isLastUpdateOld() {
return getLastUpdate() >= refresh;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public int getTimeout() {
return timeout;
}
public long getRefresh() {
return refresh;
}
public void setRefresh(long refresh) {
this.refresh = refresh;
}
public JavaPlugin getPlugin() {
return plugin;
}
}

Datei anzeigen

@ -1,87 +0,0 @@
package tsp.headdb.implementation;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
import tsp.headdb.util.Utils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
/**
* Represents a local player head that can be obtained via the LocalHeads option
*
* @author TheSilentPro
*/
public class LocalHead extends Head {
private UUID uuid;
private String name;
public LocalHead(UUID uuid) {
this.uuid = uuid;
}
@Override
public ItemStack getMenuItem() {
Validate.notNull(uuid, "uuid must not be null!");
ItemStack item = new ItemStack(Material.PLAYER_HEAD);
SkullMeta meta = (SkullMeta) item.getItemMeta();
meta.setOwningPlayer(Bukkit.getOfflinePlayer(uuid));
meta.setDisplayName(Utils.colorize("&e" + name));
List<String> lore = new ArrayList<>();
lore.add(Utils.colorize("&7UUID: " + uuid.toString()));
meta.setLore(lore);
item.setItemMeta(meta);
return item;
}
@Override
public UUID getUniqueId() {
return uuid;
}
@Override
public String getName() {
return name;
}
@Override
public String getValue() {
return null;
}
@Override
public Category getCategory() {
return null;
}
@Override
public int getId() {
return -1;
}
@Override
public List<String> getTags() {
return Collections.emptyList();
}
@Override
public LocalHead uniqueId(UUID uuid) {
this.uuid = uuid;
return this;
}
@Override
public LocalHead name(String name) {
this.name = name;
return this;
}
}

Datei anzeigen

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

Datei anzeigen

@ -0,0 +1,103 @@
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.implementation.category.Category;
import tsp.smartplugin.builder.item.ItemBuilder;
import tsp.smartplugin.localization.TranslatableLocalization;
import tsp.smartplugin.utils.Validate;
import javax.annotation.ParametersAreNonnullByDefault;
import java.lang.reflect.Field;
import java.util.Locale;
import java.util.UUID;
public class Head {
private final int id;
private final UUID uniqueId;
private final String name;
private final String texture;
private final String tags;
private final String updated;
private final Category category;
private ItemStack item;
@ParametersAreNonnullByDefault
public Head(int id, UUID uniqueId, String name, String texture, String tags, String updated, Category category) {
Validate.notNull(uniqueId, "Unique id can not be null!");
Validate.notNull(name, "Name can not be null!");
Validate.notNull(texture, "Texture can not be null!");
Validate.notNull(tags, "Tags can not be null!");
Validate.notNull(updated, "Updated can not be null!");
Validate.notNull(category, "Category can not be null!");
this.id = id;
this.uniqueId = uniqueId;
this.name = name;
this.texture = texture;
this.tags = tags;
this.updated = updated;
this.category = category;
}
public ItemStack getItem(UUID receiver) {
if (item == null) {
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 ConstantConditions
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);
}
return item.clone(); // Return clone that changes are not reflected
}
public int getId() {
return id;
}
public UUID getUniqueId() {
return uniqueId;
}
public String getName() {
return name;
}
public String getTexture() {
return texture;
}
public String getTags() {
return tags;
}
public String getUpdated() {
return updated;
}
public Category getCategory() {
return category;
}
}

Datei anzeigen

@ -0,0 +1,70 @@
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 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<>();
}
public Map<Category, List<Head>> getHeads() {
return heads;
}
public void getHeadsNoCache(BiConsumer<Long, Map<Category, List<Head>>> heads) {
getScheduler().runTaskAsynchronously(plugin, () -> {
long start = System.currentTimeMillis();
Map<Category, List<Head>> result = new HashMap<>();
for (Category category : Category.VALUES) {
requester.fetchAndResolve(category, response -> result.put(category, response));
}
heads.accept(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 long getTimestamp() {
return timestamp;
}
public JavaPlugin getPlugin() {
return plugin;
}
public BukkitScheduler getScheduler() {
return scheduler;
}
public Requester getRequester() {
return requester;
}
}

Datei anzeigen

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

Datei anzeigen

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

Datei anzeigen

@ -0,0 +1,96 @@
package tsp.headdb.implementation.requester;
import org.bukkit.plugin.java.JavaPlugin;
import tsp.headdb.HeadDB;
import tsp.headdb.core.util.Utils;
import tsp.headdb.implementation.category.Category;
import tsp.headdb.implementation.head.Head;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.function.Consumer;
public class Requester {
private final JavaPlugin plugin;
private HeadProvider provider;
public Requester(JavaPlugin plugin, HeadProvider provider) {
this.plugin = plugin;
this.provider = provider;
}
public void fetchAndResolve(Category category, Consumer<List<Head>> heads) {
try {
fetch(category, response -> {
List<Head> result = new ArrayList<>();
if (response.code() != 200) {
heads.accept(result);
return;
}
JsonArray main = JsonParser.parseString(response.response()).getAsJsonArray();
for (JsonElement entry : main) {
JsonObject obj = entry.getAsJsonObject();
result.add(new Head(
obj.get("id").getAsInt(),
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);
});
} 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);
}
}
}
public void fetch(Category category, Consumer<Response> response) throws IOException {
HttpURLConnection connection = (HttpURLConnection) new URL(provider.getFormattedUrl(category)).openConnection();
connection.setConnectTimeout(5000);
connection.setRequestMethod("GET");
connection.setRequestProperty("User-Agent", plugin.getName() + "/" + plugin.getDescription().getVersion());
connection.setRequestProperty("Accept", "application/json");
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
StringBuilder builder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
}
response.accept(new Response(builder.toString(), connection.getResponseCode(), connection.getHeaderField("date")));
}
connection.disconnect();
}
public HeadProvider getProvider() {
return provider;
}
public JavaPlugin getPlugin() {
return plugin;
}
}

Datei anzeigen

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

Datei anzeigen

@ -1,78 +0,0 @@
package tsp.headdb.inventory;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
import java.util.Objects;
import java.util.function.Consumer;
/**
* A button
*/
public class Button {
private static int counter = 0;
private static final int ID = counter++;
private final ItemStack itemStack;
private Consumer<InventoryClickEvent> action;
/**
* @param itemStack The Item
*/
@SuppressWarnings("unused")
public Button(ItemStack itemStack) {
this(itemStack, event -> {
});
}
/**
* @param itemStack The Item
* @param action The action
*/
public Button(ItemStack itemStack, Consumer<InventoryClickEvent> action) {
this.itemStack = itemStack;
this.action = action;
}
/**
* @return The icon
*/
@SuppressWarnings("WeakerAccess")
public ItemStack getItemStack() {
return itemStack;
}
/**
* @param action The new action
*/
@SuppressWarnings("unused")
public void setAction(Consumer<InventoryClickEvent> action) {
this.action = action;
}
/**
* @param event The event that triggered it
*/
@SuppressWarnings("WeakerAccess")
public void onClick(InventoryClickEvent event) {
action.accept(event);
}
// We do not want equals collisions. The default hashcode would not fulfil this contract.
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
return o instanceof Button;
}
@Override
public int hashCode() {
return Objects.hash(ID);
}
}

Datei anzeigen

@ -1,422 +0,0 @@
package tsp.headdb.inventory;
import me.clip.placeholderapi.PlaceholderAPI;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import tsp.headdb.HeadDB;
import tsp.headdb.implementation.Head;
import tsp.headdb.api.HeadAPI;
import tsp.headdb.implementation.LocalHead;
import tsp.headdb.implementation.Category;
import tsp.headdb.economy.BasicEconomyProvider;
import tsp.headdb.api.event.PlayerHeadPurchaseEvent;
import tsp.headdb.util.Localization;
import tsp.headdb.util.Utils;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
* Class for handling the "dirty" work
* such as inventories and economy.
*/
// TODO: Rewrite
public class InventoryUtils {
private InventoryUtils() {}
private static final FileConfiguration config = HeadDB.getInstance().getConfig();
private static final Localization localization = HeadDB.getInstance().getLocalization();
private static final Map<String, Integer> uiLocation = new HashMap<>();
private static final Map<String, ItemStack> uiItem = new HashMap<>();
public static void openLocalMenu(Player player) {
List<LocalHead> heads = HeadAPI.getLocalHeads();
PagedPane pane = new PagedPane(4, 6,
replace(localization.getMessage("menu.local"), heads.size(), "Local", "None", player));
for (LocalHead localHead : heads) {
pane.addButton(new Button(localHead.getMenuItem(), e -> {
if (e.getClick() == ClickType.SHIFT_LEFT) {
purchaseHead(player, localHead, 64, "local", localHead.getName());
return;
}
if (e.getClick() == ClickType.LEFT) {
purchaseHead(player, localHead, 1, "local", localHead.getName());
return;
}
if (e.getClick() == ClickType.RIGHT) {
Utils.sendMessage(player, localization.getMessage("localFavorites"));
}
}));
}
pane.open(player);
}
public static void openFavoritesMenu(Player player) {
List<Head> heads = config.getBoolean("hidden.hideFavorites") ? filter(HeadAPI.getFavoriteHeads(player.getUniqueId())) : HeadAPI.getFavoriteHeads(player.getUniqueId());
PagedPane pane = new PagedPane(4, 6,
replace(localization.getMessage("menu.favorites"), heads.size(), "Favorites", "None", player));
for (Head head : heads) {
pane.addButton(new Button(head.getMenuItem(), e -> {
if (e.getClick() == ClickType.SHIFT_LEFT) {
purchaseHead(player, head, 64, head.getCategory().getName(), head.getName());
}
if (e.getClick() == ClickType.LEFT) {
purchaseHead(player, head, 1, head.getCategory().getName(), head.getName());
}
if (e.getClick() == ClickType.RIGHT) {
HeadAPI.removeFavoriteHead(player.getUniqueId(), head.getValue());
openFavoritesMenu(player);
Utils.sendMessage(player, "&7Removed &e" + head.getName() + " &7from favorites.");
Utils.playSound(player, "removeFavorite");
}
}));
}
pane.open(player);
}
public static PagedPane openSearchDatabase(Player player, String search) {
List<Head> heads = filter(HeadAPI.getHeadsByName(search));
PagedPane pane = new PagedPane(4, 6,
replace(localization.getMessage("menu.search"), heads.size(), "None", search, player));
for (Head head : heads) {
pane.addButton(new Button(head.getMenuItem(), e -> {
if (e.getClick() == ClickType.SHIFT_LEFT) {
purchaseHead(player, head, 64, head.getCategory().getName(), head.getName());
}
if (e.getClick() == ClickType.LEFT) {
purchaseHead(player, head, 1, head.getCategory().getName(), head.getName());
}
if (e.getClick() == ClickType.RIGHT) {
if (!player.hasPermission("headdb.favorites")) {
Utils.sendMessage(player, localization.getMessage("noPermission"));
Utils.playSound(player, "noPermission");
return;
}
HeadAPI.addFavoriteHead(player.getUniqueId(), head.getValue());
Utils.sendMessage(player, "&7Added &e" + head.getName() + " &7to favorites.");
Utils.playSound(player, "addFavorite");
}
}));
}
pane.open(player);
return pane;
}
public static void openTagSearchDatabase(Player player, String tag) {
List<Head> heads = filter(HeadAPI.getHeadsByTag(tag));
PagedPane pane = new PagedPane(4, 6,
replace(localization.getMessage("menu.tagSearch"), heads.size(), "None", tag, player));
for (Head head : heads) {
pane.addButton(new Button(head.getMenuItem(), e -> {
if (e.getClick() == ClickType.SHIFT_LEFT) {
purchaseHead(player, head, 64, head.getCategory().getName(), head.getName());
}
if (e.getClick() == ClickType.LEFT) {
purchaseHead(player, head, 1, head.getCategory().getName(), head.getName());
}
if (e.getClick() == ClickType.RIGHT) {
if (!player.hasPermission("headdb.favorites")) {
Utils.sendMessage(player, localization.getMessage("noPermission"));
Utils.playSound(player, "noPermission");
return;
}
HeadAPI.addFavoriteHead(player.getUniqueId(), head.getValue());
Utils.sendMessage(player, "&7Added &e" + head.getName() + " &7to favorites.");
Utils.playSound(player, "addFavorite");
}
}));
}
pane.open(player);
}
public static void openCategoryDatabase(Player player, Category category) {
List<Head> heads = filter(HeadAPI.getHeads(category));
PagedPane pane = new PagedPane(4, 6,
replace(localization.getMessage("menu.category"), heads.size(), category.getName(), "None", player));
for (Head head : heads) {
pane.addButton(new Button(head.getMenuItem(), e -> {
if (e.getClick() == ClickType.SHIFT_LEFT) {
purchaseHead(player, head, 64, head.getCategory().getName(), head.getName());
}
if (e.getClick() == ClickType.LEFT) {
purchaseHead(player, head, 1, head.getCategory().getName(), head.getName());
}
if (e.getClick() == ClickType.RIGHT) {
if (!player.hasPermission("headdb.favorites")) {
Utils.sendMessage(player, localization.getMessage("noPermission"));
Utils.playSound(player, "noPermission");
return;
}
HeadAPI.addFavoriteHead(player.getUniqueId(), head.getValue());
Utils.sendMessage(player, "&7Added &e" + head.getName() + " &7to favorites.");
Utils.playSound(player, "addFavorite");
}
}));
}
pane.open(player);
}
@SuppressWarnings("ConstantConditions")
public static void openDatabase(Player player) {
Inventory inventory = Bukkit.createInventory(null, 54,
replace(localization.getMessage("menu.main"), HeadAPI.getHeads().size(), "Main", "None", player));
for (Category category : Category.getCache()) {
ItemStack item = getUIItem(category.getName(), category.getItem());
ItemMeta meta = item.getItemMeta();
meta.setDisplayName(Utils.colorize(localization.getMessage("menu.heads." + category.getName())));
List<String> lore = new ArrayList<>();
lore.add(Utils.colorize(replace(localization.getMessage("menu.lore"), HeadAPI.getHeads(category).size(), "Main", "None", player)));
meta.setLore(lore);
item.setItemMeta(meta);
inventory.setItem(getUILocation(category.getName(), category.getLocation()), item);
}
if (player.hasPermission("headdb.favorites")) {
inventory.setItem(getUILocation("favorites", 39), buildButton(
getUIItem("favorites", new ItemStack(Material.BOOK)),
"&eFavorites",
"",
"&8Click to view your favorites")
);
}
if (player.hasPermission("headdb.search")) {
inventory.setItem(getUILocation("search", 40), buildButton(
getUIItem("search", new ItemStack(Material.DARK_OAK_SIGN)),
"&9Search",
"",
"&8Click to open search menu"
));
}
if (player.hasPermission("headdb.local")) {
inventory.setItem(getUILocation("local", 41), buildButton(
getUIItem("local", new ItemStack(Material.COMPASS)),
"&aLocal",
"",
"&8Heads from any players that have logged on the server"
));
}
fill(inventory);
player.openInventory(inventory);
}
private static void fill(Inventory inv) {
ItemStack item = getUIItem("fill", new ItemStack(Material.BLACK_STAINED_GLASS_PANE));
// Do not bother filling the inventory if item to fill it with is AIR.
if (item == null || item.getType() == Material.AIR) return;
// Fill any non-empty inventory slots with the given item.
int size = inv.getSize();
for (int i = 0; i < size; i++) {
ItemStack slotItem = inv.getItem(i);
if (slotItem == null || slotItem.getType() == Material.AIR) {
inv.setItem(i, item);
}
}
}
private static int getUILocation(String category, int slot) {
// Try to use the cached value first.
if (uiLocation.containsKey(category)) return uiLocation.get(category);
// Try to get the value from the config file.
if (HeadDB.getInstance().getConfig().contains("ui.category." + category + ".location")) {
uiLocation.put(category, HeadDB.getInstance().getConfig().getInt("ui.category." + category + ".location"));
return uiLocation.get(category);
}
// No valid value in the config file, return the given default.
uiLocation.put(category, slot);
return slot;
}
@SuppressWarnings("ConstantConditions")
private static ItemStack getUIItem(String category, ItemStack item) {
// Try to use the cached item first.
if (uiItem.containsKey(category)) return uiItem.get(category);
// Try to get a head from the config file.
if (HeadDB.getInstance().getConfig().contains("ui.category." + category + ".head")) {
int id = HeadDB.getInstance().getConfig().getInt("ui.category." + category + ".head");
Head head = HeadAPI.getHeadByID(id);
if (head != null) {
uiItem.put(category, head.getMenuItem());
return uiItem.get(category);
}
}
// Try to get an item from the config file.
if (HeadDB.getInstance().getConfig().contains("ui.category." + category + ".item")) {
String cfg = HeadDB.getInstance().getConfig().getString("ui.category." + category + ".item");
Material mat = Material.matchMaterial(cfg);
// AIR is allowed as the fill material for the menu, but not as a category item.
if (mat != null && (category.equals("fill") || mat != Material.AIR)) {
uiItem.put(category, new ItemStack(mat));
return uiItem.get(category);
}
}
// No valid head or item in the config file, return the given default.
uiItem.put(category, item);
return item;
}
@SuppressWarnings("ConstantConditions")
private static ItemStack buildButton(ItemStack item, String name, String... lore) {
ItemMeta meta = item.getItemMeta();
meta.setDisplayName(Utils.colorize(name));
List<String> list = meta.getLore();
if (list == null) {
list = new ArrayList<>();
}
for (String line : lore) {
list.add(Utils.colorize(line));
}
item.setItemMeta(meta);
return item;
}
public static double getCategoryCost(Player player, String category) {
// If the player has the permission headdb.economy.free or headdb.economy.CATEGORY.free, the item is free.
if (player.hasPermission("headdb.economy.free") || player.hasPermission("headdb.economy." + category + ".free")) return 0;
// Otherwise, get the price for this category from the config file.
return HeadDB.getInstance().getConfig().getDouble("economy.cost." + category);
}
public static void processPayment(Player player, int amount, String category, String description, Consumer<Boolean> result) {
Utils.async(task -> {
BasicEconomyProvider economyProvider = HeadDB.getInstance().getEconomyProvider();
// If economy is disabled or no plugin is present, the item is free.
// Don't mention receiving it for free in this case, since it is always free.
if (economyProvider == null) {
Utils.sendMessage(player, String.format(localization.getMessage("noEconomy"), amount, description));
Utils.playSound(player, "noEconomy");
result.accept(true);
return;
}
BigDecimal cost = BigDecimal.valueOf(getCategoryCost(player, category) * amount);
// If the cost is higher than zero, attempt to charge for it.
if (cost.compareTo(BigDecimal.ZERO) > 0) {
economyProvider.canPurchase(player, cost, paymentResult -> {
if (Boolean.TRUE.equals(paymentResult)) {
economyProvider.charge(player, cost, chargeResult -> {
if (Boolean.TRUE.equals(chargeResult)) {
Utils.sendMessage(player, String.format(localization.getMessage("purchasedHead"), amount, description, cost));
Utils.playSound(player, "paid");
result.accept(true);
}
});
} else {
Utils.sendMessage(player, String.format(localization.getMessage("notEnoughMoney"), amount, description));
Utils.playSound(player, "unavailable");
result.accept(false);
}
});
return;
}
// Otherwise, the item is free.
Utils.sendMessage(player, String.format(localization.getMessage("free"), amount, description));
Utils.playSound(player, "free");
result.accept(true);
});
}
public static void purchaseHead(Player player, Head head, int amount, String category, String description) {
if (config.getBoolean("closeOnPurchase", false)) {
player.closeInventory();
}
Utils.sendMessage(player, String.format(localization.getMessage("processPayment"), amount, head.getName()));
processPayment(player, amount, category, description, result -> {
if (Boolean.TRUE.equals(result)) {
PlayerHeadPurchaseEvent event = new PlayerHeadPurchaseEvent(player, head, getCategoryCost(player, category));
Bukkit.getPluginManager().callEvent(event);
if (!event.isCancelled()) {
ItemStack item = head.getMenuItem();
item.setAmount(amount);
player.getInventory().addItem(item);
// Commands can only be ran on main thread
Bukkit.getScheduler().runTask(HeadDB.getInstance(), () -> runPurchaseCommands(player));
}
}
});
}
private static void runPurchaseCommands(Player player) {
for (String cmd : config.getStringList("commands.purchase")) {
if (cmd.isEmpty()) continue;
if (Bukkit.getPluginManager().isPluginEnabled("PlaceholderAPI")) PlaceholderAPI.setPlaceholders(player, cmd);
Bukkit.dispatchCommand(Bukkit.getConsoleSender(), cmd.replace("%player%", player.getName()));
}
}
private static String replace(String message, int size, String category, String search, Player player) {
return message
.replace("%size%", String.valueOf(size))
.replace("%category%", category)
.replace("%search%", search)
.replace("%player%", player.getName());
}
private static List<Head> filter(List<Head> source) {
if (!config.getBoolean("hidden.enabled")) {
return source;
}
List<Head> result = new ArrayList<>();
List<String> tags = config.getStringList("hidden.tags");
List<String> names = config.getStringList("hidden.names");
for (Head head : source) {
if (!names.contains(head.getName()) && !contains(head.getTags(), tags)) {
result.add(head);
}
}
return result;
}
private static boolean contains(List<String> list1, List<String> list2) {
return list1.stream().anyMatch(list2.stream().collect(Collectors.toSet())::contains);
}
}

Datei anzeigen

@ -1,386 +0,0 @@
package tsp.headdb.inventory;
import net.wesjd.anvilgui.AnvilGUI;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import tsp.headdb.HeadDB;
import tsp.headdb.api.HeadAPI;
import tsp.headdb.util.Utils;
import java.util.*;
import java.util.Map.Entry;
/**
* A paged pane. Credits @ I Al Ianstaan
*/
public class PagedPane implements InventoryHolder {
private Inventory inventory;
private SortedMap<Integer, Page> pages = new TreeMap<>();
private int currentIndex;
private int pageSize;
@SuppressWarnings("WeakerAccess")
protected Button controlBack;
@SuppressWarnings("WeakerAccess")
protected Button controlNext;
@SuppressWarnings("WeakerAccess")
protected Button controlMain;
/**
* @param pageSize The page size. inventory rows - 2
*/
public PagedPane(int pageSize, int rows, String title) {
Objects.requireNonNull(title, "title can not be null!");
if (rows > 6) {
throw new IllegalArgumentException("Rows must be <= 6, got " + rows);
}
if (pageSize > 6) {
throw new IllegalArgumentException("Page size must be <= 6, got" + pageSize);
}
this.pageSize = pageSize;
inventory = Bukkit.createInventory(this, rows * 9, color(title));
pages.put(0, new Page(pageSize));
}
/**
* @param button The button to add
*/
public void addButton(Button button) {
for (Entry<Integer, Page> entry : pages.entrySet()) {
if (entry.getValue().addButton(button)) {
if (entry.getKey() == currentIndex) {
reRender();
}
return;
}
}
Page page = new Page(pageSize);
page.addButton(button);
pages.put(pages.lastKey() + 1, page);
reRender();
}
/**
* @param button The Button to remove
*/
@SuppressWarnings("unused")
public void removeButton(Button button) {
for (Iterator<Entry<Integer, Page>> iterator = pages.entrySet().iterator(); iterator.hasNext(); ) {
Entry<Integer, Page> entry = iterator.next();
if (entry.getValue().removeButton(button)) {
// we may need to delete the page
if (entry.getValue().isEmpty()) {
// we have more than one page, so delete it
if (pages.size() > 1) {
iterator.remove();
}
// the currentIndex now points to a page that does not exist. Correct it.
if (currentIndex >= pages.size()) {
currentIndex--;
}
}
// if we modified the current one, re-render
// if we deleted the current page, re-render too
if (entry.getKey() >= currentIndex) {
reRender();
}
return;
}
}
}
/**
* @return The amount of pages
*/
@SuppressWarnings("WeakerAccess")
public int getPageAmount() {
return pages.size();
}
/**
* @return The number of the current page (1 based)
*/
@SuppressWarnings("WeakerAccess")
public int getCurrentPage() {
return currentIndex + 1;
}
/**
* @param index The index of the new page
*/
@SuppressWarnings("WeakerAccess")
public void selectPage(int index) {
if (index < 0 || index >= getPageAmount()) {
throw new IllegalArgumentException(
"Index out of bounds s: " + index + " [" + 0 + " " + getPageAmount() + ")"
);
}
if (index == currentIndex) {
return;
}
currentIndex = index;
reRender();
}
/**
* Renders the inventory again
*/
@SuppressWarnings("WeakerAccess")
public void reRender() {
inventory.clear();
pages.get(currentIndex).render(inventory);
controlBack = null;
controlNext = null;
controlMain = null;
createControls(inventory);
}
/**
* @param event The {@link InventoryClickEvent}
*/
@SuppressWarnings("WeakerAccess")
public void onClick(InventoryClickEvent event) {
event.setCancelled(true);
// back item
if (event.getSlot() == inventory.getSize() - 8) {
if (controlBack != null) {
controlBack.onClick(event);
}
return;
}
// next item
else if (event.getSlot() == inventory.getSize() - 2) {
if (controlNext != null) {
controlNext.onClick(event);
}
return;
}
else if (event.getSlot() == inventory.getSize()- 5) {
if (controlMain != null){
controlMain.onClick(event);
}
return;
}
pages.get(currentIndex).handleClick(event);
}
/**
* Get the object's inventory.
*
* @return The inventory.
*/
@Override
public Inventory getInventory() {
return inventory;
}
/**
* Creates the controls
*
* @param inventory The inventory
*/
@SuppressWarnings("WeakerAccess")
protected void createControls(Inventory inventory) {
// create separator
fillRow(
inventory.getSize() / 9 - 2,
new ItemStack(Material.BLACK_STAINED_GLASS_PANE),
inventory
);
if (getCurrentPage() > 1) {
String name = String.format(
Locale.ROOT,
"&3&lPage &a&l%d &7/ &c&l%d",
getCurrentPage() - 1, getPageAmount()
);
String lore = String.format(
Locale.ROOT,
"&7Previous: &c%d",
getCurrentPage() - 1
);
ItemStack itemStack = setMeta(HeadAPI.getHeadByValue("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvODY1MmUyYjkzNmNhODAyNmJkMjg2NTFkN2M5ZjI4MTlkMmU5MjM2OTc3MzRkMThkZmRiMTM1NTBmOGZkYWQ1ZiJ9fX0=").getMenuItem(), name, lore);
controlBack = new Button(itemStack, event -> selectPage(currentIndex - 1));
inventory.setItem(inventory.getSize() - 8, itemStack);
}
if (getCurrentPage() < getPageAmount()) {
String name = String.format(
Locale.ROOT,
"&3&lPage &a&l%d &7/ &c&l%d",
getCurrentPage() + 1, getPageAmount()
);
String lore = String.format(
Locale.ROOT,
"&7Next: &c%d",
getCurrentPage() + 1
);
ItemStack itemStack = setMeta(HeadAPI.getHeadByValue("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMmEzYjhmNjgxZGFhZDhiZjQzNmNhZThkYTNmZTgxMzFmNjJhMTYyYWI4MWFmNjM5YzNlMDY0NGFhNmFiYWMyZiJ9fX0=").getMenuItem(), name, lore);
controlNext = new Button(itemStack, event -> selectPage(getCurrentPage()));
inventory.setItem(inventory.getSize() - 2, itemStack);
}
String name = String.format(
Locale.ROOT,
"&3&lPage &a&l%d &7/ &c&l%d",
getCurrentPage(), getPageAmount()
);
ItemStack itemStack = setMeta(HeadAPI.getHeadByValue("eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvY2Q5MWY1MTI2NmVkZGM2MjA3ZjEyYWU4ZDdhNDljNWRiMDQxNWFkYTA0ZGFiOTJiYjc2ODZhZmRiMTdmNGQ0ZSJ9fX0=").getMenuItem(),
name,
"&7Left-Click to go to the &cMain Menu",
"&7Right-Click to go to a &6Specific Page");
controlMain = new Button(itemStack, event -> {
if (event.getClick() == ClickType.RIGHT) {
new AnvilGUI.Builder()
.onComplete((player, text) -> {
try {
int i = Integer.parseInt(text);
if (i > getPageAmount()) {
Utils.sendMessage(player, "&cPage number is out of bounds! Max: &e" + getPageAmount());
return AnvilGUI.Response.text("&cOut of bounds!");
}
selectPage(i - 1);
return AnvilGUI.Response.openInventory(this.getInventory());
} catch (NumberFormatException nfe) {
Utils.sendMessage(player, "&cValue must be a number!");
return AnvilGUI.Response.text(Utils.colorize("&cValue must be a number!"));
}
})
.title("Select Page")
.text("Page number...")
.plugin(HeadDB.getInstance())
.open((Player) event.getWhoClicked());
} else {
InventoryUtils.openDatabase((Player) event.getWhoClicked());
}
});
inventory.setItem(inventory.getSize() - 5, itemStack);
}
private void fillRow(int rowIndex, ItemStack itemStack, Inventory inventory) {
int yMod = rowIndex * 9;
for (int i = 0; i < 9; i++) {
int slot = yMod + i;
inventory.setItem(slot, setMeta(itemStack, ""));
}
}
protected ItemStack setMeta(ItemStack itemStack, String name, String... lore) {
ItemMeta meta = itemStack.getItemMeta();
meta.setDisplayName(Utils.colorize(name));
meta.setLore(Arrays.stream(lore).map(this::color).toList());
itemStack.setItemMeta(meta);
return itemStack;
}
@SuppressWarnings("WeakerAccess")
protected String color(String input) {
return ChatColor.translateAlternateColorCodes('&', input);
}
/**
* @param player The {@link Player} to open it for
*/
public void open(Player player) {
reRender();
player.openInventory(getInventory());
}
private static class Page {
private List<Button> buttons = new ArrayList<>();
private int maxSize;
Page(int maxSize) {
this.maxSize = maxSize;
}
/**
* @param event The click event
*/
void handleClick(InventoryClickEvent event) {
// user clicked in his own inventory. Silently drop it
if (event.getRawSlot() > event.getInventory().getSize()) {
return;
}
// user clicked outside of the inventory
if (event.getSlotType() == InventoryType.SlotType.OUTSIDE) {
return;
}
if (event.getSlot() >= buttons.size()) {
return;
}
Button button = buttons.get(event.getSlot());
button.onClick(event);
}
/**
* @return True if there is space left
*/
boolean hasSpace() {
return buttons.size() < maxSize * 9;
}
/**
* @param button The {@link Button} to add
*
* @return True if the button was added, false if there was no space
*/
boolean addButton(Button button) {
if (!hasSpace()) {
return false;
}
buttons.add(button);
return true;
}
/**
* @param button The {@link Button} to remove
*
* @return True if the button was removed
*/
boolean removeButton(Button button) {
return buttons.remove(button);
}
/**
* @param inventory The inventory to render in
*/
void render(Inventory inventory) {
for (int i = 0; i < buttons.size(); i++) {
Button button = buttons.get(i);
inventory.setItem(i, button.getItemStack());
}
}
/**
* @return True if this page is empty
*/
boolean isEmpty() {
return buttons.isEmpty();
}
}
}

Datei anzeigen

@ -1,28 +0,0 @@
package tsp.headdb.listener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import tsp.headdb.HeadDB;
import tsp.headdb.storage.PlayerDataFile;
/**
* This saves heads from players that join
* Used for local heads option
*/
public class JoinListener implements Listener {
public JoinListener(HeadDB plugin) {
// If local heads are disabled, there is no need for this listener.
if (HeadDB.getInstance().getConfig().getBoolean("localHeads")) {
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
}
@EventHandler
public void onJoin(PlayerJoinEvent e) {
HeadDB.getInstance().getPlayerData().modifyUsername(e.getPlayer().getUniqueId(), e.getPlayer().getName(), PlayerDataFile.ModificationType.SET);
HeadDB.getInstance().getPlayerData().save();
}
}

Datei anzeigen

@ -1,104 +0,0 @@
package tsp.headdb.listener;
import net.wesjd.anvilgui.AnvilGUI;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
import org.bukkit.plugin.java.JavaPlugin;
import tsp.headdb.HeadDB;
import tsp.headdb.api.HeadAPI;
import tsp.headdb.implementation.Category;
import tsp.headdb.inventory.InventoryUtils;
import tsp.headdb.util.Utils;
/**
* This handles all clicks on the main inventory
*/
public class MenuListener implements Listener {
public MenuListener(JavaPlugin plugin) {
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
@EventHandler
public void click(InventoryClickEvent e) {
if (!(e.getWhoClicked() instanceof Player)) {
return;
}
Player player = (Player) e.getWhoClicked();
if (e.getView().getTitle().startsWith(Utils.colorize("&c&lHeadDB"))) {
e.setCancelled(true);
Inventory inventory = e.getClickedInventory();
if (inventory != null) {
int slot = e.getSlot();
ItemStack item = inventory.getItem(slot);
if (item != null && item.getType() != Material.AIR) {
String name = ChatColor.stripColor(item.getItemMeta().getDisplayName().toLowerCase());
if (name.equalsIgnoreCase("favorites")) {
if (!player.hasPermission("headdb.favorites")) {
player.closeInventory();
Utils.sendMessage(player, "&cYou do not have permission for favorites!");
Utils.playSound(player, "noPermission");
return;
}
InventoryUtils.openFavoritesMenu(player);
Utils.playSound(player, "open");
return;
}
if (name.equalsIgnoreCase("local")) {
if (!player.hasPermission("headdb.local")) {
player.closeInventory();
Utils.sendMessage(player, "&cYou do not have permission to view local heads!");
Utils.playSound(player, "noPermission");
return;
}
InventoryUtils.openLocalMenu(player);
Utils.playSound(player, "open");
return;
}
if (name.equalsIgnoreCase("search")) {
if (!player.hasPermission("headdb.search")) {
player.closeInventory();
Utils.sendMessage(player, "&cYou do not have permission for the search function!");
Utils.playSound(player, "noPermission");
return;
}
new AnvilGUI.Builder()
.onComplete((p, text) -> {
InventoryUtils.openSearchDatabase(p, text);
return AnvilGUI.Response.openInventory(InventoryUtils.openSearchDatabase(p, text).getInventory());
})
.title("Search Heads")
.text("Name...")
.plugin(HeadDB.getInstance())
.open(player);
Utils.playSound(player, "open");
return;
}
Category category = Category.getByName(name);
if (category != null) {
if (HeadDB.getInstance().getConfig().getBoolean("requireCategoryPermission") && !player.hasPermission("headdb.category." + category)) {
Utils.sendMessage(player, "&cYou do not have permission for this category! (&e" + category.getName() + "&c)");
Utils.playSound(player, "noPermission");
return;
}
Utils.playSound(player, "open");
HeadAPI.openCategoryDatabase(player, category);
}
}
}
}
}
}

Datei anzeigen

@ -1,27 +0,0 @@
package tsp.headdb.listener;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.plugin.java.JavaPlugin;
import tsp.headdb.inventory.PagedPane;
/**
* Listens for click events for the {@link PagedPane}
*/
public class PagedPaneListener implements Listener {
public PagedPaneListener(JavaPlugin plugin) {
plugin.getServer().getPluginManager().registerEvents(this, plugin);
}
@EventHandler
public void onClick(InventoryClickEvent event) {
InventoryHolder holder = event.getInventory().getHolder();
if (holder instanceof PagedPane pane) {
pane.onClick(event);
}
}
}

Datei anzeigen

@ -1,149 +0,0 @@
package tsp.headdb.storage;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.JsonPrimitive;
import tsp.headdb.HeadDB;
import tsp.headdb.util.Log;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
/**
* Manages the data file that stores information
*/
public class PlayerDataFile {
private final File file;
private JsonObject main;
public PlayerDataFile(String name) {
HeadDB plugin = HeadDB.getInstance();
// This check avoids warning in console
if (plugin.getResource(name) != null && !new File(plugin.getDataFolder(), name).exists()) {
plugin.saveResource(name, false);
}
this.file = new File(plugin.getDataFolder(), name);
}
@Nonnull
public List<String> getFavoriteHeadsByTexture(UUID uuid) {
List<String> result = new ArrayList<>();
if (main.has(uuid.toString()) && main.get(uuid.toString()).getAsJsonObject().has("favorites")) {
JsonArray favorites = main.get(uuid.toString()).getAsJsonObject().get("favorites").getAsJsonArray();
for (int i = 0; i < favorites.size(); i++) {
String str = favorites.get(i).toString();
result.add(str.substring(1, str.length() - 1));
}
}
return result;
}
public void modifyFavorite(UUID uuid, String textureValue, ModificationType modificationType) {
JsonObject userObject;
if (main.has(uuid.toString())) {
userObject = main.get(uuid.toString()).getAsJsonObject();
} else {
userObject = new JsonObject();
}
JsonArray favorites;
if (userObject.has("favorites")) {
favorites = userObject.get("favorites").getAsJsonArray();
} else {
favorites = new JsonArray();
}
JsonPrimitive value = new JsonPrimitive(textureValue);
if (modificationType == ModificationType.SET) {
if (favorites.contains(value)) {
// Head is already in the list, no need to modify it
return;
}
favorites.add(value);
} else if (modificationType == ModificationType.REMOVE) {
if (!favorites.contains(value)) {
// Head is not in the list, no need to modify it
return;
}
favorites.remove(value);
}
userObject.add("favorites", favorites);
main.add(uuid.toString(), userObject);
}
@Nullable
public String getUsername(UUID uuid) {
return main.get(uuid.toString()).getAsJsonObject().get("username").toString();
}
public Set<String> getEntries() {
return main.keySet();
}
public void modifyUsername(UUID uuid, String username, ModificationType modificationType) {
JsonObject userObject;
if (main.has(uuid.toString())) {
userObject = main.get(uuid.toString()).getAsJsonObject();
} else {
userObject = new JsonObject();
}
if (modificationType == ModificationType.SET) {
userObject.addProperty("username", username);
} else {
userObject.remove("username");
}
main.add(uuid.toString(), userObject);
}
public void load() {
try {
main = new JsonParser().parse(new FileReader(file)).getAsJsonObject();
} catch (FileNotFoundException ex) {
Log.error("Failed to load player_data.json!");
Log.error(ex);
}
}
public void save() {
if (main == null) {
Log.debug("No data to save! Skipping...");
return;
}
try (FileWriter writer = new FileWriter(file)) {
writer.write(main.toString());
Log.debug("Saved data to " + file.getName());
} catch (IOException ex) {
Log.error("Failed to save player_data.json!");
Log.error(ex);
}
}
public File getFile() {
return file;
}
public enum ModificationType {
SET,
REMOVE;
}
}

Datei anzeigen

@ -1,49 +0,0 @@
package tsp.headdb.util;
import org.apache.commons.lang.Validate;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.io.File;
/**
* Localization class for a messages file
*
* @author TheSilentPro
*/
public class Localization {
private final File file;
@Nullable
private FileConfiguration data;
public Localization(@Nonnull File file) {
Validate.notNull(file, "File can not be null.");
this.file = file;
this.data = null;
}
public String getMessage(@Nonnull String key) {
Validate.notNull(data, "File data is null.");
Validate.notNull(key, "Message key can not be null.");
return Utils.colorize(data.getString(key, "null"));
}
public void load() {
this.data = YamlConfiguration.loadConfiguration(file);
}
public File getFile() {
return file;
}
@Nullable
public FileConfiguration getData() {
return data;
}
}

Datei anzeigen

@ -1,95 +0,0 @@
package tsp.headdb.util;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import tsp.headdb.HeadDB;
import java.io.PrintWriter;
import java.io.StringWriter;
/**
* @author TheSilentPro
*/
public class Log {
private static String name = "&cHeadDB";
public static void info(String message) {
log(LogLevel.INFO, message);
}
public static void warning(String message) {
log(LogLevel.WARNING, message);
}
public static void error(String message) {
log(LogLevel.ERROR, message);
}
public static void error(Throwable ex) {
log(LogLevel.ERROR, ex);
}
public static void debug(String message) {
log(LogLevel.DEBUG, message);
}
public static void debug(Throwable ex) {
log(LogLevel.DEBUG, ex);
}
public static void log(LogLevel level, String message) {
if (level == LogLevel.DEBUG && !HeadDB.getInstance().getConfig().getBoolean("debug")) {
return;
}
Bukkit.getConsoleSender().sendMessage(Utils.colorize("&7[&9&l" + name + "&7] " + level.getColor() + "[" + level.name() + "]: " + message));
}
public static void log(LogLevel level, Throwable ex) {
if (level == LogLevel.DEBUG && !HeadDB.getInstance().getConfig().getBoolean("debug")) {
return;
}
Bukkit.getConsoleSender().sendMessage(Utils.colorize("&7[" + name + "&7] " + level.getColor() + "[" + level.name() + "]: " + "&4&l[EXCEPTION]: " + ex.getMessage()));
Bukkit.getConsoleSender().sendMessage(Utils.colorize("&4&l[StackTrace]: " + getStackTrace(ex)));
}
public static void setName(String logName) {
name = logName;
}
public static String getName() {
return name;
}
public static String getStackTrace(Throwable throwable) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw, true);
throwable.printStackTrace(pw);
return sw.getBuffer().toString();
}
public enum LogLevel {
INFO,
WARNING,
ERROR,
DEBUG;
private ChatColor getColor() {
switch (this) {
case INFO:
return ChatColor.GREEN;
case WARNING:
return ChatColor.YELLOW;
case ERROR:
return ChatColor.DARK_RED;
case DEBUG:
return ChatColor.DARK_AQUA;
default:
return ChatColor.WHITE;
}
}
}
}

Datei anzeigen

@ -1,89 +0,0 @@
package tsp.headdb.util;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Sound;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitTask;
import tsp.headdb.HeadDB;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
import java.util.function.Consumer;
import java.util.regex.Pattern;
/**
* Several utilities used by the plugin
*/
public final class Utils {
private Utils() {}
private static final FileConfiguration config = HeadDB.getInstance().getConfig();
public static final Pattern UUID_PATTERN = Pattern.compile("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}");
public static void async(Consumer<BukkitTask> task) {
Bukkit.getScheduler().runTaskAsynchronously(HeadDB.getInstance(), task);
}
/**
* Retrieve the latest release from spigot
*
* @param plugin The plugin
* @param id The resource id on spigot
* @param latest Whether the plugin is on the latest version
*/
public static void isLatestVersion(JavaPlugin plugin, int id, Consumer<Boolean> latest) {
Bukkit.getScheduler().runTaskAsynchronously(plugin, checkTask -> {
try {
URLConnection connection = new URL("https://api.spigotmc.org/legacy/update.php?resource=" + id).openConnection();
connection.setConnectTimeout(5000);
connection.setRequestProperty("User-Agent", plugin.getName() + "-VersionChecker");
latest.accept(new BufferedReader(new InputStreamReader(connection.getInputStream())).readLine().equals(plugin.getDescription().getVersion()));
} catch (IOException ex) {
latest.accept(true); // Assume the version is latest if checking fails
Log.error(ex);
}
});
}
/**
* Validate a UUID (version 4)
*
* @param uuid UUID to be validated
* @return Returns true if the string is a valid UUID
*/
public static boolean validateUniqueId(String uuid) {
return UUID_PATTERN.matcher(uuid).matches();
}
public static void playSound(Player player, String key) {
// Check if sound is enabled
if (!config.getBoolean("ui.sound.enabled")) {
return;
}
player.playSound(player.getLocation(),
Sound.valueOf(config.getString("ui.sound." + key + ".name")),
(float) config.getDouble("ui.sound." + key + ".volume"),
(float) config.getDouble("ui.sound." + key + ".pitch"));
}
public static void sendMessage(CommandSender sender, String message) {
if (!message.isEmpty()) {
sender.sendMessage(colorize(message));
}
}
public static String colorize(String string) {
return ChatColor.translateAlternateColorCodes('&', string);
}
}

Datei anzeigen

@ -1,37 +1,22 @@
# 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.
# Local heads are heads from players that have joined your server at least once.
localHeads: true
# If enabled categories will require a permission to be used
# If enabled categories will require a permission to be used.
# Permission: headdb.category.<category>
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
# Hidden heads from the menu
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 Options
economy:
enable: false
# Supported: VAULT, TREASURY
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: ""
provider: "VAULT" # Supported: VAULT
format: "##.##"
cost:
alphabet: 100
animals: 100
@ -43,111 +28,69 @@ economy:
miscellaneous: 100
monsters: 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. Supports PlaceholderAPI.
commands:
# Commands to run ONLY if the purchase is successful.
# They are run as CONSOLE after the player has receiver the head in their inventory.
purchase:
- ""
# Graphical User Interface customization
gui:
main:
fill:
material: "BLACK_STAINED_GLASS_PANE"
name: ""
lore: []
# Categories are set in the slots 20-24 & 29 - 33. You can add specific ones here to relocate them.
# Note: that the slots start from 0
category:
alphabet: 20
animals: 21
blocks: 22
decoration: 23
food-drinks: 24
humans: 29
humanoid: 30
miscellaneous: 31
monsters: 32
plants: 33
meta:
favorites:
slot: 39
item:
material: "BOOK"
name: "&6Favorites"
lore:
- "&7Click to view your favorite heads."
search:
slot: 40
item:
material: "DARK_OAK_SIGN"
name: "&6Search"
lore:
- "&7Click to search for a specific head."
local:
slot: 41
item:
material: "COMPASS"
name: "&6Local Heads"
lore:
- "&7Click to view Local heads."
# If the original fetching fails and this is enabled,
# the plugin will attempt to fetch the heads from an archive.
# The archive is static so some heads may be missing, this will only be used when all else fails.
fallback: true
# Shows more plugin information. (/hdb info)
showAdvancedPluginInfo: true
# Storage Options
storage:
# Amount of threads in the executor pool used for storage.
threads: 2
# Debug Mode
debug: false

Datei anzeigen

@ -1,31 +0,0 @@
noPermission: "&cNo permission!"
onlyPlayers: "&cOnly players may open the database."
databaseOpen: "&7Opening &cHead Database"
invalidPlayer: "&cPlayer is not online!"
localFavorites: "&cLocal heads can not be added to favorites!"
noEconomy: "&7You received &e%d &7x &e%s&7!"
purchasedHead: "&7You purchased &e%dx %s &7for &e%.2f&7!"
processPayment: "&7Purchasing &e%dx %s&7. Please wait..."
notEnoughMoney: "&cYou do not have enough to purchase &e%dx %s&c."
free: "&7You received &e%dx %s &7for &efree&7!"
reloadMessages: "&aReloaded messages file!"
menu:
main: "&c&lHeadDB &7(%size%)"
category: "&c&lHeadDB &8- &e%category%"
tagSearch: "&c&lHeadDB &8- &eTag Search: %search%"
search: "&c&lHeadDB &8- &eSearch: %search%"
favorites: "&c&lHeadDB &8- &eFavorites: %player%"
local: "&c&lHeadDB &8- &aLocal Heads &7(%size%)"
lore: "&e%size% heads"
heads:
alphabet: "&e&lALPHABET"
animals: "&3&lANIMALS"
blocks: "&8&lBLOCKS"
decoration: "&6&lDECORATION"
food-drinks: "&6&lFOOD-DRINKS"
humans: "&1&lHUMANS"
humanoid: "&b&lHUMANOID"
miscellaneous: "&2&lMISCELLANEOUS"
monsters: "&c&lMONSTERS"
plants: "&a&lPLANTS"

Datei anzeigen

@ -0,0 +1,61 @@
noConsole: "&cOnly for in-game players!"
noPermission: "&cNo permission!"
invalidSubCommand: "&cInvalid sub-command! Run &e/hdb help&c for help."
giveCommandInvalid: "&cInvalid head: &e%name%"
invalidArguments: "&cInvalid Arguments! Use &e/hdb help&c for help."
invalidTarget: "&cInvalid target: &e%name%"
invalidCategory: "&cInvalid Category!"
invalidNumber: "&e%name% &cis not a number!"
invalidPageIndex: "&cThat page is out of bounds! Max: %pages%"
openDatabase: "" # Intentionally empty. Sent when the main gui is opened
updateDatabase: "&7Updating..."
searchCommand: "&7Searching for heads matching: &6%query%"
searchCommandResults: "&7Found &6%size% &7matches!"
giveCommand: "&7Gave &6x%size% %name% &7to &6%receiver%"
itemTexture: "&7Texture: &6%texture%"
itemNoTexture: "&cThis item does not have a texture!"
copyTexture: "&6Click to copy texture!"
# Only shown if economy is enabled
processPayment: "&7Purchasing &6%name% &7for &6%cost%&7! Please wait..."
completePayment: "&7Received &6%name% &7for &6%cost%"
invalidLanguage: "&cInvalid Language! Available: &e%languages%"
languageChanged: "&7Your language was set to: &6%language%"
menu:
main:
title: "&cHeadDB &7(%size%)"
category:
name: "&6&l%category%"
page:
name: "&7Go to specific page"
lore:
- "&7Left-Click to open"
- "&7Right-Click to open specific page"
favorites:
name: "&6Favorites"
search:
name: "&6Search"
local:
name: "&6Local Heads"
category:
name: "&cHeadDB &7- &6%category% &7(%size%)"
head:
name: "&6%name%"
search:
name: "&cHeadDB &7- &6Search: %query% &7(%size%)"
page:
name: "&cHeadDB &7- &6Enter page number"
settings:
name: "&cHeadDB &7- &6Settings"
language:
# Setting name
name: "&cLanguage"
# Available languages lore
available: "&7Languages Available: &6%size%"
# Title of the language selection inventory
title: "&cHeadDB &7- &6Languages &7(%size%)"
# Language item name
format: "&6%language%"

Datei anzeigen

@ -1 +0,0 @@
{}

Datei anzeigen

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