Mirror von
https://github.com/TheSilentPro/HeadDB.git
synchronisiert 2024-12-27 11:20:05 +01:00
Commit
d8f4dc23e3
2
.gitignore
vendored
2
.gitignore
vendored
@ -3,6 +3,8 @@ target/
|
||||
src/test/
|
||||
|
||||
dependency-reduced-pom.xml
|
||||
build.properties
|
||||
build/
|
||||
|
||||
.classpath
|
||||
.project
|
||||
|
2
LICENSE
2
LICENSE
@ -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
|
||||
|
@ -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
100
pom.xml
@ -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>
|
||||
|
@ -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";
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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.");
|
||||
}
|
||||
|
||||
}
|
@ -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"));
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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!");
|
||||
});
|
||||
}
|
||||
|
||||
}
|
194
src/main/java/tsp/headdb/core/api/HeadAPI.java
Normale Datei
194
src/main/java/tsp/headdb/core/api/HeadAPI.java
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
80
src/main/java/tsp/headdb/core/api/event/HeadPurchaseEvent.java
Normale Datei
80
src/main/java/tsp/headdb/core/api/event/HeadPurchaseEvent.java
Normale Datei
@ -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;
|
||||
}
|
||||
}
|
61
src/main/java/tsp/headdb/core/command/CommandCategory.java
Normale Datei
61
src/main/java/tsp/headdb/core/command/CommandCategory.java
Normale Datei
@ -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"));
|
||||
}
|
||||
|
||||
}
|
57
src/main/java/tsp/headdb/core/command/CommandGive.java
Normale Datei
57
src/main/java/tsp/headdb/core/command/CommandGive.java
Normale Datei
@ -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))
|
||||
);
|
||||
}
|
||||
|
||||
}
|
29
src/main/java/tsp/headdb/core/command/CommandHelp.java
Normale Datei
29
src/main/java/tsp/headdb/core/command/CommandHelp.java
Normale Datei
@ -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<===============================================================>");
|
||||
}
|
||||
|
||||
}
|
27
src/main/java/tsp/headdb/core/command/CommandInfo.java
Normale Datei
27
src/main/java/tsp/headdb/core/command/CommandInfo.java
Normale Datei
@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
51
src/main/java/tsp/headdb/core/command/CommandLanguage.java
Normale Datei
51
src/main/java/tsp/headdb/core/command/CommandLanguage.java
Normale Datei
@ -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();
|
||||
}
|
||||
|
||||
}
|
209
src/main/java/tsp/headdb/core/command/CommandMain.java
Normale Datei
209
src/main/java/tsp/headdb/core/command/CommandMain.java
Normale Datei
@ -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
|
||||
}
|
||||
|
||||
}
|
40
src/main/java/tsp/headdb/core/command/CommandManager.java
Normale Datei
40
src/main/java/tsp/headdb/core/command/CommandManager.java
Normale Datei
@ -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;
|
||||
}
|
||||
}
|
67
src/main/java/tsp/headdb/core/command/CommandSearch.java
Normale Datei
67
src/main/java/tsp/headdb/core/command/CommandSearch.java
Normale Datei
@ -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);
|
||||
}
|
||||
|
||||
}
|
53
src/main/java/tsp/headdb/core/command/CommandSettings.java
Normale Datei
53
src/main/java/tsp/headdb/core/command/CommandSettings.java
Normale Datei
@ -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);
|
||||
}
|
||||
|
||||
}
|
33
src/main/java/tsp/headdb/core/command/CommandTexture.java
Normale Datei
33
src/main/java/tsp/headdb/core/command/CommandTexture.java
Normale Datei
@ -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"));
|
||||
}
|
||||
|
||||
}
|
19
src/main/java/tsp/headdb/core/command/CommandUpdate.java
Normale Datei
19
src/main/java/tsp/headdb/core/command/CommandUpdate.java
Normale Datei
@ -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"));
|
||||
}
|
||||
|
||||
}
|
59
src/main/java/tsp/headdb/core/command/HeadDBCommand.java
Normale Datei
59
src/main/java/tsp/headdb/core/command/HeadDBCommand.java
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
36
src/main/java/tsp/headdb/core/command/SubCommand.java
Normale Datei
36
src/main/java/tsp/headdb/core/command/SubCommand.java
Normale Datei
@ -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);
|
||||
}
|
||||
|
||||
}
|
20
src/main/java/tsp/headdb/core/economy/BasicEconomyProvider.java
Normale Datei
20
src/main/java/tsp/headdb/core/economy/BasicEconomyProvider.java
Normale Datei
@ -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();
|
||||
|
||||
}
|
45
src/main/java/tsp/headdb/core/economy/VaultProvider.java
Normale Datei
45
src/main/java/tsp/headdb/core/economy/VaultProvider.java
Normale Datei
@ -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();
|
||||
}
|
||||
|
||||
}
|
9
src/main/java/tsp/headdb/core/hook/Hooks.java
Normale Datei
9
src/main/java/tsp/headdb/core/hook/Hooks.java
Normale Datei
@ -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);
|
||||
|
||||
}
|
3
src/main/java/tsp/headdb/core/hook/PluginHook.java
Normale Datei
3
src/main/java/tsp/headdb/core/hook/PluginHook.java
Normale Datei
@ -0,0 +1,3 @@
|
||||
package tsp.headdb.core.hook;
|
||||
|
||||
public record PluginHook(boolean enabled) {}
|
20
src/main/java/tsp/headdb/core/listener/PlayerJoinListener.java
Normale Datei
20
src/main/java/tsp/headdb/core/listener/PlayerJoinListener.java
Normale Datei
@ -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(), ""));
|
||||
}
|
||||
|
||||
}
|
20
src/main/java/tsp/headdb/core/storage/HeadDBThreadFactory.java
Normale Datei
20
src/main/java/tsp/headdb/core/storage/HeadDBThreadFactory.java
Normale Datei
@ -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());
|
||||
}
|
||||
|
||||
}
|
12
src/main/java/tsp/headdb/core/storage/PlayerData.java
Normale Datei
12
src/main/java/tsp/headdb/core/storage/PlayerData.java
Normale Datei
@ -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 {}
|
79
src/main/java/tsp/headdb/core/storage/PlayerStorage.java
Normale Datei
79
src/main/java/tsp/headdb/core/storage/PlayerStorage.java
Normale Datei
@ -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!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
33
src/main/java/tsp/headdb/core/storage/Storage.java
Normale Datei
33
src/main/java/tsp/headdb/core/storage/Storage.java
Normale Datei
@ -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();
|
||||
}
|
||||
|
||||
}
|
35
src/main/java/tsp/headdb/core/task/UpdateTask.java
Normale Datei
35
src/main/java/tsp/headdb/core/task/UpdateTask.java
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
45
src/main/java/tsp/headdb/core/util/BuildProperties.java
Normale Datei
45
src/main/java/tsp/headdb/core/util/BuildProperties.java
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
234
src/main/java/tsp/headdb/core/util/Utils.java
Normale Datei
234
src/main/java/tsp/headdb/core/util/Utils.java
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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!"));
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
85
src/main/java/tsp/headdb/implementation/category/Category.java
Normale Datei
85
src/main/java/tsp/headdb/implementation/category/Category.java
Normale Datei
@ -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
|
||||
}
|
||||
|
||||
}
|
103
src/main/java/tsp/headdb/implementation/head/Head.java
Normale Datei
103
src/main/java/tsp/headdb/implementation/head/Head.java
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
70
src/main/java/tsp/headdb/implementation/head/HeadDatabase.java
Normale Datei
70
src/main/java/tsp/headdb/implementation/head/HeadDatabase.java
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
28
src/main/java/tsp/headdb/implementation/head/LocalHead.java
Normale Datei
28
src/main/java/tsp/headdb/implementation/head/LocalHead.java
Normale Datei
@ -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();
|
||||
}
|
||||
|
||||
}
|
26
src/main/java/tsp/headdb/implementation/requester/HeadProvider.java
Normale Datei
26
src/main/java/tsp/headdb/implementation/requester/HeadProvider.java
Normale Datei
@ -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());
|
||||
}
|
||||
|
||||
}
|
96
src/main/java/tsp/headdb/implementation/requester/Requester.java
Normale Datei
96
src/main/java/tsp/headdb/implementation/requester/Requester.java
Normale Datei
@ -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;
|
||||
}
|
||||
|
||||
}
|
3
src/main/java/tsp/headdb/implementation/requester/Response.java
Normale Datei
3
src/main/java/tsp/headdb/implementation/requester/Response.java
Normale Datei
@ -0,0 +1,3 @@
|
||||
package tsp.headdb.implementation.requester;
|
||||
|
||||
public record Response(String response, int code, String date) {}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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
|
@ -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"
|
61
src/main/resources/messages/en.yml
Normale Datei
61
src/main/resources/messages/en.yml
Normale Datei
@ -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%"
|
@ -1 +0,0 @@
|
||||
{}
|
@ -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
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren