From b487444b651e057942d8185c331c48b24880b8c5 Mon Sep 17 00:00:00 2001 From: stijnb1234 Date: Sat, 18 Apr 2020 09:01:35 +0200 Subject: [PATCH] First commit! --- .gitignore | 2 + pom.xml | 145 ++ src/lib/themepark-1.3.2.jar | Bin 0 -> 69906 bytes .../themeparkplus/ThemeParkPlus.java | 232 ++ .../themeparkplus/api/PlusAPI.java | 31 + .../api/enums/WalkingDirection.java | 23 + .../themeparkplus/api/objects/Gate.java | 68 + .../api/objects/MalfunctionReport.java | 22 + .../themeparkplus/api/objects/WaitingRow.java | 33 + .../themeparkplus/commands/TPPCMD.java | 340 +++ .../listeners/AntiFreerunListener.java | 18 + .../listeners/DirectionalGateListener.java | 70 + .../listeners/FastpassListeners.java | 149 ++ .../listeners/StatusChangeListener.java | 41 + .../themeparkplus/managers/DBManager.java | 81 + .../themeparkplus/util/ConfigUtil.java | 11 + .../themeparkplus/util/Cuboid.java | 177 ++ .../themeparkplus/util/DirectionUtil.java | 39 + .../themeparkplus/util/LGUtil.java | 298 +++ .../themeparkplus/util/License.java | 264 +++ .../themeparkplus/util/XMaterial.java | 2030 +++++++++++++++++ src/main/resources/config.yml | 33 + src/main/resources/messages.yml | 45 + src/main/resources/plugin.yml | 13 + 24 files changed, 4165 insertions(+) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 src/lib/themepark-1.3.2.jar create mode 100644 src/main/java/nl/sbdeveloper/themeparkplus/ThemeParkPlus.java create mode 100644 src/main/java/nl/sbdeveloper/themeparkplus/api/PlusAPI.java create mode 100644 src/main/java/nl/sbdeveloper/themeparkplus/api/enums/WalkingDirection.java create mode 100644 src/main/java/nl/sbdeveloper/themeparkplus/api/objects/Gate.java create mode 100644 src/main/java/nl/sbdeveloper/themeparkplus/api/objects/MalfunctionReport.java create mode 100644 src/main/java/nl/sbdeveloper/themeparkplus/api/objects/WaitingRow.java create mode 100644 src/main/java/nl/sbdeveloper/themeparkplus/commands/TPPCMD.java create mode 100644 src/main/java/nl/sbdeveloper/themeparkplus/listeners/AntiFreerunListener.java create mode 100644 src/main/java/nl/sbdeveloper/themeparkplus/listeners/DirectionalGateListener.java create mode 100644 src/main/java/nl/sbdeveloper/themeparkplus/listeners/FastpassListeners.java create mode 100644 src/main/java/nl/sbdeveloper/themeparkplus/listeners/StatusChangeListener.java create mode 100644 src/main/java/nl/sbdeveloper/themeparkplus/managers/DBManager.java create mode 100644 src/main/java/nl/sbdeveloper/themeparkplus/util/ConfigUtil.java create mode 100644 src/main/java/nl/sbdeveloper/themeparkplus/util/Cuboid.java create mode 100644 src/main/java/nl/sbdeveloper/themeparkplus/util/DirectionUtil.java create mode 100644 src/main/java/nl/sbdeveloper/themeparkplus/util/LGUtil.java create mode 100644 src/main/java/nl/sbdeveloper/themeparkplus/util/License.java create mode 100644 src/main/java/nl/sbdeveloper/themeparkplus/util/XMaterial.java create mode 100644 src/main/resources/config.yml create mode 100644 src/main/resources/messages.yml create mode 100644 src/main/resources/plugin.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..29b636a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.idea +*.iml \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..f36ccd4 --- /dev/null +++ b/pom.xml @@ -0,0 +1,145 @@ + + + 4.0.0 + + nl.SBDeveloper + ThemeParkPlus + 2.0 + jar + + ThemeParkPlus + + Plus version of ThemePark! + + 1.8 + UTF-8 + + https://sbdplugins.nl + + + clean package + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + ${java.version} + ${java.version} + + + + org.apache.maven.plugins + maven-shade-plugin + 3.1.0 + + + package + + shade + + + false + + + de.tr7zw.changeme.nbtapi + nl.sbdeveloper.themeparkplus.nbtapi + + + + + + + + + + src/main/resources + true + + + + + + + spigot-repo + https://hub.spigotmc.org/nexus/content/repositories/snapshots/ + + + iobyte-repo + https://nexus.iobyte.nl/repository/maven-public/ + + + codemc-repo + https://repo.codemc.org/repository/maven-public/ + + + jitpack.io + https://jitpack.io + + + CodeMC + https://repo.codemc.org/repository/maven-public + + + sk89q-repo + >http://maven.sk89q.com/repo/ + + + jcenter + bintray-jcenter + https://jcenter.bintray.com + + + + + + org.spigotmc + spigot-api + 1.15.2-R0.1-SNAPSHOT + provided + + + nl.SBDevelopment + SBUtilities + 1.4 + compile + + + me.paradoxpixel + themepark + 1.3.2 + system + ${pom.basedir}/src/lib/themepark-1.3.2.jar + + + com.sk89q.worldedit + worldedit-bukkit + 6.1.4-SNAPSHOT + provided + + + org.projectlombok + lombok + 1.18.12 + provided + + + de.tr7zw + item-nbt-api + 2.3.1 + + + com.github.MilkBowl + VaultAPI + 1.7 + provided + + + club.minnced + discord-webhooks + 0.3.0 + + + diff --git a/src/lib/themepark-1.3.2.jar b/src/lib/themepark-1.3.2.jar new file mode 100644 index 0000000000000000000000000000000000000000..652c353f400933ff03a158141e7e4f4a98d30bcf GIT binary patch literal 69906 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 W5?Cc(?%c(|@NmGu<^d=$u>S)Kv_G-{ literal 0 HcmV?d00001 diff --git a/src/main/java/nl/sbdeveloper/themeparkplus/ThemeParkPlus.java b/src/main/java/nl/sbdeveloper/themeparkplus/ThemeParkPlus.java new file mode 100644 index 0000000..977844a --- /dev/null +++ b/src/main/java/nl/sbdeveloper/themeparkplus/ThemeParkPlus.java @@ -0,0 +1,232 @@ +package nl.sbdeveloper.themeparkplus; + +import club.minnced.discord.webhook.WebhookClient; +import club.minnced.discord.webhook.WebhookClientBuilder; +import net.milkbowl.vault.economy.Economy; +import nl.SBDevelopment.SBUtilities.Data.YamlFile; +import nl.SBDevelopment.SBUtilities.Logger.Logger; +import nl.SBDevelopment.SBUtilities.PrivateManagers.UpdateManager; +import nl.SBDevelopment.SBUtilities.SBUtilities; +import nl.sbdeveloper.themeparkplus.commands.TPPCMD; +import nl.sbdeveloper.themeparkplus.listeners.AntiFreerunListener; +import nl.sbdeveloper.themeparkplus.listeners.DirectionalGateListener; +import nl.sbdeveloper.themeparkplus.listeners.FastpassListeners; +import nl.sbdeveloper.themeparkplus.listeners.StatusChangeListener; +import nl.sbdeveloper.themeparkplus.managers.DBManager; +import nl.sbdeveloper.themeparkplus.util.LGUtil; +import nl.sbdeveloper.themeparkplus.util.License; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.RegisteredServiceProvider; +import org.bukkit.plugin.java.JavaPlugin; +import org.jetbrains.annotations.NotNull; + +import java.io.InputStreamReader; +import java.io.Reader; +import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Objects; + +public final class ThemeParkPlus extends JavaPlugin { + + private static ThemeParkPlus instance; + private static YamlFile config; + private static DBManager data; + private static YamlFile messages; + + private static Economy econ = null; + private static WebhookClient webhookClient; + + private int configVersion = 1; + + @Override + public void onEnable() { + instance = this; + + new SBUtilities(this, "ThemeParkPlus"); + + Logger.logInfo("-------------------------------", true); + Logger.logInfo("ThemeParkPlus v" + this.getDescription().getVersion(), true); + Logger.logInfo("Made by SBDeveloper", true); + + Logger.logInfo(" ", true); + + Logger.logInfo("Loading Files...", true); + + config = new YamlFile("config"); + config.loadDefaults(); + + data = new DBManager("data"); + + messages = new YamlFile("messages"); + messages.loadDefaults(); + + Reader defConfigStream = new InputStreamReader(Objects.requireNonNull(getResource("config.yml")), StandardCharsets.UTF_8); + YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(defConfigStream); + config.getFile().setDefaults(defConfig); + config.getFile().options().copyDefaults(true); + config.saveFile(); + + if (!config.getFile().contains("Version") || Objects.equals(config.getFile().getString("Version"), "")) { + config.getFile().set("Version", configVersion); + config.saveFile(); + } + + if (!Objects.equals(config.getFile().getString("Version"), String.valueOf(configVersion))) { + Logger.logInfo("Updating outdated config...", true); + updateConfig(); + } + + Logger.logInfo("Checking license...", true); + + if (config.getFile().contains("License")) { + Logger.logInfo("Licence code: " + config.getFile().getString("License"), true); + } else { + Logger.logError("Licence code unknown! Please change the config.yml!", true); + return; + } + + new License(this, "TP", config.getFile().getString("License")); + + new UpdateManager(this, 6, UpdateManager.CheckType.SBDPLUGINS).handleResponse((versionResponse, version) -> { + if (versionResponse == UpdateManager.VersionResponse.FOUND_NEW) { + Logger.logWarning("There is a new version available! Curent: " + this.getDescription().getVersion() + " New: " + version, true); + } else if (versionResponse == UpdateManager.VersionResponse.LATEST) { + Logger.logInfo("You are running the latest version [" + this.getDescription().getVersion() + "]!", true); + } else if (versionResponse == UpdateManager.VersionResponse.UNAVAILABLE) { + Logger.logError("Unable to perform an update check.", true); + } + }).check(); + + if (Bukkit.getPluginManager().getPlugin("ThemePark") == null) { + Logger.logError("Missing ThemePark! Please install it first.", true); + getServer().getPluginManager().disablePlugin(this); + return; + } + + if (Bukkit.getPluginManager().getPlugin("WorldEdit") == null) { + Logger.logError("Missing WorldEdit! Please install it first.", true); + getServer().getPluginManager().disablePlugin(this); + return; + } + + if (!setupEconomy()) { + Logger.logError("Missing Vault! Please install it first.", true); + getServer().getPluginManager().disablePlugin(this); + return; + } + + Logger.logInfo("Loading commands...", true); + Objects.requireNonNull(getCommand("themeparkplus"), "Couldn't read command from plugin.yml!").setExecutor(new TPPCMD()); + + Logger.logInfo("Loading listeners...", true); + Bukkit.getPluginManager().registerEvents(new DirectionalGateListener(), this); + Bukkit.getPluginManager().registerEvents(new FastpassListeners(), this); + if (getSConfig().getFile().getBoolean("AntiFreerun.Enabled")) { + Bukkit.getPluginManager().registerEvents(new AntiFreerunListener(), this); + } + + if (getSConfig().getFile().getBoolean("DiscordWebhook.Enabled")) { + String URL = getSConfig().getFile().getString("DiscordWebhook.WebhookURL"); + if (URL != null) { + Bukkit.getPluginManager().registerEvents(new StatusChangeListener(), this); + + Logger.logInfo("Loading Discord webhook...", true); + WebhookClientBuilder builder = new WebhookClientBuilder(URL); + builder.setThreadFactory((job) -> { + Thread thread = new Thread(job); + thread.setName("Hello"); + thread.setDaemon(true); + return thread; + }); + builder.setWait(true); + webhookClient = builder.build(); + } else { + Logger.logError("Couldn't load the webhook builder! The URL is null.", true); + } + } + + Logger.logInfo("Loading Lamp & Gate utils...", true); + try { + new LGUtil(); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + Logger.logError("Couldn't find classes for Lamp & Gate util. The plugin won't work as intended.", true); + } + + Logger.logInfo("Plugin enabled!", true); + Logger.logInfo("-------------------------------", true); + } + + @Override + public void onDisable() { + if (getSConfig().getFile().getBoolean("DiscordWebhook.Enabled")) { + webhookClient.close(); + } + Logger.logInfo("Plugin disabled!", true); + instance = null; + } + + public static ThemeParkPlus getInstance() { + return instance; + } + + public static YamlFile getSConfig() { + return config; + } + + public static DBManager getData() { + return data; + } + + public static YamlFile getMessages() { + return messages; + } + + public static Economy getEconomy() { + return econ; + } + + public static WebhookClient getWebhookClient() { + return webhookClient; + } + + private boolean setupEconomy() { + if (getServer().getPluginManager().getPlugin("Vault") == null) { + return false; + } + RegisteredServiceProvider rsp = getServer().getServicesManager().getRegistration(Economy.class); + if (rsp == null) { + return false; + } + econ = rsp.getProvider(); + return econ != null; + } + + private void updateConfig() { + HashMap< String, Object > newConfig = getConfigVals(); + for (String var: config.getFile().getKeys(false)) { + newConfig.remove(var); + } + if (newConfig.size() != 0) { + for (String key: newConfig.keySet()) { + config.getFile().set(key, newConfig.get(key)); + } + config.getFile().set("Version", configVersion); + config.saveFile(); + } + } + + @NotNull + private HashMap getConfigVals() { + HashMap var = new HashMap <>(); + Reader defConfigStream = new InputStreamReader(Objects.requireNonNull(getResource("config.yml")), StandardCharsets.UTF_8); + YamlConfiguration defConfig = YamlConfiguration.loadConfiguration(defConfigStream); + for (String key : defConfig.getKeys(false)) { + var.put(key, defConfig.get(key)); + } + return var; + } +} diff --git a/src/main/java/nl/sbdeveloper/themeparkplus/api/PlusAPI.java b/src/main/java/nl/sbdeveloper/themeparkplus/api/PlusAPI.java new file mode 100644 index 0000000..1f1b74d --- /dev/null +++ b/src/main/java/nl/sbdeveloper/themeparkplus/api/PlusAPI.java @@ -0,0 +1,31 @@ +package nl.sbdeveloper.themeparkplus.api; + +import nl.sbdeveloper.themeparkplus.api.objects.Gate; +import org.bukkit.Location; +import org.jetbrains.annotations.NotNull; + +import java.util.HashMap; + +public class PlusAPI { + private static HashMap gates = new HashMap<>(); + + public static void addGate(Gate gate) { + gates.put(gate.getLoc(), gate); + } + + public static void removeGate(@NotNull Gate gate) { + gates.remove(gate.getLoc()); + } + + public static boolean isGate(Location loc) { + return gates.containsKey(loc); + } + + public static Gate getGate(Location loc) { + return gates.get(loc); + } + + public static HashMap getGates() { + return gates; + } +} diff --git a/src/main/java/nl/sbdeveloper/themeparkplus/api/enums/WalkingDirection.java b/src/main/java/nl/sbdeveloper/themeparkplus/api/enums/WalkingDirection.java new file mode 100644 index 0000000..d6a9726 --- /dev/null +++ b/src/main/java/nl/sbdeveloper/themeparkplus/api/enums/WalkingDirection.java @@ -0,0 +1,23 @@ +package nl.sbdeveloper.themeparkplus.api.enums; + +import org.bukkit.block.BlockFace; + +public enum WalkingDirection { + /** + * The walking direction. Removed combinations because those can't be used in the commands. + */ + NORTH(BlockFace.NORTH), + EAST(BlockFace.EAST), + SOUTH(BlockFace.SOUTH), + WEST(BlockFace.WEST); + + private BlockFace blockFace; + + WalkingDirection(BlockFace blockFace) { + this.blockFace = blockFace; + } + + public BlockFace getBlockFace() { + return blockFace; + } +} diff --git a/src/main/java/nl/sbdeveloper/themeparkplus/api/objects/Gate.java b/src/main/java/nl/sbdeveloper/themeparkplus/api/objects/Gate.java new file mode 100644 index 0000000..1fe9d5c --- /dev/null +++ b/src/main/java/nl/sbdeveloper/themeparkplus/api/objects/Gate.java @@ -0,0 +1,68 @@ +package nl.sbdeveloper.themeparkplus.api.objects; + +import lombok.Getter; +import lombok.Setter; +import nl.sbdeveloper.themeparkplus.api.enums.WalkingDirection; +import org.bukkit.Location; + +@Getter @Setter +public class Gate { + private final Location loc; + + /* If max count */ + private boolean hasMaxCount = false; + private int passedCount = 0; + private int maxCount; + + /* If directional */ + private boolean isDirectional = false; + private WalkingDirection direction; + + /** + * Constructor for a normal gate. + * + * @param loc The gate location + */ + public Gate(Location loc) { + this.loc = loc; + } + + /** + * Constructor for a directional gate. + * + * @param loc The gate location + * @param direction The walking direction + */ + public Gate(Location loc, WalkingDirection direction) { + this.loc = loc; + this.isDirectional = true; + this.direction = direction; + } + + /** + * Constructor for a max count gate. + * + * @param loc The gate location + * @param maxCount The max player count + */ + public Gate(Location loc, int maxCount) { + this.loc = loc; + this.hasMaxCount = true; + this.maxCount = maxCount; + } + + /** + * Constructor for a directional max count gate. + * + * @param loc The gate location + * @param maxCount The max player count + * @param direction The walking direction + */ + public Gate(Location loc, int maxCount, WalkingDirection direction) { + this.loc = loc; + this.hasMaxCount = true; + this.isDirectional = true; + this.maxCount = maxCount; + this.direction = direction; + } +} diff --git a/src/main/java/nl/sbdeveloper/themeparkplus/api/objects/MalfunctionReport.java b/src/main/java/nl/sbdeveloper/themeparkplus/api/objects/MalfunctionReport.java new file mode 100644 index 0000000..53d731e --- /dev/null +++ b/src/main/java/nl/sbdeveloper/themeparkplus/api/objects/MalfunctionReport.java @@ -0,0 +1,22 @@ +package nl.sbdeveloper.themeparkplus.api.objects; + +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; +import java.util.UUID; + +@Getter @Setter +public class MalfunctionReport { + private String rideID; + private UUID reporterUUID; + private LocalDateTime reportDate; + private String reason; + + public MalfunctionReport(String rideID, UUID reporterUUID, LocalDateTime reportDate, String reason) { + this.rideID = rideID; + this.reporterUUID = reporterUUID; + this.reportDate = reportDate; + this.reason = reason; + } +} diff --git a/src/main/java/nl/sbdeveloper/themeparkplus/api/objects/WaitingRow.java b/src/main/java/nl/sbdeveloper/themeparkplus/api/objects/WaitingRow.java new file mode 100644 index 0000000..e5610e4 --- /dev/null +++ b/src/main/java/nl/sbdeveloper/themeparkplus/api/objects/WaitingRow.java @@ -0,0 +1,33 @@ +package nl.sbdeveloper.themeparkplus.api.objects; + +import com.sk89q.worldedit.regions.AbstractRegion; +import lombok.Getter; +import lombok.Setter; +import org.bukkit.Location; + +import java.util.ArrayList; + +public class WaitingRow { + @Getter @Setter private String rideID; + @Getter @Setter private AbstractRegion region; + @Getter @Setter private int waitingPlayers = 0; + @Getter @Setter private int waitingTimeMinutes = 0; + private ArrayList signLocations = new ArrayList<>(); + + public WaitingRow(String rideID, AbstractRegion region) { + this.rideID = rideID; + this.region = region; + } + + public ArrayList getSignLocations() { + return signLocations; + } + + public void addSignLocation(Location loc) { + this.signLocations.add(loc); + } + + public void removeSignLocation(Location loc) { + this.signLocations.remove(loc); + } +} diff --git a/src/main/java/nl/sbdeveloper/themeparkplus/commands/TPPCMD.java b/src/main/java/nl/sbdeveloper/themeparkplus/commands/TPPCMD.java new file mode 100644 index 0000000..e7c4883 --- /dev/null +++ b/src/main/java/nl/sbdeveloper/themeparkplus/commands/TPPCMD.java @@ -0,0 +1,340 @@ +package nl.sbdeveloper.themeparkplus.commands; + +import nl.sbdeveloper.themeparkplus.ThemeParkPlus; +import nl.sbdeveloper.themeparkplus.api.PlusAPI; +import nl.sbdeveloper.themeparkplus.api.enums.WalkingDirection; +import nl.sbdeveloper.themeparkplus.api.objects.Gate; +import nl.sbdeveloper.themeparkplus.util.Cuboid; +import nl.sbdeveloper.themeparkplus.util.LGUtil; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.command.Command; +import org.bukkit.command.CommandExecutor; +import org.bukkit.command.CommandSender; +import org.jetbrains.annotations.NotNull; + +public class TPPCMD implements CommandExecutor { + + public boolean onCommand(@NotNull CommandSender sender, @NotNull Command cmd, @NotNull String label, @NotNull String[] args) { + if (args.length == 0) { + return helpCommand(sender); + } else if (args[0].equalsIgnoreCase("info") && args.length == 1) { + return infoCommand(sender); + } else if (args[0].equalsIgnoreCase("opengate") && args.length == 5) { + if (!sender.hasPermission("tpp.opengate")) { + sender.sendMessage(ChatColor.RED + "You don't have permission to do this."); + return true; + } + return openGate(sender, args, 0, null); + } else if (args[0].equalsIgnoreCase("opengate") && args.length == 6) { + if (!sender.hasPermission("tpp.opengate")) { + sender.sendMessage(ChatColor.RED + "You don't have permission to do this."); + return true; + } + + try { + int amount = Integer.parseInt(args[5]); + return openGate(sender, args, amount, null); + } catch (NumberFormatException ex) { + try { + WalkingDirection dir = WalkingDirection.valueOf(args[5]); + + return openGate(sender, args, 0, dir); + } catch (Exception ex2) { + sender.sendMessage(ChatColor.RED + "Unknown direction or amount."); + return true; + } + } + } else if (args[0].equalsIgnoreCase("opengate") && args.length == 7) { + if (!sender.hasPermission("tpp.opengate")) { + sender.sendMessage(ChatColor.RED + "You don't have permission to do this."); + return true; + } + + int amount; + WalkingDirection dir; + + try { + amount = Integer.parseInt(args[5]); + } catch (NumberFormatException ex) { + sender.sendMessage(ChatColor.RED + "Unknown amount."); + ex.printStackTrace(); + return true; + } + + try { + dir = WalkingDirection.valueOf(args[6]); + } catch (Exception ex2) { + sender.sendMessage(ChatColor.RED + "Unknown direction."); + return true; + } + + return openGate(sender, args, amount, dir); + } else if (args[0].equalsIgnoreCase("closegate") && args.length == 5) { + if (!sender.hasPermission("tpp.closegate")) { + sender.sendMessage(ChatColor.RED + "You don't have permission to do this."); + return true; + } + + return closeGate(sender, args); + } else if (args[0].equalsIgnoreCase("lampon") && args.length == 5) { + if (!sender.hasPermission("tpp.lampon")) { + sender.sendMessage(ChatColor.RED + "You don't have permission to do this."); + return true; + } + return lampTurnOnCommand(sender, args, 0); + } else if (args[0].equalsIgnoreCase("lampon") && args.length == 6) { + if (!sender.hasPermission("tpp.lampon")) { + sender.sendMessage(ChatColor.RED + "You don't have permission to do this."); + return true; + } + + int amount; + try { + amount = Integer.parseInt(args[5]); + } catch (NumberFormatException ex) { + sender.sendMessage(ChatColor.RED + "Unknown amount."); + return true; + } + + return lampTurnOnCommand(sender, args, amount); + } else if (args[0].equalsIgnoreCase("lampoff") && args.length == 5) { + if (!sender.hasPermission("tpp.lampoff")) { + sender.sendMessage(ChatColor.RED + "You don't have permission to do this."); + return true; + } + return lampTurnOffCommand(sender, args); + } else if (args[0].equalsIgnoreCase("lampson") && args.length == 8) { + if (!sender.hasPermission("tpp.lampson")) { + sender.sendMessage(ChatColor.RED + "You don't have permission to do this."); + return true; + } + return lampsTurnOnCommand(sender, args, 0); + } else if (args[0].equalsIgnoreCase("lampson") && args.length == 9) { + if (!sender.hasPermission("tpp.lampson")) { + sender.sendMessage(ChatColor.RED + "You don't have permission to do this."); + return true; + } + + int amount; + try { + amount = Integer.parseInt(args[8]); + } catch (NumberFormatException ex) { + sender.sendMessage(ChatColor.RED + "Unknown amount."); + return true; + } + + return lampsTurnOnCommand(sender, args, amount); + } else if (args[0].equalsIgnoreCase("lampsoff") && args.length == 8) { + if (!sender.hasPermission("tpp.lampsoff")) { + sender.sendMessage(ChatColor.RED + "You don't have permission to do this."); + return true; + } + return lampsTurnOffCommand(sender, args); + } + return helpCommand(sender); + } + + private boolean infoCommand(@NotNull CommandSender sender) { + sender.sendMessage("§1=================================="); + sender.sendMessage("§6ThemeParkPlus plugin made by §aSBDeveloper"); + sender.sendMessage("§6Version: " + ThemeParkPlus.getInstance().getDescription().getVersion()); + sender.sendMessage("§6Type /themeparkplus help for more information!"); + sender.sendMessage("§1=================================="); + return true; + } + + private boolean helpCommand(@NotNull CommandSender sender) { + sender.sendMessage("§8ThemeParkPlus commands:"); + sender.sendMessage("§6/themeparkplus info§f: Gives you information about the plugin."); + sender.sendMessage("§6/themeparkplus help§f: Gives you this help page."); + sender.sendMessage(" "); + sender.sendMessage("§6/themeparkplus opengate [Player Count/Direction] [Direction]§f: Open a gate!"); + sender.sendMessage("§6/themeparkplus closegate §f: Close a gate!"); + sender.sendMessage(" "); + sender.sendMessage("§6/themeparkplus lampon [Seconds on] §f: Turn a lamp on!"); + sender.sendMessage("§6/themeparkplus lampoff §f: Turn a lamp off!"); + sender.sendMessage("§6/themeparkplus lampson [Seconds on]§f: Turn multiple lamps on."); + sender.sendMessage("§6/themeparkplus lampsoff §f: Turn multiple lamps off."); + return true; + } + + private boolean openGate(CommandSender sender, @NotNull String[] args, int amount, WalkingDirection dir) { + World bworld = Bukkit.getWorld(args[1]); + double bx = Double.parseDouble(args[2]); + double by = Double.parseDouble(args[3]); + double bz = Double.parseDouble(args[4]); + Location loc = new Location(bworld, bx, by, bz); + + Gate gate = null; + if (amount != 0 && dir == null) { + //GEEN ONEWAY MET WEL AANTAL + gate = new Gate(loc, amount); + } else if (amount == 0 && dir != null) { + //ONE WAY ZONDER AANTAL + gate = new Gate(loc, dir); + } else if (amount != 0) { + //WEL ONE WAY MET WEL AANTAL + gate = new Gate(loc, amount, dir); + } + + if (gate != null) { + PlusAPI.addGate(gate); + } + + Block b = loc.getBlock(); + if (LGUtil.isOpenable(b)) { + if (LGUtil.isOpen(b)) { + sender.sendMessage(ChatColor.RED + "That gate is already opened!"); + return true; + } + if (gate != null && gate.isDirectional()) { + LGUtil.openGate(b, gate.getDirection().getBlockFace()); + } else { + LGUtil.openGate(b); + } + } else { + sender.sendMessage(ChatColor.RED + "That block is not a gate."); + return true; + } + + if (amount != 0) { + sender.sendMessage(ChatColor.GREEN + "Gate opened for " + amount + " players!"); + } else { + sender.sendMessage(ChatColor.GREEN + "Gate opened!"); + } + return true; + } + + private boolean closeGate(CommandSender sender, @NotNull String[] args) { + World bworld = Bukkit.getWorld(args[1]); + double bx = Double.parseDouble(args[2]); + double by = Double.parseDouble(args[3]); + double bz = Double.parseDouble(args[4]); + Location loc = new Location(bworld, bx, by, bz); + + Block b = loc.getBlock(); + if (LGUtil.isOpenable(b)) { + Gate gate = PlusAPI.getGate(loc); + if (gate != null) { + PlusAPI.removeGate(gate); + } + + if (!LGUtil.isOpen(b)) { + sender.sendMessage(ChatColor.RED + "That gate is already closed!"); + return true; + } + LGUtil.closeGate(b); + } else { + sender.sendMessage(ChatColor.RED + "That block is not a gate."); + return true; + } + + sender.sendMessage(ChatColor.GREEN + "Gate closed!"); + return true; + } + + private boolean lampTurnOnCommand(CommandSender sender, @NotNull String[] args, int secOn) { + World bworld = Bukkit.getWorld(args[1]); + double bx = Double.parseDouble(args[2]); + double by = Double.parseDouble(args[3]); + double bz = Double.parseDouble(args[4]); + Location loc = new Location(bworld, bx, by, bz); + Block block = loc.getBlock(); + + if (secOn == 0) { + if (!LGUtil.zetLampAan(block)) { + sender.sendMessage(ChatColor.RED + "Couldn't turn the lamp on. Maybe it's not a lamp, or it's already on."); + return true; + } + + sender.sendMessage(ChatColor.GREEN + "Lamp turned on!"); + } else { + if (!LGUtil.zetLampAan(block)) { + sender.sendMessage(ChatColor.RED + "Couldn't turn the lamp on. Maybe it's not a lamp, or it's already on."); + return true; + } + + Bukkit.getScheduler().runTaskLater(ThemeParkPlus.getInstance(), () -> LGUtil.zetLampUit(block), secOn * 20); + sender.sendMessage(ChatColor.GREEN + "Lamp turned on for " + secOn + " second(s)!"); + } + return true; + } + + private boolean lampTurnOffCommand(CommandSender sender, @NotNull String[] args) { + World bworld = Bukkit.getWorld(args[1]); + double bx = Double.parseDouble(args[2]); + double by = Double.parseDouble(args[3]); + double bz = Double.parseDouble(args[4]); + Location loc = new Location(bworld, bx, by, bz); + + if (!LGUtil.zetLampUit(loc.getBlock())) { + sender.sendMessage(ChatColor.RED + "Couldn't turn the lamp off. Maybe it's not a lamp, or it's already off."); + return true; + } + + sender.sendMessage(ChatColor.GREEN + "Lamp turned off!"); + return true; + } + + private boolean lampsTurnOnCommand(CommandSender sender, @NotNull String[] args, int secOn) { + World bworld = Bukkit.getWorld(args[1]); + + double bx = Double.parseDouble(args[2]); + double by = Double.parseDouble(args[3]); + double bz = Double.parseDouble(args[4]); + + double bx2 = Double.parseDouble(args[5]); + double by2 = Double.parseDouble(args[6]); + double bz2 = Double.parseDouble(args[7]); + + Location loc = new Location(bworld, bx, by, bz); + Location loc2 = new Location(bworld, bx2, by2, bz2); + + Cuboid cub = new Cuboid(loc, loc2); + + if (secOn == 0) { + Bukkit.getScheduler().runTaskAsynchronously(ThemeParkPlus.getInstance(), () -> cub.getBlocks().forEach(block -> { + if (block.getType().name().contains("REDSTONE_LAMP")) Bukkit.getScheduler().runTask(ThemeParkPlus.getInstance(), () -> LGUtil.zetLampAan(block)); + })); + sender.sendMessage(ChatColor.GREEN + "Lamps in region turned on!"); + } else { + Bukkit.getScheduler().runTaskAsynchronously(ThemeParkPlus.getInstance(), () -> cub.getBlocks().forEach(block -> { + if (block.getType().name().contains("REDSTONE_LAMP")) Bukkit.getScheduler().runTask(ThemeParkPlus.getInstance(), () -> LGUtil.zetLampAan(block)); + })); + + Bukkit.getScheduler().runTaskLaterAsynchronously(ThemeParkPlus.getInstance(), () -> cub.getBlocks().forEach(block -> { + if (block.getType().name().contains("REDSTONE_LAMP")) Bukkit.getScheduler().runTask(ThemeParkPlus.getInstance(), () -> LGUtil.zetLampUit(block)); + }), secOn * 20); + sender.sendMessage(ChatColor.GREEN + "Lamps in region turned on for " + secOn + " seconds!"); + } + return true; + } + + private boolean lampsTurnOffCommand(@NotNull CommandSender sender, @NotNull String[] args) { + World bworld = Bukkit.getWorld(args[1]); + + double bx = Double.parseDouble(args[2]); + double by = Double.parseDouble(args[3]); + double bz = Double.parseDouble(args[4]); + + double bx2 = Double.parseDouble(args[5]); + double by2 = Double.parseDouble(args[6]); + double bz2 = Double.parseDouble(args[7]); + + Location loc = new Location(bworld, bx, by, bz); + Location loc2 = new Location(bworld, bx2, by2, bz2); + + Cuboid cub = new Cuboid(loc, loc2); + + Bukkit.getScheduler().runTaskAsynchronously(ThemeParkPlus.getInstance(), () -> cub.getBlocks().forEach(block -> { + if (block.getType().name().contains("REDSTONE_LAMP")) Bukkit.getScheduler().runTask(ThemeParkPlus.getInstance(), () -> LGUtil.zetLampUit(block)); + })); + + sender.sendMessage(ChatColor.GREEN + "Lamps in region turned off!"); + return true; + } +} \ No newline at end of file diff --git a/src/main/java/nl/sbdeveloper/themeparkplus/listeners/AntiFreerunListener.java b/src/main/java/nl/sbdeveloper/themeparkplus/listeners/AntiFreerunListener.java new file mode 100644 index 0000000..2dbf181 --- /dev/null +++ b/src/main/java/nl/sbdeveloper/themeparkplus/listeners/AntiFreerunListener.java @@ -0,0 +1,18 @@ +package nl.sbdeveloper.themeparkplus.listeners; + +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerJoinEvent; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; + +public class AntiFreerunListener implements Listener { + @EventHandler + public void onJoin(PlayerJoinEvent e) { + Player p = e.getPlayer(); + if (!p.hasPermission("tpp.bypassantifreerun")) { + p.addPotionEffect(new PotionEffect(PotionEffectType.JUMP, 1000000, -2, false, false)); + } + } +} diff --git a/src/main/java/nl/sbdeveloper/themeparkplus/listeners/DirectionalGateListener.java b/src/main/java/nl/sbdeveloper/themeparkplus/listeners/DirectionalGateListener.java new file mode 100644 index 0000000..508256d --- /dev/null +++ b/src/main/java/nl/sbdeveloper/themeparkplus/listeners/DirectionalGateListener.java @@ -0,0 +1,70 @@ +package nl.sbdeveloper.themeparkplus.listeners; + +import nl.sbdeveloper.themeparkplus.api.PlusAPI; +import nl.sbdeveloper.themeparkplus.api.enums.WalkingDirection; +import nl.sbdeveloper.themeparkplus.api.objects.Gate; +import nl.sbdeveloper.themeparkplus.util.DirectionUtil; +import nl.sbdeveloper.themeparkplus.util.LGUtil; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.block.Block; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerMoveEvent; + +public class DirectionalGateListener implements Listener { + @EventHandler + public void onWalkThroughFenceGate(PlayerMoveEvent e) { + if (e.getTo() != null + && (e.getFrom().getBlockX() != e.getTo().getBlockX() + || e.getFrom().getBlockY() != e.getTo().getBlockY() + || e.getFrom().getBlockZ() != e.getTo().getBlockZ())) { + Location oldLoc = e.getFrom(); + Location newLoc = e.getTo(); + Block oldBlock = oldLoc.getBlock(); + + if (oldBlock.getType().name().contains("FENCE_GATE")) { + Gate gate = PlusAPI.getGate(oldBlock.getLocation()); + if (gate == null) return; + + if (!gate.isDirectional()) { + if (gate.getMaxCount() != 0) { + if (gate.getPassedCount() + 1 == gate.getMaxCount()) { + if (LGUtil.isOpenable(oldBlock)) { + LGUtil.closeGate(oldBlock); + } else { + PlusAPI.removeGate(gate); + return; + } + PlusAPI.removeGate(gate); + } else { + gate.setPassedCount(gate.getPassedCount() + 1); + } + } + } else { + WalkingDirection loopdir = DirectionUtil.getDirection(oldLoc.getBlockX(), oldLoc.getBlockZ(), newLoc.getBlockX(), newLoc.getBlockZ()); + if (loopdir == null) return; + if (loopdir.getBlockFace() != gate.getDirection().getBlockFace()) { + e.getPlayer().teleport(oldLoc); + e.getPlayer().sendMessage(ChatColor.RED + "You can't walk in that direction."); + return; + } + + if (gate.getMaxCount() != 0) { + if (gate.getPassedCount() + 1 == gate.getMaxCount()) { + if (LGUtil.isOpenable(oldBlock)) { + LGUtil.closeGate(oldBlock); + } else { + PlusAPI.removeGate(gate); + return; + } + PlusAPI.removeGate(gate); + } else { + gate.setPassedCount(gate.getPassedCount() + 1); + } + } + } + } + } + } +} diff --git a/src/main/java/nl/sbdeveloper/themeparkplus/listeners/FastpassListeners.java b/src/main/java/nl/sbdeveloper/themeparkplus/listeners/FastpassListeners.java new file mode 100644 index 0000000..6650699 --- /dev/null +++ b/src/main/java/nl/sbdeveloper/themeparkplus/listeners/FastpassListeners.java @@ -0,0 +1,149 @@ +package nl.sbdeveloper.themeparkplus.listeners; + +import de.tr7zw.changeme.nbtapi.NBTItem; +import me.paradoxpixel.themepark.api.API; +import me.paradoxpixel.themepark.api.attraction.Attraction; +import me.paradoxpixel.themepark.api.attraction.component.Status; +import nl.sbdeveloper.themeparkplus.ThemeParkPlus; +import nl.sbdeveloper.themeparkplus.util.ConfigUtil; +import nl.sbdeveloper.themeparkplus.util.LGUtil; +import nl.sbdeveloper.themeparkplus.util.XMaterial; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.Location; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.Sign; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.block.Action; +import org.bukkit.event.block.SignChangeEvent; +import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.inventory.EquipmentSlot; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import java.util.Collections; + +public class FastpassListeners implements Listener { + @EventHandler + public void onCreate(SignChangeEvent e) { + Player p = e.getPlayer(); + String[] lines = e.getLines(); + + if (!p.hasPermission("tpp.fastpass.create")) { + p.sendMessage(ChatColor.RED + "You don't have the permission to do this."); + return; + } + + if (!API.isAttraction(e.getLine(2))) { + p.sendMessage(ChatColor.RED + e.getLine(2) + " is not an existing ride."); + return; + } + + if (lines[0].equalsIgnoreCase("[ThemeParkPlus]") && lines[1].equalsIgnoreCase("Machine") && !lines[2].isEmpty() && !lines[3].isEmpty()) { + e.setLine(0, ChatColor.GRAY + "[" + ChatColor.GOLD + "ThemeParkPlus" + ChatColor.GRAY + "]"); + e.setLine(1, ChatColor.BLUE + "Machine"); + } else if (lines[0].equalsIgnoreCase("[ThemeParkPlus]") && lines[1].equalsIgnoreCase("Scanner") && !lines[2].isEmpty() && !lines[3].isEmpty()) { + e.setLine(0, ChatColor.GRAY + "[" + ChatColor.GOLD + "ThemeParkPlus" + ChatColor.GRAY + "]"); + e.setLine(1, ChatColor.RED + "Scanner"); + } + } + + @EventHandler + public void onSignClick(PlayerInteractEvent e) { + if (e.getClickedBlock() == null || e.getAction() != Action.RIGHT_CLICK_BLOCK || !(e.getClickedBlock().getState() instanceof Sign) || e.getHand() != EquipmentSlot.HAND) return; + + Sign sign = (Sign) e.getClickedBlock().getState(); + if (ChatColor.stripColor(sign.getLine(1)).equalsIgnoreCase("Machine")) { + //Buy a ticket + String attID = sign.getLine(2); + if (!API.isAttraction(attID)) return; + + Attraction att = API.getAttraction(attID); + + double price; + try { + price = Double.parseDouble(sign.getLine(3)); + } catch (NumberFormatException ex) { + return; + } + + ItemStack ticket = XMaterial.PAPER.parseItem(); + if (ticket == null) return; + ItemMeta meta = ticket.getItemMeta(); + if (meta == null) return; + meta.setDisplayName(ChatColor.GOLD + "Fasspass Ticket"); + meta.setLore(Collections.singletonList(ChatColor.GOLD + "For: " + ConfigUtil.makecolored(att.getName()))); + ticket.setItemMeta(meta); + + NBTItem item = new NBTItem(ticket); + item.setString("RideID", attID); + item.setDouble("Price", price); + ticket = item.getItem(); + + if (ThemeParkPlus.getEconomy().getBalance(e.getPlayer()) < price) { + e.getPlayer().sendMessage(ChatColor.RED + "You can't pay this fastpass ticket."); + return; + } + + for (ItemStack content : e.getPlayer().getInventory().getContents()) { + if (content == null || content.getType() == null || content.getType() == Material.AIR) continue; + NBTItem nbtContent = new NBTItem(content); + if (content.getType() == XMaterial.PAPER.parseMaterial() && content.hasItemMeta() && nbtContent.hasKey("RideID") && nbtContent.getString("RideID").equalsIgnoreCase(attID)) { + e.getPlayer().sendMessage(ChatColor.RED + "You already have a ticket for this ride."); + return; + } + } + + ThemeParkPlus.getEconomy().withdrawPlayer(e.getPlayer(), price); + + e.getPlayer().getInventory().addItem(ticket); + e.getPlayer().updateInventory(); + e.getPlayer().sendMessage(ChatColor.GREEN + "Bought a Fastpass ticket for " + ConfigUtil.makecolored(att.getName()) + ChatColor.GREEN + ". You paid €" + String.format("%.2f", price) + " for it!"); + } else if (ChatColor.stripColor(sign.getLine(1)).equalsIgnoreCase("Scanner")) { + //Scan a ticket + if (!API.isAttraction(sign.getLine(2))) { + e.getPlayer().sendMessage(ChatColor.RED + "This sign is incorrect! The attraction doesn't exists."); + return; + } + Attraction att = API.getAttraction(sign.getLine(2)); + + ItemStack content = e.getPlayer().getInventory().getItemInMainHand(); + NBTItem nbtContent = new NBTItem(content); + if (content.getType() == Material.AIR + || !content.hasItemMeta() || !nbtContent.hasKey("RideID")) { + e.getPlayer().sendMessage(ChatColor.RED + "You need a fastpass ticket to go through the fastpass line."); + return; + } + + if (att.getStatus() != Status.OPEN && att.getStatus() != Status.ACTIVE) { + e.getPlayer().sendMessage(ChatColor.RED + "This attraction is closed."); + return; + } + + try { + String[] locationString = sign.getLine(3).split(":"); + int x = Integer.parseInt(locationString[0]); + int y = Integer.parseInt(locationString[1]); + int z = Integer.parseInt(locationString[2]); + + Block b = e.getPlayer().getWorld().getBlockAt(x, y, z); + + if (LGUtil.isOpenable(b)) { + LGUtil.openGate(b); + Bukkit.getScheduler().scheduleSyncDelayedTask(ThemeParkPlus.getInstance(), () -> LGUtil.closeGate(b), 60L); + } else { + Location tpLoc = new Location(e.getPlayer().getWorld(), x, y, z, e.getPlayer().getLocation().getYaw(), e.getPlayer().getLocation().getPitch()); + e.getPlayer().teleport(tpLoc); + } + e.getPlayer().getInventory().remove(e.getPlayer().getInventory().getItemInMainHand()); + e.getPlayer().updateInventory(); + e.getPlayer().sendMessage(ChatColor.GREEN + "Successfully redeemed your fastpass ticket!"); + } catch (NumberFormatException | ArrayIndexOutOfBoundsException ex) { + e.getPlayer().sendMessage(ChatColor.RED + "This sign is incorrect!"); + } + } + } +} diff --git a/src/main/java/nl/sbdeveloper/themeparkplus/listeners/StatusChangeListener.java b/src/main/java/nl/sbdeveloper/themeparkplus/listeners/StatusChangeListener.java new file mode 100644 index 0000000..647baab --- /dev/null +++ b/src/main/java/nl/sbdeveloper/themeparkplus/listeners/StatusChangeListener.java @@ -0,0 +1,41 @@ +package nl.sbdeveloper.themeparkplus.listeners; + +import club.minnced.discord.webhook.send.WebhookEmbed; +import club.minnced.discord.webhook.send.WebhookEmbedBuilder; +import me.paradoxpixel.themepark.api.attraction.component.Status; +import me.paradoxpixel.themepark.api.event.attraction.StatusChangeEvent; +import me.paradoxpixel.themepark.attraction.status.StatusManager; +import nl.sbdeveloper.themeparkplus.ThemeParkPlus; +import nl.sbdeveloper.themeparkplus.util.ConfigUtil; +import org.bukkit.Bukkit; +import org.bukkit.ChatColor; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; + +public class StatusChangeListener implements Listener { + @EventHandler + public void onStatusChange(StatusChangeEvent e) { + if (e.getStatusAfter() != Status.GLOBAL) { + String title = ThemeParkPlus.getSConfig().getFile().getString("DiscordWebhook.Embed.Title"); + if (title == null) return; + + String rideName = ChatColor.stripColor(ConfigUtil.makecolored(e.getAttraction().getName())); + String statusAfter = ChatColor.stripColor(ConfigUtil.makecolored(StatusManager.getName(e.getStatusAfter()))); + + title = title.replaceAll("%RideName%", rideName); + String copy = ThemeParkPlus.getSConfig().getFile().getString("DiscordWebhook.Embed.Copyright"); + String copyimg = ThemeParkPlus.getSConfig().getFile().getString("DiscordWebhook.Embed.CopyrightImage"); + Integer color = ThemeParkPlus.getSConfig().getFile().getInt("DiscordWebhook.Embed.Colors." + e.getStatusAfter().toString()); + if (copy == null || copyimg == null) return; + + WebhookEmbed embed = new WebhookEmbedBuilder() + .setTitle(new WebhookEmbed.EmbedTitle(title, "")) + .setFooter(new WebhookEmbed.EmbedFooter(copy, copyimg)) + .setColor(color) + .addField(new WebhookEmbed.EmbedField(false, rideName, statusAfter)) + .build(); + + ThemeParkPlus.getWebhookClient().send(embed); + } + } +} diff --git a/src/main/java/nl/sbdeveloper/themeparkplus/managers/DBManager.java b/src/main/java/nl/sbdeveloper/themeparkplus/managers/DBManager.java new file mode 100644 index 0000000..27a7135 --- /dev/null +++ b/src/main/java/nl/sbdeveloper/themeparkplus/managers/DBManager.java @@ -0,0 +1,81 @@ +package nl.sbdeveloper.themeparkplus.managers; + +import com.google.gson.Gson; +import nl.SBDevelopment.SBUtilities.Data.SQLiteDB; +import nl.SBDevelopment.SBUtilities.Utils.LocationSerializer; +import nl.sbdeveloper.themeparkplus.api.PlusAPI; +import nl.sbdeveloper.themeparkplus.api.objects.Gate; +import org.bukkit.Location; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Map; + +public class DBManager { + + private static SQLiteDB data; + private static Connection con; + + public DBManager(String name) { + data = new SQLiteDB(name); + + try { + con = data.getConnection(); + + String query = "CREATE TABLE IF NOT EXISTS gates (gateLocation varchar(255) NOT NULL, gateData blob NOT NULL, UNIQUE (gateLocation))"; + PreparedStatement statement = con.prepareStatement(query); + statement.execute(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + + public void load() throws SQLException { + /* Load gates */ + String query = "SELECT * FROM gates"; + PreparedStatement statement = con.prepareStatement(query); + ResultSet gateSet = statement.executeQuery(); + while (gateSet.next()) { + //Loading a gates... + byte[] blob = gateSet.getBytes("gateData"); + String json = new String(blob); + + Gson gson = new Gson(); + Gate gate = gson.fromJson(json, Gate.class); + PlusAPI.addGate(gate); + } + } + + public void save() { + for (Map.Entry entry : PlusAPI.getGates().entrySet()) { + + Gson gson = new Gson(); + byte[] blob = gson.toJson(entry.getValue()).getBytes(); + + try { + String query = "INSERT INTO gates (gateLocation, gateData) VALUES (?, ?)"; + PreparedStatement statement = con.prepareStatement(query); + statement.setString(1, LocationSerializer.serialize(entry.getKey())); + statement.setBytes(2, blob); + statement.executeUpdate(); + } catch (SQLException ignored) {} + + try { + String query2 = "UPDATE gates SET gateData = ? WHERE gateLocation = ?"; + PreparedStatement statement2 = con.prepareStatement(query2); + statement2.setBytes(1, blob); + statement2.setString(2, LocationSerializer.serialize(entry.getKey())); + statement2.executeUpdate(); + } catch (SQLException e) { + e.printStackTrace(); + } + } + } + + public void closeConnection() { + data.closeSource(); + } + +} diff --git a/src/main/java/nl/sbdeveloper/themeparkplus/util/ConfigUtil.java b/src/main/java/nl/sbdeveloper/themeparkplus/util/ConfigUtil.java new file mode 100644 index 0000000..fa4b6a6 --- /dev/null +++ b/src/main/java/nl/sbdeveloper/themeparkplus/util/ConfigUtil.java @@ -0,0 +1,11 @@ +package nl.sbdeveloper.themeparkplus.util; + +import org.bukkit.ChatColor; +import org.jetbrains.annotations.NotNull; + +public class ConfigUtil { + @NotNull + public static String makecolored(String str) { + return ChatColor.translateAlternateColorCodes('&', str); + } +} diff --git a/src/main/java/nl/sbdeveloper/themeparkplus/util/Cuboid.java b/src/main/java/nl/sbdeveloper/themeparkplus/util/Cuboid.java new file mode 100644 index 0000000..12983f7 --- /dev/null +++ b/src/main/java/nl/sbdeveloper/themeparkplus/util/Cuboid.java @@ -0,0 +1,177 @@ +package nl.sbdeveloper.themeparkplus.util; + +import org.bukkit.Bukkit; +import org.bukkit.Location; +import org.bukkit.World; +import org.bukkit.block.Block; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +public class Cuboid implements Cloneable, ConfigurationSerializable, Iterable { + + protected String worldName; + protected final Vector minimumPoint, maximumPoint; + + public Cuboid(@NotNull Cuboid cuboid) { + this(cuboid.worldName, cuboid.minimumPoint.getX(), cuboid.minimumPoint.getY(), cuboid.minimumPoint.getZ(), cuboid.maximumPoint.getX(), cuboid.maximumPoint.getY(), cuboid.maximumPoint.getZ()); + } + + public Cuboid(Location loc) { + this(loc, loc); + } + + public Cuboid(Location loc1, Location loc2) { + if (loc1 != null && loc2 != null) { + if (loc1.getWorld() != null && loc2.getWorld() != null) { + if (!loc1.getWorld().getUID().equals(loc2.getWorld().getUID())) + throw new IllegalStateException("The 2 locations of the cuboid must be in the same world!"); + } else { + throw new NullPointerException("One/both of the worlds is/are null!"); + } + this.worldName = loc1.getWorld().getName(); + + double xPos1 = Math.min(loc1.getX(), loc2.getX()); + double yPos1 = Math.min(loc1.getY(), loc2.getY()); + double zPos1 = Math.min(loc1.getZ(), loc2.getZ()); + double xPos2 = Math.max(loc1.getX(), loc2.getX()); + double yPos2 = Math.max(loc1.getY(), loc2.getY()); + double zPos2 = Math.max(loc1.getZ(), loc2.getZ()); + this.minimumPoint = new Vector(xPos1, yPos1, zPos1); + this.maximumPoint = new Vector(xPos2, yPos2, zPos2); + } else { + throw new NullPointerException("One/both of the locations is/are null!"); + } + } + + public Cuboid(String worldName, double x1, double y1, double z1, double x2, double y2, double z2) { + if (worldName == null || Bukkit.getServer().getWorld(worldName) == null) + throw new NullPointerException("One/both of the worlds is/are null!"); + this.worldName = worldName; + + double xPos1 = Math.min(x1, x2); + double xPos2 = Math.max(x1, x2); + double yPos1 = Math.min(y1, y2); + double yPos2 = Math.max(y1, y2); + double zPos1 = Math.min(z1, z2); + double zPos2 = Math.max(z1, z2); + this.minimumPoint = new Vector(xPos1, yPos1, zPos1); + this.maximumPoint = new Vector(xPos2, yPos2, zPos2); + } + + public boolean containsLocation(Location location) { + return location != null && location.getWorld().getName().equals(this.worldName) && location.toVector().isInAABB(this.minimumPoint, this.maximumPoint); + } + + public boolean containsVector(Vector vector) { + return vector != null && vector.isInAABB(this.minimumPoint, this.maximumPoint); + } + + public List getBlocks() { + List blockList = new ArrayList<>(); + World world = this.getWorld(); + if (world != null) { + for (int x = this.minimumPoint.getBlockX(); x <= this.maximumPoint.getBlockX(); x++) { + for (int y = this.minimumPoint.getBlockY(); y <= this.maximumPoint.getBlockY() && y <= world.getMaxHeight(); y++) { + for (int z = this.minimumPoint.getBlockZ(); z <= this.maximumPoint.getBlockZ(); z++) { + blockList.add(world.getBlockAt(x, y, z)); + } + } + } + } + return blockList; + } + + public Location getLowerLocation() { + return this.minimumPoint.toLocation(this.getWorld()); + } + + public double getLowerX() { + return this.minimumPoint.getX(); + } + + public double getLowerY() { + return this.minimumPoint.getY(); + } + + public double getLowerZ() { + return this.minimumPoint.getZ(); + } + + public Location getUpperLocation() { + return this.maximumPoint.toLocation(this.getWorld()); + } + + public double getUpperX() { + return this.maximumPoint.getX(); + } + + public double getUpperY() { + return this.maximumPoint.getY(); + } + + public double getUpperZ() { + return this.maximumPoint.getZ(); + } + + public double getVolume() { + return (this.getUpperX() - this.getLowerX() + 1) * (this.getUpperY() - this.getLowerY() + 1) * (this.getUpperZ() - this.getLowerZ() + 1); + } + + public World getWorld() { + World world = Bukkit.getServer().getWorld(this.worldName); + if (world == null) throw new NullPointerException("World '" + this.worldName + "' is not loaded."); + return world; + } + + public void setWorld(World world) { + if (world != null) this.worldName = world.getName(); + else throw new NullPointerException("The world cannot be null."); + } + + @Override + public Cuboid clone() { + return new Cuboid(this); + } + + @Override + public ListIterator iterator() { + return this.getBlocks().listIterator(); + } + + @Override + public Map serialize() { + Map serializedCuboid = new HashMap<>(); + serializedCuboid.put("worldName", this.worldName); + serializedCuboid.put("x1", this.minimumPoint.getX()); + serializedCuboid.put("x2", this.maximumPoint.getX()); + serializedCuboid.put("y1", this.minimumPoint.getY()); + serializedCuboid.put("y2", this.maximumPoint.getY()); + serializedCuboid.put("z1", this.minimumPoint.getZ()); + serializedCuboid.put("z2", this.maximumPoint.getZ()); + return serializedCuboid; + } + + @Nullable + public static Cuboid deserialize(Map serializedCuboid) { + try { + String worldName = (String) serializedCuboid.get("worldName"); + + double xPos1 = (Double) serializedCuboid.get("x1"); + double xPos2 = (Double) serializedCuboid.get("x2"); + double yPos1 = (Double) serializedCuboid.get("y1"); + double yPos2 = (Double) serializedCuboid.get("y2"); + double zPos1 = (Double) serializedCuboid.get("z1"); + double zPos2 = (Double) serializedCuboid.get("z2"); + + return new Cuboid(worldName, xPos1, yPos1, zPos1, xPos2, yPos2, zPos2); + } catch (Exception ex) { + ex.printStackTrace(); + return null; + } + } + +} \ No newline at end of file diff --git a/src/main/java/nl/sbdeveloper/themeparkplus/util/DirectionUtil.java b/src/main/java/nl/sbdeveloper/themeparkplus/util/DirectionUtil.java new file mode 100644 index 0000000..713a5d5 --- /dev/null +++ b/src/main/java/nl/sbdeveloper/themeparkplus/util/DirectionUtil.java @@ -0,0 +1,39 @@ +package nl.sbdeveloper.themeparkplus.util; + +import nl.sbdeveloper.themeparkplus.api.enums.WalkingDirection; +import org.jetbrains.annotations.Nullable; + +public class DirectionUtil { + + @Nullable + public static WalkingDirection getDirection(int xoud, int zoud, int xnieuw, int znieuw) { + int changeInX = xnieuw - xoud; + int changeInZ = znieuw - zoud; + + if (changeInZ != 0 && changeInX == 0) { + if (changeInZ < 0) { + return WalkingDirection.NORTH; + } else { + return WalkingDirection.SOUTH; + } + } else if (changeInX != 0 && changeInZ == 0) { + if (changeInX < 0) { + return WalkingDirection.WEST; + } else { + return WalkingDirection.EAST; + } + } + return null; + } + + public static boolean isGoodDirection(WalkingDirection current, WalkingDirection good) { + if (current == WalkingDirection.EAST && good == WalkingDirection.EAST) { + return true; + } else if (current == WalkingDirection.NORTH && good == WalkingDirection.NORTH) { + return true; + } else if (current == WalkingDirection.SOUTH && good == WalkingDirection.SOUTH) { + return true; + } else return current == WalkingDirection.WEST && good == WalkingDirection.WEST; + + } +} diff --git a/src/main/java/nl/sbdeveloper/themeparkplus/util/LGUtil.java b/src/main/java/nl/sbdeveloper/themeparkplus/util/LGUtil.java new file mode 100644 index 0000000..d16c17b --- /dev/null +++ b/src/main/java/nl/sbdeveloper/themeparkplus/util/LGUtil.java @@ -0,0 +1,298 @@ +package nl.sbdeveloper.themeparkplus.util; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import nl.sbdeveloper.themeparkplus.ThemeParkPlus; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.block.Block; +import org.bukkit.block.BlockFace; +import org.bukkit.block.BlockState; +import org.bukkit.block.data.type.Gate; +import org.bukkit.util.Vector; +import org.jetbrains.annotations.Nullable; + +public class LGUtil { + + private static List offsets = new ArrayList<>(); + private static List nearbyBlocks = new ArrayList<>(); + + private static boolean nieuweVersie = false; + + private static Method getBlockDataMethod; + private static Method setBlockDataMethod; + private static Method getAsStringMethod; + private static Object lampAanData; + private static Object lampUitData; + + public LGUtil() throws ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException { + offsets.add(new Vector(1, 0, 0)); + offsets.add(new Vector(-1, 0, 0)); + offsets.add(new Vector(0, 1, 0)); + offsets.add(new Vector(0, -1, 0)); + offsets.add(new Vector(0, 0, 1)); + offsets.add(new Vector(0, 0, -1)); + + Class blockDataClass = Class.forName("org.bukkit.block.data.BlockData"); + Method methodCreateBlockData = Bukkit.class.getMethod("createBlockData", String.class); + getBlockDataMethod = Block.class.getMethod("getBlockData"); + setBlockDataMethod = Block.class.getMethod("setBlockData", blockDataClass, + Boolean.TYPE); + getAsStringMethod = blockDataClass.getMethod("getAsString"); + lampAanData = methodCreateBlockData.invoke(null, "minecraft:redstone_lamp[lit=true]"); + lampUitData = methodCreateBlockData.invoke(null, "minecraft:redstone_lamp[lit=false]"); + + nieuweVersie = true; + } + + public static boolean zetLampAan(Block lampBlock) { + if (nieuweVersie) { + if (!isLamp(lampBlock)) { + Bukkit.getLogger().severe(lampBlock.getType().toString()); + } + if (isAan(lampBlock)) { + Bukkit.getLogger().severe(getAsString(getBlockData(lampBlock))); + } + if ((isLamp(lampBlock)) && (!isAan(lampBlock))) { + setBlockData(lampBlock, lampAanData); + + return true; + } + return false; + } + if (!isAan(lampBlock)) { + final Block neighbor = getNeighbor(lampBlock); + if (neighbor != null) { + BlockState neighborState = neighbor.getState(); + try { + for (org.bukkit.util.Vector offset: offsets) { + Block neighborOfNeighbor = neighbor.getLocation().add(offset).getBlock(); + if ((neighborOfNeighbor.getX() != lampBlock.getX()) || (neighborOfNeighbor.getY() != lampBlock.getY()) || (neighborOfNeighbor.getZ() != lampBlock.getZ())) { + nearbyBlocks.add(neighborOfNeighbor); + } + } + neighbor.setType(Material.REDSTONE_BLOCK); + } catch (Exception e) { + e.printStackTrace(); + } finally { + neighborState.update(true, false); + Bukkit.getScheduler().runTaskLater(ThemeParkPlus.getInstance(), () -> { + for (Vector offset: offsets) { + Block neighborOfNeighbor = neighbor.getLocation().add(offset).getBlock(); + nearbyBlocks.remove(neighborOfNeighbor); + } + }, 1L); + } + return true; + } + } + + return false; + } + + public static boolean zetLampUit(Block lampBlock) { + if (nieuweVersie) { + if ((isLamp(lampBlock)) && (isAan(lampBlock))) { + setBlockData(lampBlock, lampUitData); + + return true; + } + return false; + } + if (isAan(lampBlock)) { + lampBlock.setType(Objects.requireNonNull(Material.matchMaterial(XMaterial.REDSTONE_LAMP.getLegacy()[0]))); + return true; + } + return false; + } + + public static boolean openGate(Block b) { + return openGate(b, getDirection(b)); + } + + public static boolean openGate(Block b, BlockFace openFace) { + if (nieuweVersie) { + org.bukkit.block.data.BlockData blockData = b.getBlockData(); + if (isOpenable(b)) { + org.bukkit.block.data.Openable op = (org.bukkit.block.data.Openable) blockData; + if (op.isOpen()) return false; + op.setOpen(true); + b.setBlockData(blockData); + + org.bukkit.block.data.Directional dir = (org.bukkit.block.data.Directional) blockData; + dir.setFacing(openFace); + b.setBlockData(blockData); + return true; + } + } else { + BlockState state = b.getState(); + if (isOpenable(b)) { + org.bukkit.material.Openable openable = (org.bukkit.material.Openable) state.getData(); + if (openable.isOpen()) return false; + openable.setOpen(true); + state.setData((org.bukkit.material.MaterialData) openable); + state.update(); + + org.bukkit.material.Directional dir = (org.bukkit.material.Directional) state.getData(); + dir.setFacingDirection(openFace); + state.setData((org.bukkit.material.MaterialData) dir); + state.update(); + return true; + } + } + return false; + } + + public static boolean closeGate(Block b) { + if (nieuweVersie) { + org.bukkit.block.data.BlockData blockData = b.getBlockData(); + if (isOpenable(b)) { + org.bukkit.block.data.Openable op = (org.bukkit.block.data.Openable) blockData; + if (!op.isOpen()) { + return false; + } + op.setOpen(false); + b.setBlockData(blockData); + return true; + } + } else { + BlockState state = b.getState(); + if (isOpenable(b)) { + org.bukkit.material.Openable openable = (org.bukkit.material.Openable) state.getData(); + if (!openable.isOpen()) { + return false; + } + openable.setOpen(false); + state.setData((org.bukkit.material.MaterialData) openable); + state.update(); + return true; + } + } + return false; + } + + @Nullable + public static BlockFace getDirection(Block b) { + if (nieuweVersie) { + org.bukkit.block.data.BlockData blockData = b.getBlockData(); + if (isOpenable(b)) { + org.bukkit.block.data.Directional dir = (org.bukkit.block.data.Directional) blockData; + return dir.getFacing(); + } + } else { + BlockState state = b.getState(); + if (isOpenable(b)) { + org.bukkit.material.Directional dir = (org.bukkit.material.Directional) state.getData(); + return dir.getFacing(); + } + } + return null; + } + + public static boolean isOpen(Block b) { + if (nieuweVersie) { + org.bukkit.block.data.BlockData blockData = b.getBlockData(); + if (isOpenable(b)) { + org.bukkit.block.data.Openable op = (org.bukkit.block.data.Openable) blockData; + return op.isOpen(); + } + } else { + BlockState state = b.getState(); + if (isOpenable(b)) { + org.bukkit.material.Openable openable = (org.bukkit.material.Openable) state.getData(); + return openable.isOpen(); + } + } + return false; + } + + /* Gate codes sponsored by MrWouter <3 */ + public static boolean isOpenable(Block b) { + if (b == null) { + return false; + } + if (nieuweVersie) { + return b.getBlockData() instanceof org.bukkit.block.data.Openable; + } else { + return b.getState().getData() instanceof org.bukkit.material.Openable; + } + } + + private static Block getNeighbor(Block lampBlock) { + List possibleNeighbors = new ArrayList<>(); + if (lampBlock.getLocation().getY() > 0.0D) { + possibleNeighbors.add(lampBlock.getLocation().add(0.0D, -1.0D, 0.0D).getBlock()); + } + if (lampBlock.getLocation().getY() < 255.0D) { + possibleNeighbors.add(lampBlock.getLocation().add(0.0D, 1.0D, 0.0D).getBlock()); + } + possibleNeighbors.add(lampBlock.getLocation().add(0.0D, 0.0D, 1.0D).getBlock()); + possibleNeighbors.add(lampBlock.getLocation().add(0.0D, 0.0D, -1.0D).getBlock()); + possibleNeighbors.add(lampBlock.getLocation().add(1.0D, 0.0D, 0.0D).getBlock()); + possibleNeighbors.add(lampBlock.getLocation().add(-1.0D, 0.0D, 0.0D).getBlock()); + + for (Block neighbor: possibleNeighbors) { + if (neighbor.getType() == Material.AIR) { + return neighbor; + } + } + + for (Block neighbor: possibleNeighbors) { + if ((neighbor.getState().getClass().getSimpleName().equals("CraftBlockState")) && (neighbor.getType() != Material.valueOf(XMaterial.PISTON_HEAD.getLegacy()[0]) && neighbor.getType() != Material.valueOf(XMaterial.MOVING_PISTON.getLegacy()[1])) && (neighbor.getType() != Material.valueOf("REDSTONE_LAMP_ON"))) { + return neighbor; + } + } + + for (Block neighbor: possibleNeighbors) { + if ((neighbor.getState().getClass().getSimpleName().equals("CraftBlockState")) && (neighbor.getType() != Material.valueOf(XMaterial.PISTON_HEAD.getLegacy()[0]) && neighbor.getType() != Material.valueOf(XMaterial.MOVING_PISTON.getLegacy()[1]))) { + return neighbor; + } + } + return null; + } + + private static boolean isAan(Block lamp) { + return ((nieuweVersie) && (Objects.requireNonNull(getAsString(getBlockData(lamp))).contains("lit=true"))) || ((!nieuweVersie) && (lamp.getType() == Material.matchMaterial(XMaterial.REDSTONE_LAMP.getLegacy()[1]))); + } + + private static boolean isLamp(Block lamp) { + if (nieuweVersie) { + return lamp.getType() == XMaterial.REDSTONE_LAMP.parseMaterial(); + } else { + return lamp.getType() == Material.matchMaterial(XMaterial.REDSTONE_LAMP.getLegacy()[1]) + || lamp.getType() == Material.matchMaterial(XMaterial.REDSTONE_LAMP.getLegacy()[0]); + } + } + + private static void setBlockData(Block lamp, Object blockData) { + try { + setBlockDataMethod.invoke(lamp, blockData, + Boolean.FALSE); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private static Object getBlockData(Block lamp) { + try { + return getBlockDataMethod.invoke(lamp); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + private static String getAsString(Object blockData) { + try { + return (String) getAsStringMethod.invoke(blockData, new Object[0]); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + +} \ No newline at end of file diff --git a/src/main/java/nl/sbdeveloper/themeparkplus/util/License.java b/src/main/java/nl/sbdeveloper/themeparkplus/util/License.java new file mode 100644 index 0000000..e3260cc --- /dev/null +++ b/src/main/java/nl/sbdeveloper/themeparkplus/util/License.java @@ -0,0 +1,264 @@ +package nl.sbdeveloper.themeparkplus.util; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import org.bukkit.Bukkit; +import org.bukkit.plugin.Plugin; +import org.jetbrains.annotations.Nullable; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Objects; + +/** + * License class for SBDevelopment + * + * v1.3 - Changed in 03-03-2020 + * + * @author Stijn [SBDeveloper] + * @since 23-12-2019 + */ +public class License { + /* + This file is part of ActionFoto. + Copyright (c) 2018-2020 SBDevelopment - All Rights Reserved + Unauthorized copying of this file, via any medium is strictly prohibited + Proprietary and confidential + Written by Stijn Bannink , January 2020 + */ + + private Plugin plugin; + private String license; + private String prefix; + + /** + * Construct a new license + * @param plugin The Main class [Javaplugin] + * @param prefix The prefix, like TPP or AF + * @param license The license from the config + */ + public License(Plugin plugin, String prefix, String license) { + this.prefix = prefix; + this.plugin = plugin; + this.license = license; + + startTimer(); + } + + private void startTimer() { + Bukkit.getScheduler().runTaskTimerAsynchronously(plugin, () -> { + if (!validateLicense()) { + Bukkit.getLogger().severe("[" + prefix + "] License is incorrect!"); + } + }, 0, 20 * 60 * 60); + } + + /** + * Check a license + * + * @return true/false + */ + private boolean validateLicense() { + String url = "https://sbdplugins.nl/wp-json/lmfwc/v2/licenses/" + this.license; + + @Nullable JsonObject res; + try { + res = sendGETRequestJSON(url); + } catch (IOException e) { + disable("GET_request_error"); + return false; + } + + if (res == null) { + disable("GET_request_error_2"); + return false; + } + + JsonObject dat = res.get("data").getAsJsonObject(); + + int stat = dat.get("status").getAsInt(); + if (stat == 404) { + disable("status_404_error"); + return false; + } + + if (dat.get("licenseKey").isJsonNull()) { + disable("license_null_error"); + return false; + } + + if (!dat.get("licenseKey").getAsString().split("-")[0].contains(prefix)) { + disable("prefix_error"); + return false; + } + + switch(dat.get("status").getAsString()) { + case "2": + //activate? + break; + case "3": + //it's good + break; + default: + disable("status_error"); + return false; + } + + //Not activated? Activate it! + if (dat.get("timesActivated").isJsonNull() || dat.get("timesActivated").getAsString().equalsIgnoreCase("0")) { + return activate(); + } + + if (dat.get("expiresAt").isJsonNull()) { + disable("null_error"); + return false; + } + + SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + Date date; + try { + date = format.parse(dat.get("expiresAt").getAsString()); + } catch (ParseException e) { + e.printStackTrace(); + disable("null_error"); + return false; + } + + if (!(Objects.requireNonNull(date).after(new Date()))) { + disable("expired_error"); + return false; + } + + if (!dat.get("ipcheck").getAsBoolean()) { + disable("ip_error"); + return false; + } + + if (dat.get("port").isJsonNull()) { + disable("null_error"); + return false; + } + + try { + int por = dat.get("port").getAsInt(); + if (por != Bukkit.getServer().getPort()) { + disable("port_error"); + return false; + } + } catch(NumberFormatException e) { + disable("null_error"); + return false; + } + + return true; + } + + /** + * Activate the license (private) + * + * @return true/false + */ + private boolean activate() { + String url = "https://sbdplugins.nl/wp-json/lmfwc/v2/licenses/activate/" + this.license + "?port=" + Bukkit.getServer().getPort(); + + @Nullable JsonObject res; + try { + res = sendGETRequestJSON(url); + } catch (IOException e) { + e.printStackTrace(); + disable("GET_request_error"); + return false; + } + + if (res == null) { + disable("GET_request_error_2"); + return false; + } + + JsonObject dat = res.get("data").getAsJsonObject(); + + int stat = dat.get("status").getAsInt(); + if (stat == 404) { + disable("status_404_error"); + return false; + } + + if (dat.get("licenseKey").isJsonNull()) { + disable("license_null_error"); + return false; + } + + if (!dat.get("licenseKey").getAsString().split("-")[0].contains(prefix)) { + disable("prefix_error"); + return false; + } + + switch(dat.get("status").getAsString()) { + case "2": + //activate? + break; + case "3": + //it's good + break; + default: + disable("status_error"); + return false; + } + + //Still not activated? Something is wrong... + return !dat.get("timesActivated").isJsonNull() && !dat.get("timesActivated").getAsString().equalsIgnoreCase("0"); + } + + /** + * Disable the plugin (private) + * + * @param reason The disabling reason + */ + private void disable(String reason) { + Bukkit.getScheduler().runTask(plugin, () -> { + Bukkit.getLogger().severe("[" + plugin.getName() + "] " + "Stopping plugin because licensing system check failed."); + Bukkit.getLogger().severe("[" + plugin.getName() + "] " + "Reason: " + reason); + Bukkit.getPluginManager().disablePlugin(plugin); + }); + } + + /** + * Send an GET request with JSONObject response + * + * @param uri The URL + * + * @return The JSONObject + * @throws IOException URL errors + */ + @Nullable + private JsonObject sendGETRequestJSON(String uri) throws IOException { + URL url = new URL(uri); + HttpURLConnection con = (HttpURLConnection) url.openConnection(); + con.setRequestMethod("GET"); + con.setRequestProperty("User-Agent", "Mozilla/5.0"); + String authStringEnc = "Y2tfMGEzNWEzMWE2NzExNmM3NDg2MGEwYTJhNjUxNGVjZjM4NTBmM2JmMDpjc185NmYxZGNlYjI4MWRkZDExOTBjMzQ3ZjJjYzMwMGNjZTIzYWNhODI1"; + con.setRequestProperty("Authorization", "Basic " + authStringEnc); + int code = con.getResponseCode(); + if (code == 404) { + disable("404_error"); + return null; + } + BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream())); + String inputLine; + StringBuilder response = new StringBuilder(); + while ((inputLine = in.readLine()) != null) { + response.append(inputLine); + } + in.close(); + + JsonParser parser = new JsonParser(); + return parser.parse(response.toString()).getAsJsonObject(); + } + +} \ No newline at end of file diff --git a/src/main/java/nl/sbdeveloper/themeparkplus/util/XMaterial.java b/src/main/java/nl/sbdeveloper/themeparkplus/util/XMaterial.java new file mode 100644 index 0000000..6911ff1 --- /dev/null +++ b/src/main/java/nl/sbdeveloper/themeparkplus/util/XMaterial.java @@ -0,0 +1,2030 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2018 Hex_27 + * Copyright (c) 2020 Crypto Morin + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all 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 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 SOFTWARE. + */ +package nl.sbdeveloper.themeparkplus.util; + +import com.google.common.base.Enums; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Maps; +import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.Validate; +import org.apache.commons.lang.WordUtils; +import org.bukkit.Bukkit; +import org.bukkit.Material; +import org.bukkit.inventory.ItemStack; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; + +/** + * XMaterial - Data Values/Pre-flattening
+ * Supports 1.8-1.15
+ * 1.13 and above as priority. + *

+ * This class is mainly designed to support ItemStacks. + * If you want to use it on blocks you'll have to + * use XBlock + *

+ * Pre-flattening: https://minecraft.gamepedia.com/Java_Edition_data_values/Pre-flattening + * Materials: https://hub.spigotmc.org/javadocs/spigot/org/bukkit/Material.html + * Materials (1.12): https://helpch.at/docs/1.12.2/index.html?org/bukkit/Material.html + * Material IDs: https://minecraft-ids.grahamedgecombe.com/ + * Material Source Code: https://hub.spigotmc.org/stash/projects/SPIGOT/repos/bukkit/browse/src/main/java/org/bukkit/Material.java + * XMaterial v1: https://www.spigotmc.org/threads/329630/ + * + * @author Crypto Morin + * @version 4.0.0 + * @see Material + * @see ItemStack + */ +public enum XMaterial { + ACACIA_BOAT("BOAT_ACACIA"), + ACACIA_BUTTON("WOOD_BUTTON"), + ACACIA_DOOR("ACACIA_DOOR_ITEM"), + ACACIA_FENCE, + ACACIA_FENCE_GATE, + ACACIA_LEAVES("LEAVES_2"), + ACACIA_LOG("LOG_2"), + ACACIA_PLANKS(4, "WOOD"), + ACACIA_PRESSURE_PLATE("WOOD_PLATE"), + ACACIA_SAPLING(4, "SAPLING"), + ACACIA_SIGN("SIGN"), + ACACIA_SLAB(4, "WOOD_STEP", "WOODEN_SLAB", "WOOD_DOUBLE_STEP"), + ACACIA_STAIRS, + ACACIA_TRAPDOOR("TRAP_DOOR"), + ACACIA_WALL_SIGN("SIGN_POST", "WALL_SIGN"), + ACACIA_WOOD("LOG_2"), + ACTIVATOR_RAIL, + /** + * https://minecraft.gamepedia.com/Air + * {@link Material#isAir()} + * + * @see #VOID_AIR + * @see #CAVE_AIR + */ + AIR, + ALLIUM(2, "RED_ROSE"), + ANDESITE(5, "STONE"), + ANDESITE_SLAB, + ANDESITE_STAIRS, + ANDESITE_WALL, + ANVIL, + APPLE, + ARMOR_STAND, + ARROW, + ATTACHED_MELON_STEM(7, "MELON_STEM"), + ATTACHED_PUMPKIN_STEM(7, "PUMPKIN_STEM"), + AZURE_BLUET(3, "RED_ROSE"), + BAKED_POTATO, + BAMBOO("1.14", "SUGAR_CANE", ""), + BAMBOO_SAPLING("1.14"), + BARREL("1.14", "CHEST", ""), + BARRIER, + BAT_SPAWN_EGG(65, "MONSTER_EGG"), + BEACON, + BEDROCK, + BEEF("RAW_BEEF"), + BEEHIVE("1.15"), + /** + * Beetroot is a known material in pre-1.13 + * Use XBlock when comparing block types. + */ + BEETROOT("BEETROOT_BLOCK"), + BEETROOTS("BEETROOT"), + BEETROOT_SEEDS, + BEETROOT_SOUP, + BEE_NEST("1.15"), + BEE_SPAWN_EGG("1.15"), + BELL("1.14"), + BIRCH_BOAT("BOAT_BIRCH"), + BIRCH_BUTTON("WOOD_BUTTON"), + BIRCH_DOOR("BIRCH_DOOR_ITEM"), + BIRCH_FENCE, + BIRCH_FENCE_GATE, + BIRCH_LEAVES(2, "LEAVES"), + BIRCH_LOG(2, "LOG"), + BIRCH_PLANKS(2, "WOOD"), + BIRCH_PRESSURE_PLATE("WOOD_PLATE"), + BIRCH_SAPLING(2, "SAPLING"), + BIRCH_SIGN("SIGN"), + BIRCH_SLAB(2, "WOOD_STEP", "WOODEN_SLAB", "WOOD_DOUBLE_STEP"), + BIRCH_STAIRS("BIRCH_WOOD_STAIRS"), + BIRCH_TRAPDOOR("TRAP_DOOR"), + BIRCH_WALL_SIGN("SIGN_POST", "WALL_SIGN"), + BIRCH_WOOD(2, "LOG"), + BLACK_BANNER("BANNER", "STANDING_BANNER"), + BLACK_BED(15, "BED", "BED_BLOCK"), + BLACK_CARPET(15, "CARPET"), + BLACK_CONCRETE(15, "CONCRETE"), + BLACK_CONCRETE_POWDER(15, "CONCRETE_POWDER"), + BLACK_DYE("1.14", "INK_SACK"), + BLACK_GLAZED_TERRACOTTA(15, "1.12", "HARD_CLAY", "STAINED_CLAY", "BLACK_TERRACOTTA"), + BLACK_SHULKER_BOX, + BLACK_STAINED_GLASS(15, "STAINED_GLASS"), + BLACK_STAINED_GLASS_PANE(15, "STAINED_GLASS_PANE"), + BLACK_TERRACOTTA(15, "HARD_CLAY", "STAINED_CLAY"), + BLACK_WALL_BANNER("WALL_BANNER"), + BLACK_WOOL(15, "WOOL"), + BLAST_FURNACE("1.14", "FURNACE", ""), + BLAZE_POWDER, + BLAZE_ROD, + BLAZE_SPAWN_EGG(61, "MONSTER_EGG"), + BLUE_BANNER(11, "BANNER", "STANDING_BANNER"), + BLUE_BED(4, "BED", "BED_BLOCK"), + BLUE_CARPET(11, "CARPET"), + BLUE_CONCRETE(11, "CONCRETE"), + BLUE_CONCRETE_POWDER(11, "CONCRETE_POWDER"), + BLUE_DYE(4, "INK_SACK", "LAPIS_LAZULI"), + BLUE_GLAZED_TERRACOTTA(11, "1.12", "HARD_CLAY", "STAINED_CLAY", "BLUE_TERRACOTTA"), + BLUE_ICE("1.13", "PACKED_ICE", ""), + BLUE_ORCHID(1, "RED_ROSE"), + BLUE_SHULKER_BOX, + BLUE_STAINED_GLASS(11, "STAINED_GLASS"), + BLUE_STAINED_GLASS_PANE(11, "THIN_GLASS", "STAINED_GLASS_PANE"), + BLUE_TERRACOTTA(11, "STAINED_CLAY"), + BLUE_WALL_BANNER(11, "WALL_BANNER"), + BLUE_WOOL(11, "WOOL"), + BONE, + BONE_BLOCK, + BONE_MEAL(15, "INK_SACK"), + BOOK, + BOOKSHELF, + BOW, + BOWL, + BRAIN_CORAL("1.13"), + BRAIN_CORAL_BLOCK("1.13"), + BRAIN_CORAL_FAN("1.13"), + BRAIN_CORAL_WALL_FAN, + BREAD, + BREWING_STAND("BREWING_STAND_ITEM"), + BRICK("CLAY_BRICK"), + BRICKS("BRICK"), + BRICK_SLAB(4, "STEP"), + BRICK_STAIRS, + BRICK_WALL, + BROWN_BANNER(3, "BANNER", "STANDING_BANNER"), + BROWN_BED(12, "BED", "BED_BLOCK"), + BROWN_CARPET(12, "CARPET"), + BROWN_CONCRETE(12, "CONCRETE"), + BROWN_CONCRETE_POWDER(12, "CONCRETE_POWDER"), + BROWN_DYE(3, "INK_SACK", "COCOA", "COCOA_BEANS"), + BROWN_GLAZED_TERRACOTTA(12, "1.12", "HARD_CLAY", "STAINED_CLAY", "BROWN_TERRACOTTA"), + BROWN_MUSHROOM, + BROWN_MUSHROOM_BLOCK("BROWN_MUSHROOM", "HUGE_MUSHROOM_1"), + BROWN_SHULKER_BOX, + BROWN_STAINED_GLASS(12, "STAINED_GLASS"), + BROWN_STAINED_GLASS_PANE(12, "THIN_GLASS", "STAINED_GLASS_PANE"), + BROWN_TERRACOTTA(12, "STAINED_CLAY"), + BROWN_WALL_BANNER(3, "WALL_BANNER"), + BROWN_WOOL(12, "WOOL"), + BUBBLE_COLUMN("1.13"), + BUBBLE_CORAL("1.13"), + BUBBLE_CORAL_BLOCK("1.13"), + BUBBLE_CORAL_FAN("1.13"), + BUBBLE_CORAL_WALL_FAN, + BUCKET, + CACTUS, + CAKE("CAKE_BLOCK"), + CAMPFIRE("1.14"), + CARROT("CARROT_ITEM"), + CARROTS("CARROT"), + CARROT_ON_A_STICK("CARROT_STICK"), + CARTOGRAPHY_TABLE("1.14", "CRAFTING_TABLE", ""), + CARVED_PUMPKIN(1, "1.13", "PUMPKIN", ""), + CAT_SPAWN_EGG, + CAULDRON("CAULDRON_ITEM"), + /** + * 1.13 tag is not added because it's the same thing as {@link #AIR} + * + * @see #VOID_AIR + */ + CAVE_AIR("AIR"), + CAVE_SPIDER_SPAWN_EGG(59, "MONSTER_EGG"), + CHAINMAIL_BOOTS, + CHAINMAIL_CHESTPLATE, + CHAINMAIL_HELMET, + CHAINMAIL_LEGGINGS, + CHAIN_COMMAND_BLOCK("COMMAND", "COMMAND_CHAIN"), + CHARCOAL(1, "COAL"), + CHEST("LOCKED_CHEST"), + CHEST_MINECART("STORAGE_MINECART"), + CHICKEN("RAW_CHICKEN"), + CHICKEN_SPAWN_EGG(93, "MONSTER_EGG"), + CHIPPED_ANVIL(1, "ANVIL"), + CHISELED_QUARTZ_BLOCK(1, "QUARTZ_BLOCK"), + CHISELED_RED_SANDSTONE(1, "RED_SANDSTONE"), + CHISELED_SANDSTONE(1, "SANDSTONE"), + CHISELED_STONE_BRICKS(3, "SMOOTH_BRICK"), + CHORUS_FLOWER("1.9"), + CHORUS_FRUIT("1.9"), + CHORUS_PLANT("1.9"), + CLAY, + CLAY_BALL, + CLOCK("WATCH"), + COAL, + COAL_BLOCK, + COAL_ORE, + COARSE_DIRT(1, "DIRT"), + COBBLESTONE, + COBBLESTONE_SLAB(3, "STEP"), + COBBLESTONE_STAIRS, + COBBLESTONE_WALL("COBBLE_WALL"), + COBWEB("WEB"), + COCOA("1.15"), + COCOA_BEANS(3, "INK_SACK", "COCOA"), + COD("RAW_FISH"), + COD_BUCKET("1.13", "BUCKET", "WATER_BUCKET", ""), + COD_SPAWN_EGG("1.13", "MONSTER_EGG", ""), + COMMAND_BLOCK("COMMAND"), + COMMAND_BLOCK_MINECART("COMMAND_MINECART"), + COMPARATOR("REDSTONE_COMPARATOR", "REDSTONE_COMPARATOR_ON", "REDSTONE_COMPARATOR_OFF"), + COMPASS, + COMPOSTER("1.14", "CAULDRON", ""), + CONDUIT("1.13", "BEACON"), + COOKED_BEEF, + COOKED_CHICKEN, + COOKED_COD("COOKED_FISH"), + COOKED_MUTTON, + COOKED_PORKCHOP("PORK", "GRILLED_PORK"), + COOKED_RABBIT, + COOKED_SALMON(1, "COOKED_FISH"), + COOKIE, + CORNFLOWER(4, "1.14", "BLUE_DYE", ""), + COW_SPAWN_EGG(92, "MONSTER_EGG"), + CRACKED_STONE_BRICKS(2, "SMOOTH_BRICK"), + CRAFTING_TABLE("WORKBENCH"), + CREEPER_BANNER_PATTERN, + CREEPER_HEAD(4, "SKULL", "SKULL_ITEM"), + CREEPER_SPAWN_EGG(50, "MONSTER_EGG"), + CREEPER_WALL_HEAD(4, "SKULL", "SKULL_ITEM"), + CROSSBOW, + CUT_RED_SANDSTONE("1.13"), + CUT_RED_SANDSTONE_SLAB("STONE_SLAB2"), + CUT_SANDSTONE("1.13"), + CUT_SANDSTONE_SLAB("STEP"), + CYAN_BANNER(6, "BANNER", "STANDING_BANNER"), + CYAN_BED(9, "BED", "BED_BLOCK"), + CYAN_CARPET(9, "CARPET"), + CYAN_CONCRETE(9, "CONCRETE"), + CYAN_CONCRETE_POWDER(9, "CONCRETE_POWDER"), + CYAN_DYE(6, "INK_SACK"), + CYAN_GLAZED_TERRACOTTA(9, "1.12", "HARD_CLAY", "STAINED_CLAY", "CYAN_TERRACOTTA"), + CYAN_SHULKER_BOX, + CYAN_STAINED_GLASS(9, "STAINED_GLASS"), + CYAN_STAINED_GLASS_PANE(9, "STAINED_GLASS_PANE"), + CYAN_TERRACOTTA(9, "HARD_CLAY", "STAINED_CLAY"), + CYAN_WALL_BANNER(6, "WALL_BANNER"), + CYAN_WOOL(9, "WOOL"), + DAMAGED_ANVIL(2, "ANVIL"), + DANDELION("YELLOW_FLOWER"), + DARK_OAK_BOAT("BOAT_DARK_OAK"), + DARK_OAK_BUTTON("WOOD_BUTTON"), + DARK_OAK_DOOR("DARK_OAK_DOOR_ITEM"), + DARK_OAK_FENCE, + DARK_OAK_FENCE_GATE, + DARK_OAK_LEAVES(1, "LEAVES", "LEAVES_2"), + DARK_OAK_LOG(1, "LOG", "LOG_2"), + DARK_OAK_PLANKS(5, "WOOD"), + DARK_OAK_PRESSURE_PLATE("WOOD_PLATE"), + DARK_OAK_SAPLING(5, "SAPLING"), + DARK_OAK_SIGN("SIGN"), + DARK_OAK_SLAB("WOOD_STEP", "WOODEN_SLAB", "WOOD_DOUBLE_STEP"), + DARK_OAK_STAIRS, + DARK_OAK_TRAPDOOR("TRAP_DOOR"), + DARK_OAK_WALL_SIGN("SIGN_POST", "WALL_SIGN"), + DARK_OAK_WOOD(1, "LOG", "LOG_2"), + DARK_PRISMARINE(1, "PRISMARINE"), + DARK_PRISMARINE_SLAB("1.13"), + DARK_PRISMARINE_STAIRS("1.13"), + DAYLIGHT_DETECTOR("DAYLIGHT_DETECTOR_INVERTED"), + DEAD_BRAIN_CORAL("1.13"), + DEAD_BRAIN_CORAL_BLOCK("1.13"), + DEAD_BRAIN_CORAL_FAN("1.13"), + DEAD_BRAIN_CORAL_WALL_FAN("1.13"), + DEAD_BUBBLE_CORAL("1.13"), + DEAD_BUBBLE_CORAL_BLOCK("1.13"), + DEAD_BUBBLE_CORAL_FAN("1.13"), + DEAD_BUBBLE_CORAL_WALL_FAN("1.13"), + DEAD_BUSH, + DEAD_FIRE_CORAL("1.13"), + DEAD_FIRE_CORAL_BLOCK("1.13"), + DEAD_FIRE_CORAL_FAN("1.13"), + DEAD_FIRE_CORAL_WALL_FAN("1.13"), + DEAD_HORN_CORAL("1.13"), + DEAD_HORN_CORAL_BLOCK("1.13"), + DEAD_HORN_CORAL_FAN("1.13"), + DEAD_HORN_CORAL_WALL_FAN("1.13"), + DEAD_TUBE_CORAL("1.13"), + DEAD_TUBE_CORAL_BLOCK("1.13"), + DEAD_TUBE_CORAL_FAN("1.13"), + DEAD_TUBE_CORAL_WALL_FAN("1.13"), + DEBUG_STICK("1.13", "STICK", ""), + DETECTOR_RAIL, + DIAMOND, + DIAMOND_AXE, + DIAMOND_BLOCK, + DIAMOND_BOOTS, + DIAMOND_CHESTPLATE, + DIAMOND_HELMET, + DIAMOND_HOE, + DIAMOND_HORSE_ARMOR("DIAMOND_BARDING"), + DIAMOND_LEGGINGS, + DIAMOND_ORE, + DIAMOND_PICKAXE, + DIAMOND_SHOVEL("DIAMOND_SPADE"), + DIAMOND_SWORD, + DIORITE(3, "STONE"), + DIORITE_SLAB, + DIORITE_STAIRS, + DIORITE_WALL, + DIRT, + DISPENSER, + DOLPHIN_SPAWN_EGG("1.13", "MONSTER_EGG", ""), + DONKEY_SPAWN_EGG(32, "MONSTER_EGG"), + DRAGON_BREATH("DRAGONS_BREATH"), + DRAGON_EGG, + DRAGON_HEAD(5, "1.9", "SKULL", "SKULL_ITEM"), + DRAGON_WALL_HEAD(5, "SKULL", "SKULL_ITEM"), + DRIED_KELP("1.13"), + DRIED_KELP_BLOCK("1.13"), + DROPPER, + DROWNED_SPAWN_EGG("1.13", "MONSTER_EGG", ""), + EGG, + ELDER_GUARDIAN_SPAWN_EGG(4, "MONSTER_EGG"), + ELYTRA, + EMERALD, + EMERALD_BLOCK, + EMERALD_ORE, + ENCHANTED_BOOK, + ENCHANTED_GOLDEN_APPLE(1, "GOLDEN_APPLE"), + ENCHANTING_TABLE("ENCHANTMENT_TABLE"), + ENDERMAN_SPAWN_EGG(58, "MONSTER_EGG"), + ENDERMITE_SPAWN_EGG(67, "MONSTER_EGG"), + ENDER_CHEST, + ENDER_EYE("EYE_OF_ENDER"), + ENDER_PEARL, + END_CRYSTAL, + END_GATEWAY("1.9"), + END_PORTAL("ENDER_PORTAL"), + END_PORTAL_FRAME("ENDER_PORTAL_FRAME"), + END_ROD("1.9", "BLAZE_ROD", ""), + END_STONE("ENDER_STONE"), + END_STONE_BRICKS("END_BRICKS"), + END_STONE_BRICK_SLAB(4, "STEP"), + END_STONE_BRICK_STAIRS("SMOOTH_STAIRS"), + END_STONE_BRICK_WALL, + EVOKER_SPAWN_EGG(34, "MONSTER_EGG"), + EXPERIENCE_BOTTLE("EXP_BOTTLE"), + FARMLAND("SOIL"), + FEATHER, + FERMENTED_SPIDER_EYE, + FERN(2, "LONG_GRASS"), + FILLED_MAP("MAP"), + FIRE, + FIREWORK_ROCKET("FIREWORK"), + FIREWORK_STAR("FIREWORK_CHARGE"), + FIRE_CHARGE("FIREBALL"), + FIRE_CORAL("1.13"), + FIRE_CORAL_BLOCK("1.13"), + FIRE_CORAL_FAN("1.13"), + FIRE_CORAL_WALL_FAN, + FISHING_ROD, + FLETCHING_TABLE("1.14", "CRAFTING_TABLE", ""), + FLINT, + FLINT_AND_STEEL, + FLOWER_BANNER_PATTERN, + FLOWER_POT("FLOWER_POT_ITEM"), + FOX_SPAWN_EGG("1.14"), + /** + * This special material cannot be obtained as an item. + */ + FROSTED_ICE("1.9", "PACKED_ICE", ""), + FURNACE("BURNING_FURNACE"), + FURNACE_MINECART("POWERED_MINECART"), + GHAST_SPAWN_EGG(56, "MONSTER_EGG"), + GHAST_TEAR, + GLASS, + GLASS_BOTTLE, + GLASS_PANE("THIN_GLASS"), + GLISTERING_MELON_SLICE("SPECKLED_MELON"), + GLOBE_BANNER_PATTERN, + GLOWSTONE, + GLOWSTONE_DUST, + GOLDEN_APPLE, + GOLDEN_AXE("GOLD_AXE"), + GOLDEN_BOOTS("GOLD_BOOTS"), + GOLDEN_CARROT, + GOLDEN_CHESTPLATE("GOLD_CHESTPLATE"), + GOLDEN_HELMET("GOLD_HELMET"), + GOLDEN_HOE("GOLD_HOE"), + GOLDEN_HORSE_ARMOR("GOLD_BARDING"), + GOLDEN_LEGGINGS("GOLD_LEGGINGS"), + GOLDEN_PICKAXE("GOLD_PICKAXE"), + GOLDEN_SHOVEL("GOLD_SPADE"), + GOLDEN_SWORD("GOLD_SWORD"), + GOLD_BLOCK, + GOLD_INGOT, + GOLD_NUGGET, + GOLD_ORE, + GRANITE(1, "STONE"), + GRANITE_SLAB, + GRANITE_STAIRS, + GRANITE_WALL, + GRASS, + GRASS_BLOCK("GRASS"), + GRASS_PATH, + GRAVEL, + GRAY_BANNER(8, "BANNER", "STANDING_BANNER"), + GRAY_BED(7, "BED", "BED_BLOCK"), + GRAY_CARPET(7, "CARPET"), + GRAY_CONCRETE(7, "CONCRETE"), + GRAY_CONCRETE_POWDER(7, "CONCRETE_POWDER"), + GRAY_DYE(8, "INK_SACK"), + GRAY_GLAZED_TERRACOTTA(7, "1.12", "HARD_CLAY", "STAINED_CLAY", "GRAY_TERRACOTTA"), + GRAY_SHULKER_BOX, + GRAY_STAINED_GLASS(7, "STAINED_GLASS"), + GRAY_STAINED_GLASS_PANE(7, "THIN_GLASS", "STAINED_GLASS_PANE"), + GRAY_TERRACOTTA(7, "HARD_CLAY", "STAINED_CLAY"), + GRAY_WALL_BANNER(8, "WALL_BANNER"), + GRAY_WOOL(7, "WOOL"), + GREEN_BANNER(2, "BANNER", "STANDING_BANNER"), + GREEN_BED(13, "BED", "BED_BLOCK"), + GREEN_CARPET(13, "CARPET"), + GREEN_CONCRETE(13, "CONCRETE"), + GREEN_CONCRETE_POWDER(13, "CONCRETE_POWDER"), + GREEN_DYE(2, "INK_SACK", "CACTUS_GREEN"), + GREEN_GLAZED_TERRACOTTA(13, "1.12", "HARD_CLAY", "STAINED_CLAY", "GREEN_TERRACOTTA"), + GREEN_SHULKER_BOX, + GREEN_STAINED_GLASS(13, "STAINED_GLASS"), + GREEN_STAINED_GLASS_PANE(13, "THIN_GLASS", "STAINED_GLASS_PANE"), + GREEN_TERRACOTTA(13, "HARD_CLAY", "STAINED_CLAY"), + GREEN_WALL_BANNER(2, "WALL_BANNER"), + GREEN_WOOL(13, "WOOL"), + GRINDSTONE("1.14", "ANVIL", ""), + GUARDIAN_SPAWN_EGG(68, "MONSTER_EGG"), + GUNPOWDER("SULPHUR"), + HAY_BLOCK, + HEART_OF_THE_SEA("1.13"), + HEAVY_WEIGHTED_PRESSURE_PLATE("IRON_PLATE"), + HONEYCOMB("1.15"), + HONEYCOMB_BLOCK("1.15"), + HONEY_BLOCK("1.15", "SLIME_BLOCK", ""), + HONEY_BOTTLE("1.15", "GLASS_BOTTLE", ""), + HOPPER, + HOPPER_MINECART, + HORN_CORAL("1.13"), + HORN_CORAL_BLOCK("1.13"), + HORN_CORAL_FAN("1.13"), + HORN_CORAL_WALL_FAN, + HORSE_SPAWN_EGG(100, "MONSTER_EGG"), + HUSK_SPAWN_EGG(23, "MONSTER_EGG"), + ICE, + INFESTED_CHISELED_STONE_BRICKS(5, "MONSTER_EGGS", "SMOOTH_BRICK"), + INFESTED_COBBLESTONE(1, "MONSTER_EGGS"), + INFESTED_CRACKED_STONE_BRICKS(4, "MONSTER_EGGS", "SMOOTH_BRICK"), + INFESTED_MOSSY_STONE_BRICKS(3, "MONSTER_EGGS"), + INFESTED_STONE("MONSTER_EGGS"), + INFESTED_STONE_BRICKS(2, "MONSTER_EGGS", "SMOOTH_BRICK"), + INK_SAC("INK_SACK"), + IRON_AXE, + IRON_BARS("IRON_FENCE"), + IRON_BLOCK, + IRON_BOOTS, + IRON_CHESTPLATE, + IRON_DOOR("IRON_DOOR_BLOCK"), + IRON_HELMET, + IRON_HOE, + IRON_HORSE_ARMOR("IRON_BARDING"), + IRON_INGOT, + IRON_LEGGINGS, + IRON_NUGGET, + IRON_ORE, + IRON_PICKAXE, + IRON_SHOVEL("IRON_SPADE"), + IRON_SWORD, + IRON_TRAPDOOR, + ITEM_FRAME, + JACK_O_LANTERN, + JIGSAW("1.14", "COMMAND_BLOCK", "STRUCTURE_BLOCK", ""), + JUKEBOX, + JUNGLE_BOAT("BOAT_JUNGLE"), + JUNGLE_BUTTON("WOOD_BUTTON"), + JUNGLE_DOOR("JUNGLE_DOOR_ITEM"), + JUNGLE_FENCE, + JUNGLE_FENCE_GATE, + JUNGLE_LEAVES(3, "LEAVES"), + JUNGLE_LOG(3, "LOG"), + JUNGLE_PLANKS(3, "WOOD"), + JUNGLE_PRESSURE_PLATE("WOOD_PLATE"), + JUNGLE_SAPLING(3, "SAPLING"), + JUNGLE_SIGN("SIGN"), + JUNGLE_SLAB(3, "WOOD_STEP", "WOODEN_SLAB", "WOOD_DOUBLE_STEP"), + JUNGLE_STAIRS("JUNGLE_WOOD_STAIRS"), + JUNGLE_TRAPDOOR("TRAP_DOOR"), + JUNGLE_WALL_SIGN("SIGN_POST", "WALL_SIGN"), + JUNGLE_WOOD(3, "LOG"), + KELP("1.13"), + KELP_PLANT("1.13"), + KNOWLEDGE_BOOK("1.12", "BOOK"), + LADDER, + LANTERN("1.14", "SEA_LANTERN", ""), + LAPIS_BLOCK, + LAPIS_LAZULI(4, "INK_SACK"), + LAPIS_ORE, + LARGE_FERN(3, "DOUBLE_PLANT"), + LAVA("STATIONARY_LAVA"), + LAVA_BUCKET, + LEAD("LEASH"), + LEATHER, + LEATHER_BOOTS, + LEATHER_CHESTPLATE, + LEATHER_HELMET, + LEATHER_HORSE_ARMOR("1.14", "IRON_HORSE_ARMOR", ""), + LEATHER_LEGGINGS, + LECTERN("1.14", "BOOKSHELF", ""), + LEVER, + LIGHT_BLUE_BANNER(3, "BANNER", "STANDING_BANNER"), + LIGHT_BLUE_BED(3, "BED", "BED_BLOCK"), + LIGHT_BLUE_CARPET(3, "CARPET"), + LIGHT_BLUE_CONCRETE(3, "CONCRETE"), + LIGHT_BLUE_CONCRETE_POWDER(3, "CONCRETE_POWDER"), + LIGHT_BLUE_DYE(12, "INK_SACK"), + LIGHT_BLUE_GLAZED_TERRACOTTA(3, "1.12", "HARD_CLAY", "STAINED_CLAY", "LIGHT_BLUE_TERRACOTTA"), + LIGHT_BLUE_SHULKER_BOX, + LIGHT_BLUE_STAINED_GLASS(3, "STAINED_GLASS"), + LIGHT_BLUE_STAINED_GLASS_PANE(3, "THIN_GLASS", "STAINED_GLASS_PANE"), + LIGHT_BLUE_TERRACOTTA(3, "STAINED_CLAY"), + LIGHT_BLUE_WALL_BANNER(12, "WALL_BANNER", "BANNER", "STANDING_BANNER"), + LIGHT_BLUE_WOOL(3, "WOOL"), + LIGHT_GRAY_BANNER(7, "BANNER", "STANDING_BANNER"), + LIGHT_GRAY_BED(8, "BED", "BED_BLOCK"), + LIGHT_GRAY_CARPET(8, "CARPET"), + LIGHT_GRAY_CONCRETE(8, "CONCRETE"), + LIGHT_GRAY_CONCRETE_POWDER(8, "CONCRETE_POWDER"), + LIGHT_GRAY_DYE(7, "INK_SACK"), + /** + * Renamed to SILVER_GLAZED_TERRACOTTA in 1.13 + * Renamed to LIGHT_GRAY_GLAZED_TERRACOTTA in 1.14 + */ + LIGHT_GRAY_GLAZED_TERRACOTTA(8, "1.12", "HARD_CLAY", "STAINED_CLAY", "LIGHT_GRAY_TERRACOTTA", "SILVER_GLAZED_TERRACOTTA"), + LIGHT_GRAY_SHULKER_BOX("SILVER_SHULKER_BOX"), + LIGHT_GRAY_STAINED_GLASS(8, "STAINED_GLASS"), + LIGHT_GRAY_STAINED_GLASS_PANE(8, "THIN_GLASS", "STAINED_GLASS_PANE"), + LIGHT_GRAY_TERRACOTTA(8, "HARD_CLAY", "STAINED_CLAY"), + LIGHT_GRAY_WALL_BANNER(7, "WALL_BANNER"), + LIGHT_GRAY_WOOL(8, "WOOL"), + LIGHT_WEIGHTED_PRESSURE_PLATE("GOLD_PLATE"), + LILAC(1, "DOUBLE_PLANT"), + LILY_OF_THE_VALLEY(15, "1.14", "WHITE_DYE", ""), + LILY_PAD("WATER_LILY"), + LIME_BANNER(10, "BANNER", "STANDING_BANNER"), + LIME_BED(5, "BED", "BED_BLOCK"), + LIME_CARPET(5, "CARPET"), + LIME_CONCRETE(5, "CONCRETE"), + LIME_CONCRETE_POWDER(5, "CONCRETE_POWDER"), + LIME_DYE(10, "INK_SACK"), + LIME_GLAZED_TERRACOTTA(5, "1.12", "HARD_CLAY", "STAINED_CLAY", "LIME_TERRACOTTA"), + LIME_SHULKER_BOX, + LIME_STAINED_GLASS(5, "STAINED_GLASS"), + LIME_STAINED_GLASS_PANE(5, "STAINED_GLASS_PANE"), + LIME_TERRACOTTA(5, "HARD_CLAY", "STAINED_CLAY"), + LIME_WALL_BANNER(10, "WALL_BANNER"), + LIME_WOOL(5, "WOOL"), + LINGERING_POTION, + LLAMA_SPAWN_EGG(103, "MONSTER_EGG"), + LOOM("1.14"), + MAGENTA_BANNER(13, "BANNER", "STANDING_BANNER"), + MAGENTA_BED(2, "BED", "BED_BLOCK"), + MAGENTA_CARPET(2, "CARPET"), + MAGENTA_CONCRETE(2, "CONCRETE"), + MAGENTA_CONCRETE_POWDER(2, "CONCRETE_POWDER"), + MAGENTA_DYE(13, "INK_SACK"), + MAGENTA_GLAZED_TERRACOTTA(2, "1.12", "HARD_CLAY", "STAINED_CLAY", "MAGENTA_TERRACOTTA"), + MAGENTA_SHULKER_BOX, + MAGENTA_STAINED_GLASS(2, "STAINED_GLASS"), + MAGENTA_STAINED_GLASS_PANE(2, "THIN_GLASS", "STAINED_GLASS_PANE"), + MAGENTA_TERRACOTTA(2, "HARD_CLAY", "STAINED_CLAY"), + MAGENTA_WALL_BANNER(13, "WALL_BANNER"), + MAGENTA_WOOL(2, "WOOL"), + MAGMA_BLOCK("1.10", "MAGMA"), + MAGMA_CREAM, + MAGMA_CUBE_SPAWN_EGG(62, "MONSTER_EGG"), + MAP("EMPTY_MAP"), + MELON("MELON_BLOCK"), + MELON_SEEDS, + MELON_SLICE("MELON"), + MELON_STEM, + MILK_BUCKET, + MINECART, + MOJANG_BANNER_PATTERN, + MOOSHROOM_SPAWN_EGG(96, "MONSTER_EGG"), + MOSSY_COBBLESTONE, + MOSSY_COBBLESTONE_SLAB(3, "STEP"), + MOSSY_COBBLESTONE_STAIRS, + MOSSY_COBBLESTONE_WALL(1, "COBBLE_WALL", "COBBLESTONE_WALL"), + MOSSY_STONE_BRICKS(1, "SMOOTH_BRICK"), + MOSSY_STONE_BRICK_SLAB(4, "STEP"), + MOSSY_STONE_BRICK_STAIRS("SMOOTH_STAIRS"), + MOSSY_STONE_BRICK_WALL, + MOVING_PISTON("PISTON_BASE", "PISTON_MOVING_PIECE"), + MULE_SPAWN_EGG(32, "MONSTER_EGG"), + MUSHROOM_STEM("BROWN_MUSHROOM"), + MUSHROOM_STEW("MUSHROOM_SOUP"), + MUSIC_DISC_11("GOLD_RECORD"), + MUSIC_DISC_13("GREEN_RECORD"), + MUSIC_DISC_BLOCKS("RECORD_3"), + MUSIC_DISC_CAT("RECORD_4"), + MUSIC_DISC_CHIRP("RECORD_5"), + MUSIC_DISC_FAR("RECORD_6"), + MUSIC_DISC_MALL("RECORD_7"), + MUSIC_DISC_MELLOHI("RECORD_8"), + MUSIC_DISC_STAL("RECORD_9"), + MUSIC_DISC_STRAD("RECORD_10"), + MUSIC_DISC_WAIT("RECORD_11"), + MUSIC_DISC_WARD("RECORD_12"), + MUTTON, + MYCELIUM("MYCEL"), + NAME_TAG, + NAUTILUS_SHELL("1.13"), + NETHERRACK, + NETHER_BRICK("NETHER_BRICK_ITEM"), + NETHER_BRICKS("NETHER_BRICK"), + NETHER_BRICK_FENCE("NETHER_FENCE"), + NETHER_BRICK_SLAB(4, "STEP"), + NETHER_BRICK_STAIRS, + NETHER_BRICK_WALL, + NETHER_PORTAL("PORTAL"), + NETHER_QUARTZ_ORE("QUARTZ_ORE"), + NETHER_STAR, + /** + * Just like mentioned in https://minecraft.gamepedia.com/Nether_Wart + * Nether wart is also known as nether stalk in the code. + * NETHER_STALK is the planted state of nether warts. + */ + NETHER_WART("NETHER_WARTS", "NETHER_STALK"), + NETHER_WART_BLOCK, + NOTE_BLOCK, + OAK_BOAT("BOAT"), + OAK_BUTTON("WOOD_BUTTON"), + OAK_DOOR("WOOD_DOOR", "WOODEN_DOOR"), + OAK_FENCE("FENCE"), + OAK_FENCE_GATE("FENCE_GATE"), + OAK_LEAVES("LEAVES"), + OAK_LOG("LOG"), + OAK_PLANKS("WOOD"), + OAK_PRESSURE_PLATE("WOOD_PLATE"), + OAK_SAPLING("SAPLING"), + OAK_SIGN("SIGN"), + OAK_SLAB("WOOD_STEP", "WOODEN_SLAB", "WOOD_DOUBLE_STEP"), + OAK_STAIRS("WOOD_STAIRS"), + OAK_TRAPDOOR("TRAP_DOOR"), + OAK_WALL_SIGN("SIGN_POST", "WALL_SIGN"), + OAK_WOOD("LOG"), + OBSERVER, + OBSIDIAN, + OCELOT_SPAWN_EGG(98, "MONSTER_EGG"), + ORANGE_BANNER(14, "BANNER", "STANDING_BANNER"), + ORANGE_BED(1, "BED", "BED_BLOCK"), + ORANGE_CARPET(1, "CARPET"), + ORANGE_CONCRETE(1, "CONCRETE"), + ORANGE_CONCRETE_POWDER(1, "CONCRETE_POWDER"), + ORANGE_DYE(14, "INK_SACK"), + ORANGE_GLAZED_TERRACOTTA(1, "1.12", "HARD_CLAY", "STAINED_CLAY", "ORANGE_TERRACOTTA"), + ORANGE_SHULKER_BOX, + ORANGE_STAINED_GLASS(1, "STAINED_GLASS"), + ORANGE_STAINED_GLASS_PANE(1, "STAINED_GLASS_PANE"), + ORANGE_TERRACOTTA(1, "HARD_CLAY", "STAINED_CLAY"), + ORANGE_TULIP(5, "RED_ROSE"), + ORANGE_WALL_BANNER(14, "WALL_BANNER"), + ORANGE_WOOL(1, "WOOL"), + OXEYE_DAISY(8, "RED_ROSE"), + PACKED_ICE, + PAINTING, + PANDA_SPAWN_EGG("1.14"), + PAPER, + PARROT_SPAWN_EGG(105, "MONSTER_EGG"), + PEONY(5, "DOUBLE_PLANT"), + PETRIFIED_OAK_SLAB("WOOD_STEP"), + PHANTOM_MEMBRANE("1.13"), + PHANTOM_SPAWN_EGG("1.13", "MONSTER_EGG", ""), + PIG_SPAWN_EGG(90, "MONSTER_EGG"), + PILLAGER_SPAWN_EGG("1.14"), + PINK_BANNER(9, "BANNER", "STANDING_BANNER"), + PINK_BED(6, "BED", "BED_BLOCK"), + PINK_CARPET(6, "CARPET"), + PINK_CONCRETE(6, "CONCRETE"), + PINK_CONCRETE_POWDER(6, "CONCRETE_POWDER"), + PINK_DYE(9, "INK_SACK"), + PINK_GLAZED_TERRACOTTA(6, "1.12", "HARD_CLAY", "STAINED_CLAY", "PINK_TERRACOTTA"), + PINK_SHULKER_BOX, + PINK_STAINED_GLASS(6, "STAINED_GLASS"), + PINK_STAINED_GLASS_PANE(6, "THIN_GLASS", "STAINED_GLASS_PANE"), + PINK_TERRACOTTA(6, "HARD_CLAY", "STAINED_CLAY"), + PINK_TULIP(7, "RED_ROSE"), + PINK_WALL_BANNER(14, "WALL_BANNER"), + PINK_WOOL(6, "WOOL"), + PISTON("PISTON_BASE"), + PISTON_HEAD("PISTON_EXTENSION"), + PLAYER_HEAD(3, "SKULL", "SKULL_ITEM"), + PLAYER_WALL_HEAD(3, "SKULL", "SKULL_ITEM"), + PODZOL(2, "DIRT"), + POISONOUS_POTATO, + POLAR_BEAR_SPAWN_EGG(102, "MONSTER_EGG"), + POLISHED_ANDESITE(6, "STONE"), + POLISHED_ANDESITE_SLAB, + POLISHED_ANDESITE_STAIRS, + POLISHED_DIORITE(4, "STONE"), + POLISHED_DIORITE_SLAB, + POLISHED_DIORITE_STAIRS, + POLISHED_GRANITE(2, "STONE"), + POLISHED_GRANITE_SLAB, + POLISHED_GRANITE_STAIRS, + POPPED_CHORUS_FRUIT("CHORUS_FRUIT_POPPED"), + POPPY("RED_ROSE"), + PORKCHOP("PORK"), + POTATO("POTATO_ITEM"), + POTATOES("POTATO"), + POTION, + POTTED_ACACIA_SAPLING(4, "SAPLING", "FLOWER_POT"), + POTTED_ALLIUM(2, "RED_ROSE", "FLOWER_POT"), + POTTED_AZURE_BLUET(3, "RED_ROSE", "FLOWER_POT"), + POTTED_BAMBOO, + POTTED_BIRCH_SAPLING(2, "SAPLING", "FLOWER_POT"), + POTTED_BLUE_ORCHID(1, "RED_ROSE", "FLOWER_POT"), + POTTED_BROWN_MUSHROOM("FLOWER_POT"), + POTTED_CACTUS("FLOWER_POT"), + POTTED_CORNFLOWER, + POTTED_DANDELION("YELLOW_FLOWER", "FLOWER_POT"), + POTTED_DARK_OAK_SAPLING(5, "SAPLING", "FLOWER_POT"), + POTTED_DEAD_BUSH("FLOWER_POT"), + POTTED_FERN(2, "LONG_GRASS", "FLOWER_POT"), + POTTED_JUNGLE_SAPLING(3, "SAPLING", "FLOWER_POT"), + POTTED_LILY_OF_THE_VALLEY, + POTTED_OAK_SAPLING("SAPLING", "FLOWER_POT"), + POTTED_ORANGE_TULIP(5, "RED_ROSE", "FLOWER_POT"), + POTTED_OXEYE_DAISY(8, "RED_ROSE", "FLOWER_POT"), + POTTED_PINK_TULIP(7, "RED_ROSE", "FLOWER_POT"), + POTTED_POPPY("RED_ROSE", "FLOWER_POT"), + POTTED_RED_MUSHROOM("FLOWER_POT"), + POTTED_RED_TULIP(4, "RED_ROSE", "FLOWER_POT"), + POTTED_SPRUCE_SAPLING(1, "SAPLING", "FLOWER_POT"), + POTTED_WHITE_TULIP(6, "RED_ROSE", "FLOWER_POT"), + POTTED_WITHER_ROSE, + POWERED_RAIL, + PRISMARINE, + PRISMARINE_BRICKS(2, "PRISMARINE"), + PRISMARINE_BRICK_SLAB(4, "STEP"), + PRISMARINE_BRICK_STAIRS("1.13"), + PRISMARINE_CRYSTALS, + PRISMARINE_SHARD, + PRISMARINE_SLAB("1.13"), + PRISMARINE_STAIRS("1.13"), + PRISMARINE_WALL, + PUFFERFISH(3, "RAW_FISH"), + PUFFERFISH_BUCKET("1.13", "BUCKET", "WATER_BUCKET", ""), + PUFFERFISH_SPAWN_EGG("1.13", "MONSTER_EGG", ""), + PUMPKIN, + PUMPKIN_PIE, + PUMPKIN_SEEDS, + PUMPKIN_STEM, + PURPLE_BANNER(5, "BANNER", "STANDING_BANNER"), + PURPLE_BED(10, "BED", "BED_BLOCK"), + PURPLE_CARPET(10, "CARPET"), + PURPLE_CONCRETE(10, "CONCRETE"), + PURPLE_CONCRETE_POWDER(10, "CONCRETE_POWDER"), + PURPLE_DYE(5, "INK_SACK"), + PURPLE_GLAZED_TERRACOTTA(10, "1.12", "HARD_CLAY", "STAINED_CLAY", "PURPLE_TERRACOTTA"), + PURPLE_SHULKER_BOX, + PURPLE_STAINED_GLASS(10, "STAINED_GLASS"), + PURPLE_STAINED_GLASS_PANE(10, "THIN_GLASS", "STAINED_GLASS_PANE"), + PURPLE_TERRACOTTA(10, "HARD_CLAY", "STAINED_CLAY"), + PURPLE_WALL_BANNER(5, "WALL_BANNER"), + PURPLE_WOOL(10, "WOOL"), + PURPUR_BLOCK, + PURPUR_PILLAR, + PURPUR_SLAB("PURPUR_DOUBLE_SLAB"), + PURPUR_STAIRS, + QUARTZ, + QUARTZ_BLOCK, + QUARTZ_PILLAR(2, "QUARTZ_BLOCK"), + QUARTZ_SLAB(7, "STEP"), + QUARTZ_STAIRS, + RABBIT, + RABBIT_FOOT, + RABBIT_HIDE, + RABBIT_SPAWN_EGG(101, "MONSTER_EGG"), + RABBIT_STEW, + RAIL("RAILS"), + RAVAGER_SPAWN_EGG("1.14"), + REDSTONE, + REDSTONE_BLOCK, + REDSTONE_LAMP("REDSTONE_LAMP_OFF", "REDSTONE_LAMP_ON"), + REDSTONE_ORE("GLOWING_REDSTONE_ORE"), + REDSTONE_TORCH("REDSTONE_TORCH_ON", "REDSTONE_TORCH_OFF"), + REDSTONE_WALL_TORCH(1, "REDSTONE_TORCH_ON", "REDSTONE_TORCH_OFF"), + REDSTONE_WIRE, + RED_BANNER(1, "BANNER", "STANDING_BANNER"), + RED_BED(14, "BED", "BED_BLOCK"), + RED_CARPET(14, "CARPET"), + RED_CONCRETE(14, "CONCRETE"), + RED_CONCRETE_POWDER(14, "CONCRETE_POWDER"), + RED_DYE(1, "ROSE_RED"), + RED_GLAZED_TERRACOTTA(14, "1.12", "HARD_CLAY", "STAINED_CLAY", "RED_TERRACOTTA"), + RED_MUSHROOM, + RED_MUSHROOM_BLOCK("RED_MUSHROOM", "HUGE_MUSHROOM_2"), + RED_NETHER_BRICKS("RED_NETHER_BRICK"), + RED_NETHER_BRICK_SLAB(4, "STEP"), + RED_NETHER_BRICK_STAIRS, + RED_NETHER_BRICK_WALL, + RED_SAND(1, "SAND"), + RED_SANDSTONE, + RED_SANDSTONE_SLAB("STONE_SLAB2", "DOUBLE_STONE_SLAB2"), + RED_SANDSTONE_STAIRS, + RED_SANDSTONE_WALL, + RED_SHULKER_BOX, + RED_STAINED_GLASS(14, "STAINED_GLASS"), + RED_STAINED_GLASS_PANE(14, "THIN_GLASS", "STAINED_GLASS_PANE"), + RED_TERRACOTTA(14, "HARD_CLAY", "STAINED_CLAY"), + RED_TULIP(4, "RED_ROSE"), + RED_WALL_BANNER(1, "WALL_BANNER"), + RED_WOOL(14, "WOOL"), + REPEATER("DIODE", "DIODE_BLOCK_ON", "DIODE_BLOCK_OFF"), + REPEATING_COMMAND_BLOCK("COMMAND", "COMMAND_REPEATING"), + ROSE_BUSH(4, "DOUBLE_PLANT"), + ROTTEN_FLESH, + SADDLE, + SALMON(1, "RAW_FISH"), + SALMON_BUCKET("1.13", "BUCKET", "WATER_BUCKET", ""), + SALMON_SPAWN_EGG("1.13", "MONSTER_EGG", ""), + SAND, + SANDSTONE, + SANDSTONE_SLAB(1, "STEP", "STONE_SLAB", "DOUBLE_STEP"), + SANDSTONE_STAIRS, + SANDSTONE_WALL, + SCAFFOLDING("1.14", "SLIME_BLOCK", ""), + SCUTE("1.13"), + SEAGRASS("1.13", "GRASS", ""), + SEA_LANTERN, + SEA_PICKLE("1.13"), + SHEARS, + SHEEP_SPAWN_EGG(91, "MONSTER_EGG"), + SHIELD, + SHULKER_BOX("PURPLE_SHULKER_BOX"), + SHULKER_SHELL, + SHULKER_SPAWN_EGG(69, "MONSTER_EGG"), + SILVERFISH_SPAWN_EGG(60, "MONSTER_EGG"), + SKELETON_HORSE_SPAWN_EGG(28, "MONSTER_EGG"), + SKELETON_SKULL("SKULL", "SKULL_ITEM"), + SKELETON_SPAWN_EGG(51, "MONSTER_EGG"), + SKELETON_WALL_SKULL("SKULL", "SKULL_ITEM"), + SKULL_BANNER_PATTERN, + SLIME_BALL, + SLIME_BLOCK, + SLIME_SPAWN_EGG(55, "MONSTER_EGG"), + SMITHING_TABLE, + SMOKER("1.14", "FURNACE", ""), + SMOOTH_QUARTZ("1.13", "QUARTZ", ""), + SMOOTH_QUARTZ_SLAB(7, "STEP"), + SMOOTH_QUARTZ_STAIRS, + SMOOTH_RED_SANDSTONE(2, "RED_SANDSTONE"), + SMOOTH_RED_SANDSTONE_SLAB("STONE_SLAB2"), + SMOOTH_RED_SANDSTONE_STAIRS, + SMOOTH_SANDSTONE(2, "SANDSTONE"), + SMOOTH_SANDSTONE_SLAB("STEP"), + SMOOTH_SANDSTONE_STAIRS, + SMOOTH_STONE("STEP"), + SMOOTH_STONE_SLAB("STEP"), + SNOW, + SNOWBALL("SNOW_BALL"), + SNOW_BLOCK, + SOUL_SAND, + SPAWNER("MOB_SPAWNER"), + SPECTRAL_ARROW("1.9", "ARROW", ""), + SPIDER_EYE, + SPIDER_SPAWN_EGG(52, "MONSTER_EGG"), + SPLASH_POTION, + SPONGE, + SPRUCE_BOAT("BOAT_SPRUCE"), + SPRUCE_BUTTON("WOOD_BUTTON"), + SPRUCE_DOOR("SPRUCE_DOOR_ITEM"), + SPRUCE_FENCE, + SPRUCE_FENCE_GATE, + SPRUCE_LEAVES(1, "LEAVES"), + SPRUCE_LOG(1, "LOG"), + SPRUCE_PLANKS(1, "WOOD"), + SPRUCE_PRESSURE_PLATE("WOOD_PLATE"), + SPRUCE_SAPLING(1, "SAPLING"), + SPRUCE_SIGN("SIGN"), + SPRUCE_SLAB(1, "WOOD_STEP", "WOODEN_SLAB", "WOOD_DOUBLE_STEP"), + SPRUCE_STAIRS("SPRUCE_WOOD_STAIRS"), + SPRUCE_TRAPDOOR("TRAP_DOOR"), + SPRUCE_WALL_SIGN("SIGN_POST", "WALL_SIGN"), + SPRUCE_WOOD(1, "LOG"), + SQUID_SPAWN_EGG(94, "MONSTER_EGG"), + STICK, + STICKY_PISTON("PISTON_BASE", "PISTON_STICKY_BASE"), + STONE, + STONECUTTER("1.14"), + STONE_AXE, + STONE_BRICKS("SMOOTH_BRICK"), + STONE_BRICK_SLAB(4, "STEP", "STONE_SLAB", "DOUBLE_STEP"), + STONE_BRICK_STAIRS("SMOOTH_STAIRS"), + STONE_BRICK_WALL, + STONE_BUTTON, + STONE_HOE, + STONE_PICKAXE, + STONE_PRESSURE_PLATE("STONE_PLATE"), + STONE_SHOVEL("STONE_SPADE"), + STONE_SLAB("STEP", "DOUBLE_STEP"), + STONE_STAIRS, + STONE_SWORD, + STRAY_SPAWN_EGG(6, "MONSTER_EGG"), + STRING, + STRIPPED_ACACIA_LOG("LOG_2"), + STRIPPED_ACACIA_WOOD("LOG_2"), + STRIPPED_BIRCH_LOG(2, "LOG"), + STRIPPED_BIRCH_WOOD(2, "LOG"), + STRIPPED_DARK_OAK_LOG("LOG"), + STRIPPED_DARK_OAK_WOOD("LOG"), + STRIPPED_JUNGLE_LOG(3, "LOG"), + STRIPPED_JUNGLE_WOOD(3, "LOG"), + STRIPPED_OAK_LOG("LOG"), + STRIPPED_OAK_WOOD("LOG"), + STRIPPED_SPRUCE_LOG(1, "LOG"), + STRIPPED_SPRUCE_WOOD(1, "LOG"), + STRUCTURE_BLOCK, + /** + * Originally developers used barrier blocks for its purpose. + * So technically this isn't really considered as a suggested material. + */ + STRUCTURE_VOID("1.10", "", "BARRIER"), + SUGAR, + /** + * Sugar Cane is a known material in pre-1.13 + * Use XBlock when comparing block types. + */ + SUGAR_CANE("SUGAR_CANE_BLOCK"), + SUNFLOWER("DOUBLE_PLANT"), + SUSPICIOUS_STEW("1.14", "MUSHROOM_STEW", ""), + SWEET_BERRIES("1.14"), + SWEET_BERRY_BUSH("1.14", "GRASS", ""), + TALL_GRASS(2, "DOUBLE_PLANT"), + TALL_SEAGRASS(2, "1.13", "TALL_GRASS", ""), + TERRACOTTA("HARD_CLAY"), + TIPPED_ARROW("1.9", "ARROW", ""), + TNT, + TNT_MINECART("EXPLOSIVE_MINECART"), + TORCH, + TOTEM_OF_UNDYING("TOTEM"), + TRADER_LLAMA_SPAWN_EGG(103, "1.14", "MONSTER_EGG", ""), + TRAPPED_CHEST, + TRIDENT("1.13"), + TRIPWIRE, + TRIPWIRE_HOOK, + TROPICAL_FISH(2, "RAW_FISH"), + TROPICAL_FISH_BUCKET("1.13", "BUCKET", "WATER_BUCKET"), + TROPICAL_FISH_SPAWN_EGG("1.13", "MONSTER_EGG"), + TUBE_CORAL("1.13"), + TUBE_CORAL_BLOCK("1.13"), + TUBE_CORAL_FAN("1.13"), + TUBE_CORAL_WALL_FAN, + TURTLE_EGG("1.13", "EGG", ""), + TURTLE_HELMET("1.13", "IRON_HELMET", ""), + TURTLE_SPAWN_EGG("1.13", "CHICKEN_SPAWN_EGG", ""), + VEX_SPAWN_EGG(35, "MONSTER_EGG"), + VILLAGER_SPAWN_EGG(120, "MONSTER_EGG"), + VINDICATOR_SPAWN_EGG(36, "MONSTER_EGG"), + VINE, + /** + * 1.13 tag is not added because it's the same thing as {@link #AIR} + * + * @see #CAVE_AIR + */ + VOID_AIR("AIR"), + WALL_TORCH("TORCH"), + WANDERING_TRADER_SPAWN_EGG("1.14", "VILLAGER_SPAWN_EGG", ""), + /** + * This is used for blocks only. + * In 1.13- WATER will turn into STATIONARY_WATER after it finished spreading. + * After 1.13+ this uses + * https://hub.spigotmc.org/javadocs/spigot/org/bukkit/block/data/Levelled.html water flowing system. + * Use XBlock for this instead. + */ + WATER("STATIONARY_WATER"), + WATER_BUCKET, + WET_SPONGE(1, "SPONGE"), + /** + * Wheat is a known material in pre-1.13 + * Use XBlock when comparing block types. + */ + WHEAT("CROPS"), + WHEAT_SEEDS("SEEDS"), + WHITE_BANNER(15, "BANNER", "STANDING_BANNER"), + WHITE_BED("BED", "BED_BLOCK"), + WHITE_CARPET("CARPET"), + WHITE_CONCRETE("CONCRETE"), + WHITE_CONCRETE_POWDER("CONCRETE_POWDER"), + WHITE_DYE(15, "1.14", "INK_SACK", "BONE_MEAL"), + WHITE_GLAZED_TERRACOTTA("1.12", "HARD_CLAY", "STAINED_CLAY", "WHITE_TERRACOTTA"), + WHITE_SHULKER_BOX, + WHITE_STAINED_GLASS("STAINED_GLASS"), + WHITE_STAINED_GLASS_PANE("THIN_GLASS", "STAINED_GLASS_PANE"), + WHITE_TERRACOTTA("HARD_CLAY", "TERRACOTTA"), + WHITE_TULIP(6, "RED_ROSE"), + WHITE_WALL_BANNER(15, "WALL_BANNER"), + WHITE_WOOL("WOOL"), + WITCH_SPAWN_EGG(66, "MONSTER_EGG"), + WITHER_ROSE("1.14", "BLACK_DYE", ""), + WITHER_SKELETON_SKULL(1, "SKULL", "SKULL_ITEM"), + WITHER_SKELETON_SPAWN_EGG(5, "MONSTER_EGG"), + WITHER_SKELETON_WALL_SKULL(1, "SKULL", "SKULL_ITEM"), + WOLF_SPAWN_EGG(95, "MONSTER_EGG"), + WOODEN_AXE("WOOD_AXE"), + WOODEN_HOE("WOOD_HOE"), + WOODEN_PICKAXE("WOOD_PICKAXE"), + WOODEN_SHOVEL("WOOD_SPADE"), + WOODEN_SWORD("WOOD_SWORD"), + WRITABLE_BOOK("BOOK_AND_QUILL"), + WRITTEN_BOOK, + YELLOW_BANNER(11, "BANNER", "STANDING_BANNER"), + YELLOW_BED(4, "BED", "BED_BLOCK"), + YELLOW_CARPET(4, "CARPET"), + YELLOW_CONCRETE(4, "CONCRETE"), + YELLOW_CONCRETE_POWDER(4, "CONCRETE_POWDER"), + YELLOW_DYE(11, "INK_SACK", "DANDELION_YELLOW"), + YELLOW_GLAZED_TERRACOTTA(4, "1.12", "HARD_CLAY", "STAINED_CLAY", "YELLOW_TERRACOTTA"), + YELLOW_SHULKER_BOX, + YELLOW_STAINED_GLASS(4, "STAINED_GLASS"), + YELLOW_STAINED_GLASS_PANE(4, "THIN_GLASS", "STAINED_GLASS_PANE"), + YELLOW_TERRACOTTA(4, "HARD_CLAY", "STAINED_CLAY"), + YELLOW_WALL_BANNER(11, "WALL_BANNER"), + YELLOW_WOOL(4, "WOOL"), + ZOMBIE_HEAD(2, "SKULL", "SKULL_ITEM"), + ZOMBIE_HORSE_SPAWN_EGG(29, "MONSTER_EGG"), + ZOMBIE_PIGMAN_SPAWN_EGG(57, "MONSTER_EGG"), + ZOMBIE_SPAWN_EGG(54, "MONSTER_EGG"), + ZOMBIE_VILLAGER_SPAWN_EGG(27, "MONSTER_EGG"), + ZOMBIE_WALL_HEAD(2, "SKULL", "SKULL_ITEM"); + + + /** + * An immutable cached set of {@link XMaterial#values()} to avoid allocating memory for + * calling the method every time. + * + * @since 2.0.0 + */ + public static final EnumSet VALUES = EnumSet.allOf(XMaterial.class); + /** + * A set of material names that can be damaged. + *

+ * Most of the names are not complete as this list is intended to be + * checked with {@link String#contains} for memory usage. + * + * @since 1.0.0 + */ + private static final ImmutableSet DAMAGEABLE = ImmutableSet.of( + "HELMET", "CHESTPLATE", "LEGGINGS", "BOOTS", + "SWORD", "AXE", "PICKAXE", "SHOVEL", "HOE", + "ELYTRA", "TRIDENT", "HORSE_ARMOR", "BARDING", + "SHEARS", "FLINT_AND_STEEL", "BOW", "FISHING_ROD", + "CARROT_ON_A_STICK", "CARROT_STICK", "SPADE", "SHIELD" + ); + /** + * XMaterial Paradox (Duplication Check) + *

+ * A map of duplicated material names in 1.13 and 1.12 that will conflict with the legacy names. + * Values are the new material names. + *
+ * Duplicates are normally only checked by keys, not values. + * + * @since 3.0.0 + */ + @SuppressWarnings("UnstableApiUsage") + private static final ImmutableMap DUPLICATED = Maps.immutableEnumMap(ImmutableMap.builder() + .put(MELON, MELON_SLICE) + .put(CARROT, CARROTS) + .put(POTATO, POTATOES) + .put(BEETROOT, BEETROOTS) + .put(BROWN_MUSHROOM, BROWN_MUSHROOM_BLOCK) + .put(BRICK, BRICKS) + .put(RED_MUSHROOM, RED_MUSHROOM_BLOCK) + .put(MAP, FILLED_MAP) + .put(NETHER_BRICK, NETHER_BRICKS) + .build() + ); + /* + * A set of all the legacy names without duplicates. + *

+ * It'll help to free up a lot of memory if it's not used. + * Add it back if you need it. + * + * @see #containsLegacy(String) + * @since 2.2.0 + * + private static final ImmutableSet LEGACY_VALUES = VALUES.stream().map(XMaterial::getLegacy) + .flatMap(Arrays::stream) + .filter(m -> m.charAt(1) == '.') + .collect(Collectors.collectingAndThen(Collectors.toSet(), ImmutableSet::copyOf)); + */ + + /** + * Guava (Google Core Libraries for Java)'s cache for performance and timed caches. + * For strings that match a certain XMaterial. Mostly cached for configs. + * + * @since 1.0.0 + */ + private static final Cache NAME_CACHE = CacheBuilder.newBuilder() + .softValues() + .expireAfterAccess(15, TimeUnit.MINUTES) + .build(); + /** + * Guava (Google Core Libraries for Java)'s cache for performance and timed caches. + * For XMaterials that are already parsed once. + * + * @since 3.0.0 + */ + private static final Cache> PARSED_CACHE = CacheBuilder.newBuilder() + .softValues() + .expireAfterAccess(10, TimeUnit.MINUTES) + .concurrencyLevel(Runtime.getRuntime().availableProcessors()) + .build(); + + /** + * Pre-compiled RegEx pattern. + * Include both replacements to avoid recreating string multiple times with multiple RegEx checks. + * + * @since 3.0.0 + */ + private static final Pattern FORMAT_PATTERN = Pattern.compile("\\W+"); + /** + * The current version of the server in the a form of a major version. + * + * @since 1.0.0 + */ + private static final int VERSION = Integer.parseInt(getMajorVersion(Bukkit.getVersion()).substring(2)); + /** + * Cached result if the server version is after the v1.13 flattening update. + * Please don't mistake this with flat-chested people. It happened. + * + * @since 3.0.0 + */ + private static final boolean ISFLAT = supports(13); + /** + * The data value of this material https://minecraft.gamepedia.com/Java_Edition_data_values/Pre-flattening + * + * @see #getData() + */ + private final byte data; + /** + * A list of material names that was being used for older verions. + * + * @see #getLegacy() + */ + private final String[] legacy; + + XMaterial(int data, String... legacy) { + this.data = (byte) data; + this.legacy = legacy; + } + + XMaterial() { + this(0); + } + + XMaterial(String... legacy) { + this(0, legacy); + } + + /** + * Checks if the version is 1.13 Aquatic Update or higher. + * An invocation of this method yields the cached result from the expression: + *

+ *

+ * {@link #supports(int) 13}} + *
+ * + * @return true if 1.13 or higher. + * @see #getVersion() + * @see #supports(int) + * @since 1.0.0 + */ + public static boolean isNewVersion() { + return ISFLAT; + } + + /** + * This is just an extra method that method that can be used for many cases. + * It can be used in {@link org.bukkit.event.player.PlayerInteractEvent} + * or when accessing {@link org.bukkit.entity.Player#getMainHand()}, + * or other compatibility related methods. + *

+ * An invocation of this method yields exactly the same result as the expression: + *

+ *

+ * {@link #getVersion()} == 1.8 + *
+ * + * @since 2.0.0 + */ + public static boolean isOneEight() { + return !supports(9); + } + + /** + * The current version of the server. + * + * @return the current server version or 0.0 if unknown. + * @see #isNewVersion() + * @since 2.0.0 + */ + public static double getVersion() { + return VERSION; + } + + /** + * When using newer versions of Minecraft ({@link #isNewVersion()}), helps + * to find the old material name with its data value using a cached search for optimization. + * + * @see #matchDefinedXMaterial(String, byte) + * @since 1.0.0 + */ + @Nullable + private static XMaterial requestOldXMaterial(@Nonnull String name, byte data) { + String holder = name + data; + XMaterial material = NAME_CACHE.getIfPresent(holder); + if (material != null) return material; + + for (XMaterial materials : VALUES) { + if ((data == -1 || data == materials.data) && materials.anyMatchLegacy(name)) { + NAME_CACHE.put(holder, materials); + return materials; + } + } + + return null; + } + + /** + * Checks if XMaterial enum contains a material with the given name. + *

+ * You should use {@link #matchXMaterial(String)} instead if you're going + * to get the XMaterial object after checking if it's available in the list + * by doing a simple {@link Optional#isPresent()} check. + * This is just to avoid multiple loops for maximum performance. + * + * @param name name of the material. + * @return true if XMaterial enum has this material. + * @since 1.0.0 + */ + public static boolean contains(@Nonnull String name) { + Validate.notEmpty(name, "Cannot check for null or empty material name"); + name = format(name); + + for (XMaterial materials : VALUES) + if (materials.name().equals(name)) return true; + return false; + } + + /** + * Parses the given material name as an XMaterial with unspecified data value. + * + * @see #matchXMaterial(String, byte) + * @since 2.0.0 + */ + @Nonnull + public static Optional matchXMaterial(@Nonnull String name) { + return matchXMaterial(name, (byte) -1); + } + + /** + * Parses the given material name as an XMaterial. + * Can also be used like: MATERIAL:DATA + *

+ * Examples + *

+     *     {@code INK_SACK:1 -> RED_DYE}
+     *     {@code WOOL, 14  -> RED_WOOL}
+     * 
+ * + * @see #matchDefinedXMaterial(String, byte) + * @see #matchXMaterial(ItemStack) + * @since 2.0.0 + */ + @Nonnull + public static Optional matchXMaterial(@Nonnull String name, byte data) { + Validate.notEmpty(name, "Cannot match a material with null or empty material name"); + Optional oldMatch = matchXMaterialWithData(name); + if (oldMatch.isPresent()) return oldMatch; + + // -1 Determines whether the item's data value is unknown and only the name is given. + // Checking if the item is damageable won't do anything as the data is not going to be checked in requestOldMaterial anyway. + return matchDefinedXMaterial(format(name), data); + } + + /** + * Parses material name and data value from the specified string. + * The seperators are: , or : + * Spaces are allowed. Mostly used when getting materials from config for old school minecrafters. + *

+ * Examples + *

+     *     {@code INK_SACK:1 -> RED_DYE}
+     *     {@code WOOL, 14  -> RED_WOOL}
+     * 
+ * + * @param name the material string that consists of the material name, data and separator character. + * @return the parsed XMaterial. + * @see #matchXMaterial(String) + * @since 3.0.0 + */ + private static Optional matchXMaterialWithData(String name) { + for (char separator : new char[]{',', ':'}) { + int index = name.indexOf(separator); + if (index == -1) continue; + + String mat = format(name.substring(0, index)); + byte data = Byte.parseByte(StringUtils.deleteWhitespace(name.substring(index + 1))); + return matchDefinedXMaterial(mat, data); + } + + return Optional.empty(); + } + + /** + * Parses the given material as an XMaterial. + * + * @throws IllegalArgumentException may be thrown as an unexpected exception. + * @see #matchDefinedXMaterial(String, byte) + * @see #matchXMaterial(ItemStack) + * @since 2.0.0 + */ + @Nonnull + public static XMaterial matchXMaterial(@Nonnull Material material) { + Objects.requireNonNull(material, "Cannot match null material"); + return matchDefinedXMaterial(material.name(), (byte) -1) + .orElseThrow(() -> new IllegalArgumentException("Unsupported Material: " + material)); + } + + /** + * Parses the given item as an XMaterial using its material and data value (durability). + * + * @param item the ItemStack to match. + * @return an XMaterial if matched any. + * @throws IllegalArgumentException may be thrown as an unexpected exception. + * @see #matchDefinedXMaterial(String, byte) + * @since 2.0.0 + */ + @Nonnull + @SuppressWarnings("deprecation") + public static XMaterial matchXMaterial(@Nonnull ItemStack item) { + Objects.requireNonNull(item, "Cannot match null ItemStack"); + String material = item.getType().name(); + return matchDefinedXMaterial(material, + isDamageable(material) ? (byte) 0 : (byte) item.getDurability()) + .orElseThrow(() -> new IllegalArgumentException("Unsupported Material: " + material)); + } + + /** + * Parses the given material name and data value as an XMaterial. + * All the values passed to this method will not be null or empty and are formatted correctly. + * + * @param name the formatted name of the material. + * @param data the data value of the material. + * @return an XMaterial (with the same data value if specified) + * @see #matchXMaterial(String, byte) + * @see #matchXMaterial(Material) + * @see #matchXMaterial(int, byte) + * @see #matchXMaterial(ItemStack) + * @since 3.0.0 + */ + @Nonnull + private static Optional matchDefinedXMaterial(@Nonnull String name, byte data) { + boolean duplicated = isDuplicated(name); + + // Do basic number and boolean checks before accessing more complex enum stuff. + // Maybe we can simplify (ISFLAT || !duplicated) with the (!ISFLAT && duplicated) under it to save a few nanoseconds? + // if (!Boolean.valueOf(Boolean.getBoolean(Boolean.TRUE.toString())).equals(Boolean.FALSE.booleanValue())) return null; + if (data <= 0 && (ISFLAT || !duplicated)) { + // Apparently the transform method is more efficient than toJavaUtil() + // toJavaUtil isn't even supported in older versions. + Optional xMat = Enums.getIfPresent(XMaterial.class, name).transform(Optional::of).or(Optional.empty()); + if (xMat.isPresent()) return xMat; + } + + // XMaterial Paradox (Duplication Check) + // I've concluded that this is just an infinite loop that keeps + // going around the Singular Form and the Plural Form materials. A waste of brain cells and a waste of time. + // This solution works just fine anyway. + if (!ISFLAT && duplicated) return Optional.ofNullable(requestDuplicatedXMaterial(name, data)); + return Optional.ofNullable(requestOldXMaterial(name, data)); + } + + /** + * XMaterial Paradox (Duplication Check) + * Checks if the material has any duplicates. + *

+ * Example: + *

{@code MELON, CARROT, POTATO, BEETROOT -> true} + * + * @param name the name of the material to check. + * @return true if there's a duplicated material for this material, otherwise false. + * @see #isDuplicated() + * @since 2.0.0 + */ + public static boolean isDuplicated(@Nonnull String name) { + Validate.notEmpty(name, "Cannot check duplication for null or empty material name"); + name = format(name); + + // Don't use matchXMaterial() since this method is being called from matchXMaterial() itself and will cause a StackOverflowError. + for (Map.Entry duplicated : DUPLICATED.entrySet()) + if (duplicated.getKey().name().equals(name) || duplicated.getKey().anyMatchLegacy(name)) return true; + return false; + } + + /** + * Gets the XMaterial based on the material's ID (Magic Value) and data value.
+ * You should avoid using this for performance issues. + * + * @param id the ID (Magic value) of the material. + * @param data the data value of the material. + * @return a parsed XMaterial with the same ID and data value. + * @see #matchXMaterial(ItemStack) + * @since 2.0.0 + */ + @Nonnull + public static Optional matchXMaterial(int id, byte data) { + if (id < 0 || data < 0) return Optional.empty(); + + // Looping through Material.values() will take longer. + for (XMaterial materials : VALUES) + if (materials.data == data && materials.getId() == id) return Optional.of(materials); + return Optional.empty(); + } + + /** + * A solution for XMaterial Paradox. + * Manually parses the duplicated materials to find the exact material based on the server version. + * + * @param name the name of the material. + * @return the duplicated XMaterial based on the version. + * @throws IllegalArgumentException may be thrown. If thrown, it's a bug. + * @since 2.0.0 + */ + @Nullable + private static XMaterial requestDuplicatedXMaterial(@Nonnull String name, byte data) { + XMaterial mat = requestOldXMaterial(name, data); + // If ends with "S" -> Plural Form Material + return mat.name().charAt(mat.name().length() - 1) == 'S' ? Enums.getIfPresent(XMaterial.class, name).orNull() : mat; + } + + /** + * Always returns the value with the given duplicated material key name. + * + * @param name the name of the material. + * @return the new XMaterial of this duplicated material. + * @see #getXMaterialIfDuplicated(String) + * @since 2.0.0 + */ + @Nonnull + public static Optional getNewXMaterialIfDuplicated(@Nonnull String name) { + Validate.notEmpty(name, "Cannot get new duplicated material for null or empty material name"); + name = format(name); + + for (Map.Entry duplicated : DUPLICATED.entrySet()) + if (duplicated.getKey().name().equals(name)) return Optional.of(duplicated.getKey()); + return Optional.empty(); + } + + /** + * Checks if the item is duplicated for a different purpose in new versions from {@link #DUPLICATED}. + * + * @param name the name of the material. + * @return the other XMaterial (key or value) of the XMaterial (key or value). + * @see #matchXMaterial(String, byte) + * @since 2.0.0 + */ + @Nullable + public static XMaterial getXMaterialIfDuplicated(@Nonnull String name) { + Validate.notEmpty(name, "Cannot get duplicated material for null or empty material name"); + name = format(name); + + for (Map.Entry duplicated : DUPLICATED.entrySet()) + if (duplicated.getKey().name().equals(name)) return duplicated.getValue(); + else if (duplicated.getValue().name().equals(name)) return duplicated.getKey(); + + return null; + } + + /** + * Attempts to build the string like an enum name. + * Removes all the spaces, numbers and extra non-English characters. Also removes some config/in-game based strings. + * + * @param name the material name to modify. + * @return a Material enum name. + * @since 2.0.0 + */ + @Nonnull + private static String format(@Nonnull String name) { + return FORMAT_PATTERN.matcher( + name.trim().replace('-', '_').replace(' ', '_')).replaceAll("").toUpperCase(Locale.ENGLISH); + } + + /** + * Checks if the specified version is the same version or higher than the current server version. + * + * @param version the major version to be checked. "1." is ignored. E.g. 1.12 = 12 | 1.9 = 9 + * @return true of the version is equal or higher than the current version. + * @since 2.0.0 + */ + public static boolean supports(int version) { + return VERSION >= version; + } + + /** + * Converts the enum names to a more friendly and readable string. + * + * @return a formatted string. + * @see #toWord(String) + * @since 2.1.0 + */ + @Nonnull + public static String toWord(@Nonnull Material material) { + Objects.requireNonNull(material, "Cannot translate a null material to a word"); + return toWord(material.name()); + } + + /** + * Parses an enum name to a normal word. + * Normal names have underlines removed and each word capitalized. + *

+ * Examples: + *

+     *     EMERALD                 -> Emerald
+     *     EMERALD_BLOCK           -> Emerald Block
+     *     ENCHANTED_GOLDEN_APPLE  -> Enchanted Golden Apple
+     * 
+ * + * @param name the name of the enum. + * @return a cleaned more readable enum name. + * @since 2.1.0 + */ + @Nonnull + private static String toWord(@Nonnull String name) { + return WordUtils.capitalize(name.replace('_', ' ').toLowerCase(Locale.ENGLISH)); + } + + /** + * Gets the exact major version (..., 1.9, 1.10, ..., 1.14) + * + * @param version Supports {@link Bukkit#getVersion()}, {@link Bukkit#getBukkitVersion()} and normal formats such as "1.14" + * @return the exact major version. + * @since 2.0.0 + */ + @Nonnull + public static String getMajorVersion(@Nonnull String version) { + Validate.notEmpty(version, "Cannot get major Minecraft version from null or empty string"); + + // getVersion() + int index = version.lastIndexOf("MC:"); + if (index != -1) { + version = version.substring(index + 4, version.length() - 1); + } else if (version.endsWith("SNAPSHOT")) { + // getBukkitVersion() + index = version.indexOf('-'); + version = version.substring(0, index); + } + + // 1.13.2, 1.14.4, etc... + int lastDot = version.lastIndexOf('.'); + if (version.indexOf('.') != lastDot) version = version.substring(0, lastDot); + + return version; + } + + /** + * Checks if the material can be damaged by using it. + * Names going through this method are not formatted. + * + * @param name the name of the material. + * @return true of the material can be damaged. + * @see #isDamageable() + * @since 1.0.0 + */ + public static boolean isDamageable(@Nonnull String name) { + Objects.requireNonNull(name, "Material name cannot be null"); + for (String damageable : DAMAGEABLE) + if (name.contains(damageable)) return true; + return false; + } + + /** + * Checks if the list of given material names matches the given base material. + * Mostly used for configs. + *

+ * Supports {@link String#contains} {@code CONTAINS:NAME} and Regular Expression {@code REGEX:PATTERN} formats. + *

+ * Example: + *

+     *     XMaterial material = {@link #matchXMaterial(ItemStack)};
+     *     if (material.isOneOf(plugin.getConfig().getStringList("disabled-items")) return;
+     * 
+ *
+ * {@code CONTAINS} Examples: + *
+     *     {@code "CONTAINS:CHEST" -> CHEST, ENDERCHEST, TRAPPED_CHEST -> true}
+     *     {@code "cOnTaINS:dYe" -> GREEN_DYE, YELLOW_DYE, BLUE_DYE, INK_SACK -> true}
+     * 
+ *

+ * {@code REGEX} Examples + *

+     *     {@code "REGEX:^.+_.+_.+$" -> Every Material with 3 underlines or more: SHULKER_SPAWN_EGG, SILVERFISH_SPAWN_EGG, SKELETON_HORSE_SPAWN_EGG}
+     *     {@code "REGEX:^.{1,3}$" -> Material names that have 3 letters only: BED, MAP, AIR}
+     * 
+ *

+ * The reason that there are tags for {@code CONTAINS} and {@code REGEX} + * is for the performance. + * Please avoid using the {@code REGEX} tag if you can use the {@code CONTAINS} tag. + * It'll have a huge impact on performance. + * Please avoid using {@code (capturing groups)} there's no use for them in this case. + * If you want to use groups, use {@code (?: non-capturing groups)}. It's faster. + *

+ * You can make a cache for pre-compiled RegEx patterns from your config. + * It's better, but not much faster since these patterns are not that complex. + *

+ * Want to learn RegEx? You can mess around in RegExr website. + * + * @param material the base material to match other materials with. + * @param materials the material names to check base material on. + * @return true if one of the given material names is similar to the base material. + * @since 3.1.1 + */ + public static boolean isOneOf(@Nonnull Material material, @Nullable List materials) { + if (materials == null || materials.isEmpty()) return false; + Objects.requireNonNull(material, "Cannot match materials with a null material"); + String name = material.name(); + + for (String comp : materials) { + comp = comp.toUpperCase(); + if (comp.startsWith("CONTAINS:")) { + comp = format(comp.substring(9)); + if (name.contains(comp)) return true; + continue; + } + if (comp.startsWith("REGEX:")) { + comp = comp.substring(6); + if (name.matches(comp)) return true; + continue; + } + + // Direct Object Equals + Optional mat = matchXMaterial(comp); + if (mat.isPresent() && mat.get().parseMaterial() == material) return true; + } + return false; + } + + /** + * Gets the version which this material was added in. + * If the material doesn't have a version it'll return 0; + * + * @return the Minecraft version which tihs material was added in. + * @since 3.0.0 + */ + public int getMaterialVersion() { + if (this.legacy.length == 0) return 0; + String version = this.legacy[0]; + if (version.charAt(1) != '.') return 0; + + return Integer.parseInt(version.substring(2)); + } + + /** + * Sets the {@link Material} (and data value on older versions) of an item. + * Damageable materials will not have their durability changed. + *

+ * Use {@link #parseItem()} instead when creating new ItemStacks. + * + * @param item the item to change its type. + * @see #parseItem() + * @since 3.0.0 + */ + @Nonnull + @SuppressWarnings("deprecation") + public ItemStack setType(@Nonnull ItemStack item) { + Objects.requireNonNull(item, "Cannot set material for null ItemStack"); + + item.setType(this.parseMaterial()); + if (!ISFLAT && !this.isDamageable()) item.setDurability(this.data); + return item; + } + + /** + * Checks if the list of given material names matches the given base material. + * Mostly used for configs. + * + * @param materials the material names to check base material on. + * @return true if one of the given material names is similar to the base material. + * @see #isOneOf(Material, List) + * @since 3.0.0 + */ + public boolean isOneOf(@Nullable List materials) { + Material material = this.parseMaterial(); + if (material == null) return false; + return isOneOf(material, materials); + } + + /** + * Checks if the given string matches any of this material's legacy material names. + * All the values passed to this method will not be null or empty and are formatted correctly. + * + * @param name the name to check + * @return true if it's one of the legacy names. + * @since 2.0.0 + */ + private boolean anyMatchLegacy(@Nonnull String name) { + for (String legacy : this.legacy) { + if (legacy.isEmpty()) break; // Left-side suggestion list + if (name.equals(legacy)) return true; + } + return false; + } + + /** + * User-friendly readable name for this material + * In most cases you should be using {@link #name()} instead. + * + * @return string of this object. + * @see #toWord(String) + * @since 3.0.0 + */ + @Override + public String toString() { + return toWord(this.name()); + } + + /** + * Gets the ID (Magic value) of the material. + * + * @return the ID of the material or -1 if it's a new block or the material is not supported. + * @see #matchXMaterial(int, byte) + * @since 2.2.0 + */ + @SuppressWarnings("deprecation") + public int getId() { + if (this.data != 0 || (this.legacy.length != 0 && Integer.parseInt(this.legacy[0].substring(2)) >= 13)) return -1; + Material material = this.parseMaterial(); + return material == null ? -1 : material.getId(); + } + + /** + * Checks if the material has any duplicates. + * + * @return true if there is a duplicated name for this material, otherwise false. + * @see #getXMaterialIfDuplicated() + * @see #isDuplicated(String) + * @since 2.0.0 + */ + public boolean isDuplicated() { + return DUPLICATED.containsKey(this); + } + + /** + * Checks if the item is duplicated for a different purpose in new versions. + * + * @return true if the item's name is duplicated, otherwise false. + * @see #isDuplicated() + * @see #getNewXMaterialIfDuplicated(String) + * @since 2.0.0 + */ + @Nullable + public XMaterial getXMaterialIfDuplicated() { + return DUPLICATED.get(this); + } + + /** + * Checks if the material can be damaged by using it. + * Names going through this method are not formatted. + * + * @return true if the item can be damaged (have its durability changed), otherwise false. + * @see #isDamageable(String) + * @since 1.0.0 + */ + public boolean isDamageable() { + return isDamageable(this.name()); + } + + /** + * The data value of this material pre-flattening. + *

+ * Can be accessed with {@link ItemStack#getData()} then {@code MaterialData#getData()} + * or {@link ItemStack#getDurability()} if not damageable. + * + * @return data of this material, or 0 if none. + * @since 1.0.0 + */ + @SuppressWarnings("deprecation") + public byte getData() { + return data; + } + + /** + * Get a list of materials names that was previously used by older versions. + * If the material was added in a new version {@link #isNewVersion()}, + * then the first element will indicate which version the material was added in. + * + * @return a list of legacy material names and the first element as the version the material was added in if new. + * @since 1.0.0 + */ + @Nonnull + public String[] getLegacy() { + return legacy; + } + + /** + * Parses an item from this XMaterial. + * Uses data values on older versions. + * + * @return an ItemStack with the same material (and data value if in older versions.) + * @see #parseItem(boolean) + * @see #setType(ItemStack) + * @since 1.0.0 + */ + @Nullable + public ItemStack parseItem() { + return parseItem(false); + } + + /** + * Parses an item from this XMaterial. + * Uses data values on older versions. + * + * @param suggest if true {@link #parseMaterial(boolean)} true will be used. + * @return an ItemStack with the same material (and data value if in older versions.) + * @see #setType(ItemStack) + * @since 2.0.0 + */ + @Nullable + @SuppressWarnings("deprecation") + public ItemStack parseItem(boolean suggest) { + Material material = this.parseMaterial(suggest); + if (material == null) return null; + return ISFLAT ? new ItemStack(material) : new ItemStack(material, 1, this.data); + } + + /** + * Parses the material of this XMaterial. + * + * @return the material related to this XMaterial based on the server version. + * @see #parseMaterial(boolean) + * @since 1.0.0 + */ + @Nullable + public Material parseMaterial() { + return parseMaterial(false); + } + + /** + * Parses the material of this XMaterial and accepts suggestions. + * + * @param suggest use a suggested material (from older materials) if the material is added in a later version of Minecraft. + * @return the material related to this XMaterial based on the server version. + * @see #matchXMaterial(String, byte) + * @since 2.0.0 + */ + @SuppressWarnings("OptionalAssignedToNull") + @Nullable + public Material parseMaterial(boolean suggest) { + Optional cache = PARSED_CACHE.getIfPresent(this); + if (cache != null) return cache.orElse(null); + Material mat; + + if (!ISFLAT && this.isDuplicated()) mat = requestOldMaterial(suggest); + else { + mat = Material.getMaterial(this.name()); + if (mat == null) mat = requestOldMaterial(suggest); + } + + if (mat != null) PARSED_CACHE.put(this, Optional.ofNullable(mat)); + return mat; + } + + /** + * Parses a material for older versions of Minecraft. + * Accepts suggestions if specified. + * + * @param suggest if true suggested materials will be considered for old versions. + * @return a parsed material suitable for the current Minecraft version. + * @see #parseMaterial(boolean) + * @since 2.0.0 + */ + @Nullable + private Material requestOldMaterial(boolean suggest) { + for (int i = this.legacy.length - 1; i >= 0; i--) { + String legacy = this.legacy[i]; + + // Check if we've reached the end and the last string is our + // material version. + if (i == 0 && legacy.charAt(1) == '.') return null; + + // According to the suggestion list format, all the other names continuing + // from here are considered as a "suggestion" + // The empty string is an indicator for suggestion list on the left side. + if (legacy.isEmpty()) { + if (suggest) continue; + break; + } + + Material material = Material.getMaterial(legacy); + if (material != null) return material; + } + return null; + } + + /** + * Checks if an item has the same material (and data value on older versions). + * + * @param item item to check. + * @return true if the material is the same as the item's material (and data value if on older versions), otherwise false. + * @since 1.0.0 + */ + @SuppressWarnings("deprecation") + public boolean isSimilar(@Nonnull ItemStack item) { + Objects.requireNonNull(item, "Cannot compare with null ItemStack"); + if (item.getType() != this.parseMaterial()) return false; + return ISFLAT || this.isDamageable() || item.getDurability() == this.data; + } + + /** + * Gets the suggested material names that can be used + * if the material is not supported in the current version. + * + * @return a list of suggested material names. + * @see #parseMaterial(boolean) + * @since 2.0.0 + */ + @Nonnull + public List getSuggestions() { + if (this.legacy.length == 0 || this.legacy[0].charAt(1) != '.') return new ArrayList<>(); + List suggestions = new ArrayList<>(); + for (String legacy : this.legacy) { + if (legacy.isEmpty()) break; + suggestions.add(legacy); + } + return suggestions; + } + + /** + * Checks if this material is supported in the current version. + * Suggested materials will be ignored. + *

+ * Note that you should use {@link #parseMaterial()} and check if it's null + * if you're going to parse and use the material later. + * + * @return true if the material exists in {@link Material} list. + * @since 2.0.0 + */ + public boolean isSupported() { + int version = this.getMaterialVersion(); + if (version != 0) return supports(version); + + Material material = Material.getMaterial(this.name()); + if (material != null) return true; + return requestOldMaterial(false) != null; + } + + /** + * Checks if the material is newly added after the 1.13 Aquatic Update. + * + * @return true if the material was newly added, otherwise false. + * @see #getMaterialVersion() + * @since 2.0.0 + */ + public boolean isFromNewSystem() { + return this.legacy.length != 0 && Integer.parseInt(this.legacy[0].substring(2)) > 13; + } +} \ No newline at end of file diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..00d13a9 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,33 @@ +License: 'TP-ABC-ABC' +AntiFreerun: + Enabled: false +Fastpass: + ItemName: '&6ThemeParkTicket' + MachineSign: + Row1: "&3[ThemePark]" + Row2: "&bMachine" + ControlSign: + Row1: "&3[ThemePark]" + Row2: "&bControl" +DiscordWebhook: + Enabled: false + WebhookURL: "" + Embed: + Title: "%RideName% - Status change" + Copyright: "Copyright ThemePark 2020" + CopyrightImage: "https://www.spigotmc.org/data/resource_icons/48/48648.jpg?1544984106" + Colors: + OPEN: 0x55FF55 + CLOSED: 0xAA0000 + MAINTENANCE: 0xFFAA00 + CONSTRUCTION: 0xAAAAAA + MALFUNCTION: 0xAA00AA + ACTIVE: 0x55FF55 + INACTIVE: 0xAA0000 +WaitingRows: + MinutesPerWaitingPerson: 2 + Sign: + Row1: "&3[ThemePark]" + Row2: "&bWaitingrow" + Row3: "%AttractionName%" + Row4: "%WaitTime% min." \ No newline at end of file diff --git a/src/main/resources/messages.yml b/src/main/resources/messages.yml new file mode 100644 index 0000000..39ed743 --- /dev/null +++ b/src/main/resources/messages.yml @@ -0,0 +1,45 @@ +General: + OnlyForPlayers: "&cOnly players can run this command!" + NoPermission: "&cYou don't have the permission to do this!" +Gates: + Open: + ChooseDirection: "&cYou have to choose between: NORTH, EAST, SOUTH and WEST" + AlreadyOpen: "&cThat gate is already opened!" + Opened: "&aThat gate is now open!" + OpenedPlayers: "&aThat gate is now open! A maximum of %COUNT% %SINGMULTI:visitor:visitors% can now walk through it!" + Close: + AlreadyClosed: "&cThat gate is already closed!" + Closed: "&aThat gate is now closed!" + NoGate: "&cThat block is not a gate!" + NoGateConsole: "&cThe gate on the location %LocationXYZ% in the world %World% couldn't be find! It's now removed of the data!" + InvalidDirection: "&cYou can't walk through that gate in this direction!" +Lamps: + TurnOn: + NoLampOrAlreadyOn: "&cCouldn't turn that lamp on! Maybe it's already on, or it's not a redstone lamp." + IncorrectNumber: "&cThe number of seconds isn't correct! Give a number please." + TurnedOn: "&aThat lamp is succesfully turned on!" + TurnedOnSeconds: "&fThat lamp is succesfully turned on for %COUNT% %SINGMULTI:second:seconds%!" + TurnOff: + NoLampOrAlreadyOff: "&cCouldn't turn that lamp off! Maybe it's already off, or it's not a redstone lamp." + TurnedOff: "&aThat lamp is succesfully turned off!" +Fastpass: + NotEnoughMoney: "&cYou don't have enough money for this Fastpass ticket!" + AlreadyHasOne: "&cYou've already got a Fastpass ticket for this ride!" + MoneyWithdrawed: "&aA amount of $%MoneyAmount%,- is taken of your account!" + WrongLocation: "&cThat sign contains wrong coordinates!" + Expired: "&cYour fastpass ticket is already expired!" + NotOpen: "&cThat ride is not open!" + YouNeedATicket: "&cYou need a (correct) ticket to use the FastPass row!" + Succes: "&aHave fun in this ride!" +Malfunction: + ReasonQuestion: "&aPlease type the reason of the malfunction! (Type STOP to set it to Unknown)" + Reported: "&aThe malfunction has been reported to the Technical Service!" + Fixed: "&aThe malfunction has succesfully been removed!" + NotAllowedToChange: "&cYou can't change the status if a ride has a malfunction!" +WaitingRows: + WrongLocation: "&cA waitingrow sign (from the attraction %AttractionID%) couldn't be found! It will be deleted." + WrongAttraction: "&cThat attraction doesn't exists!" + ForgotSelection: "&cYou forgot to select a region! Please select a point one (left) and point two (right) with a STICK." + PosOneSelected: "&aSuccesfully selected position one!" + PosTwoSelected: "&aSuccesfully selected position two!" + SignCreated: "&aYou've succesfully created a WaitingRow sign!" \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..4bbf9f1 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,13 @@ +name: ThemeParkPlus +version: ${project.version} +main: nl.sbdeveloper.themeparkplus.ThemeParkPlus +api-version: "1.13" +authors: [SBDeveloper] +softdepend: [Vault, ThemePark, WorldEdit] +description: Plus version of ThemePark! +website: https://sbdplugins.nl +commands: + themeparkplus: + description: The ThemeParkPlus command! + permission: tpp.command + aliases: ['tpp', 'themeparkp'] \ No newline at end of file